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.velocity = '0 0 0';
1185 self.state = STATE_BOTTOM;
1186 self.think = SUB_Null;
1189 void spawnfunc_func_door()
1191 //if (!self.deathtype) // map makers can override this
1192 // self.deathtype = " got in the way";
1195 self.max_health = self.health;
1196 if not(InitMovingBrushTrigger())
1198 self.effects |= EF_LOWPRECISION;
1199 self.classname = "door";
1201 self.blocked = door_blocked;
1202 self.use = door_use;
1204 if(self.spawnflags & 8)
1207 if(self.dmg && (!self.message))
1208 self.message = "was squished";
1209 if(self.dmg && (!self.message2))
1210 self.message2 = "was squished by";
1212 if (self.sounds > 0)
1214 precache_sound ("plats/medplat1.wav");
1215 precache_sound ("plats/medplat2.wav");
1216 self.noise2 = "plats/medplat1.wav";
1217 self.noise1 = "plats/medplat2.wav";
1227 self.pos1 = self.origin;
1228 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1230 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1231 // but spawn in the open position
1232 if (self.spawnflags & DOOR_START_OPEN)
1233 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1235 self.state = STATE_BOTTOM;
1239 self.takedamage = DAMAGE_YES;
1240 self.event_damage = door_damage;
1246 self.touch = door_touch;
1248 // LinkDoors can't be done until all of the doors have been spawned, so
1249 // the sizes can be detected properly.
1250 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1252 self.reset = door_reset;
1255 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1256 if two doors touch, they are assumed to be connected and operate as a unit.
1258 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1260 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1261 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1262 must have set trigger_reverse to 1.
1263 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1265 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).
1267 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1268 "angle" determines the destination angle for opening. negative values reverse the direction.
1269 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1270 "health" if set, door must be shot open
1271 "speed" movement speed (100 default)
1272 "wait" wait before returning (3 default, -1 = never return)
1273 "dmg" damage to inflict when blocked (2 default)
1280 FIXME: only one sound set available at the time being
1283 void door_rotating_reset()
1285 self.angles = self.pos1;
1286 self.avelocity = '0 0 0';
1287 self.state = STATE_BOTTOM;
1288 self.think = SUB_Null;
1291 void door_rotating_init_startopen()
1293 self.angles = self.movedir;
1294 self.pos2 = '0 0 0';
1295 self.pos1 = self.movedir;
1299 void spawnfunc_func_door_rotating()
1302 //if (!self.deathtype) // map makers can override this
1303 // self.deathtype = " got in the way";
1305 // I abuse "movedir" for denoting the axis for now
1306 if (self.spawnflags & 64) // X (untested)
1307 self.movedir = '0 0 1';
1308 else if (self.spawnflags & 128) // Y (untested)
1309 self.movedir = '1 0 0';
1311 self.movedir = '0 1 0';
1313 if (self.angles_y==0) self.angles_y = 90;
1315 self.movedir = self.movedir * self.angles_y;
1316 self.angles = '0 0 0';
1318 self.max_health = self.health;
1319 if not(InitMovingBrushTrigger())
1321 //self.effects |= EF_LOWPRECISION;
1322 self.classname = "door_rotating";
1324 self.blocked = door_blocked;
1325 self.use = door_use;
1327 if(self.spawnflags & 8)
1330 if(self.dmg && (!self.message))
1331 self.message = "was squished";
1332 if(self.dmg && (!self.message2))
1333 self.message2 = "was squished by";
1335 if (self.sounds > 0)
1337 precache_sound ("plats/medplat1.wav");
1338 precache_sound ("plats/medplat2.wav");
1339 self.noise2 = "plats/medplat1.wav";
1340 self.noise1 = "plats/medplat2.wav";
1347 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1349 self.pos1 = '0 0 0';
1350 self.pos2 = self.movedir;
1352 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1353 // but spawn in the open position
1354 if (self.spawnflags & DOOR_START_OPEN)
1355 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1357 self.state = STATE_BOTTOM;
1361 self.takedamage = DAMAGE_YES;
1362 self.event_damage = door_damage;
1368 self.touch = door_touch;
1370 // LinkDoors can't be done until all of the doors have been spawned, so
1371 // the sizes can be detected properly.
1372 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1374 self.reset = door_rotating_reset;
1378 =============================================================================
1382 =============================================================================
1385 void() fd_secret_move1;
1386 void() fd_secret_move2;
1387 void() fd_secret_move3;
1388 void() fd_secret_move4;
1389 void() fd_secret_move5;
1390 void() fd_secret_move6;
1391 void() fd_secret_done;
1393 float SECRET_OPEN_ONCE = 1; // stays open
1394 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1395 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1396 float SECRET_NO_SHOOT = 8; // only opened by trigger
1397 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1400 void fd_secret_use()
1403 string message_save;
1405 self.health = 10000;
1406 self.bot_attack = TRUE;
1408 // exit if still moving around...
1409 if (self.origin != self.oldorigin)
1412 message_save = self.message;
1413 self.message = ""; // no more message
1414 SUB_UseTargets(); // fire all targets / killtargets
1415 self.message = message_save;
1417 self.velocity = '0 0 0';
1419 // Make a sound, wait a little...
1421 if (self.noise1 != "")
1422 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1423 self.nextthink = self.ltime + 0.1;
1425 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1426 makevectors(self.mangle);
1430 if (self.spawnflags & SECRET_1ST_DOWN)
1431 self.t_width = fabs(v_up * self.size);
1433 self.t_width = fabs(v_right * self.size);
1437 self.t_length = fabs(v_forward * self.size);
1439 if (self.spawnflags & SECRET_1ST_DOWN)
1440 self.dest1 = self.origin - v_up * self.t_width;
1442 self.dest1 = self.origin + v_right * (self.t_width * temp);
1444 self.dest2 = self.dest1 + v_forward * self.t_length;
1445 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1446 if (self.noise2 != "")
1447 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1450 // Wait after first movement...
1451 void fd_secret_move1()
1453 self.nextthink = self.ltime + 1.0;
1454 self.think = fd_secret_move2;
1455 if (self.noise3 != "")
1456 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1459 // Start moving sideways w/sound...
1460 void fd_secret_move2()
1462 if (self.noise2 != "")
1463 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1464 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1467 // Wait here until time to go back...
1468 void fd_secret_move3()
1470 if (self.noise3 != "")
1471 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1472 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1474 self.nextthink = self.ltime + self.wait;
1475 self.think = fd_secret_move4;
1480 void fd_secret_move4()
1482 if (self.noise2 != "")
1483 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1484 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1488 void fd_secret_move5()
1490 self.nextthink = self.ltime + 1.0;
1491 self.think = fd_secret_move6;
1492 if (self.noise3 != "")
1493 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1496 void fd_secret_move6()
1498 if (self.noise2 != "")
1499 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1500 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1503 void fd_secret_done()
1505 if (self.spawnflags&SECRET_YES_SHOOT)
1507 self.health = 10000;
1508 self.takedamage = DAMAGE_YES;
1509 //self.th_pain = fd_secret_use;
1511 if (self.noise3 != "")
1512 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1515 void secret_blocked()
1517 if (time < self.attack_finished_single)
1519 self.attack_finished_single = time + 0.5;
1520 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1532 if not(other.iscreature)
1534 if (self.attack_finished_single > time)
1537 self.attack_finished_single = time + 2;
1541 if (other.flags & FL_CLIENT)
1542 centerprint (other, self.message);
1543 play2(other, "misc/talk.wav");
1549 if (self.spawnflags&SECRET_YES_SHOOT)
1551 self.health = 10000;
1552 self.takedamage = DAMAGE_YES;
1554 setorigin(self, self.oldorigin);
1555 self.think = SUB_Null;
1558 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1559 Basic secret door. Slides back, then to the side. Angle determines direction.
1560 wait = # of seconds before coming back
1561 1st_left = 1st move is left of arrow
1562 1st_down = 1st move is down from arrow
1563 always_shoot = even if targeted, keep shootable
1564 t_width = override WIDTH to move back (or height if going down)
1565 t_length = override LENGTH to move sideways
1566 "dmg" damage to inflict when blocked (2 default)
1568 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1575 void spawnfunc_func_door_secret()
1577 /*if (!self.deathtype) // map makers can override this
1578 self.deathtype = " got in the way";*/
1584 self.mangle = self.angles;
1585 self.angles = '0 0 0';
1586 self.classname = "door";
1587 if not(InitMovingBrushTrigger())
1589 self.effects |= EF_LOWPRECISION;
1591 self.touch = secret_touch;
1592 self.blocked = secret_blocked;
1594 self.use = fd_secret_use;
1599 self.spawnflags |= SECRET_YES_SHOOT;
1601 if(self.spawnflags&SECRET_YES_SHOOT)
1603 self.health = 10000;
1604 self.takedamage = DAMAGE_YES;
1605 self.event_damage = fd_secret_use;
1607 self.oldorigin = self.origin;
1609 self.wait = 5; // 5 seconds before closing
1611 self.reset = secret_reset;
1615 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1616 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1617 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
1618 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1619 height: amplitude modifier (default 32)
1620 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1621 noise: path/name of looping .wav file to play.
1622 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1626 void func_fourier_controller_think()
1631 self.nextthink = time + 0.1;
1633 n = floor((tokenize_console(self.owner.netname)) / 5);
1634 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1636 v = self.owner.destvec;
1638 for(i = 0; i < n; ++i)
1640 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1641 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;
1644 // * 10 so it will arrive in 0.1 sec
1645 self.owner.velocity = (v - self.owner.origin) * 10;
1648 void spawnfunc_func_fourier()
1650 local entity controller;
1651 if (self.noise != "")
1653 precache_sound(self.noise);
1654 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1661 self.destvec = self.origin;
1662 self.cnt = 360 / self.speed;
1664 self.blocked = generic_plat_blocked;
1665 if(self.dmg & (!self.message))
1666 self.message = " was squished";
1667 if(self.dmg && (!self.message2))
1668 self.message2 = "was squished by";
1669 if(self.dmg && (!self.dmgtime))
1670 self.dmgtime = 0.25;
1671 self.dmgtime2 = time;
1673 if(self.netname == "")
1674 self.netname = "1 0 0 0 1";
1676 if not(InitMovingBrushTrigger())
1679 // wait for targets to spawn
1680 controller = spawn();
1681 controller.classname = "func_fourier_controller";
1682 controller.owner = self;
1683 controller.nextthink = time + 1;
1684 controller.think = func_fourier_controller_think;
1685 self.nextthink = self.ltime + 999999999;
1686 self.think = SUB_Null;
1688 // Savage: Reduce bandwith, critical on e.g. nexdm02
1689 self.effects |= EF_LOWPRECISION;
1691 // TODO make a reset function for this one
1694 // reusing some fields havocbots declared
1695 .entity wp00, wp01, wp02, wp03;
1697 .float targetfactor, target2factor, target3factor, target4factor;
1698 .vector targetnormal, target2normal, target3normal, target4normal;
1700 vector func_vectormamamam_origin(entity o, float t)
1712 p = e.origin + t * e.velocity;
1714 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1716 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1722 p = e.origin + t * e.velocity;
1724 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1726 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1732 p = e.origin + t * e.velocity;
1734 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1736 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1742 p = e.origin + t * e.velocity;
1744 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1746 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1752 void func_vectormamamam_controller_think()
1754 self.nextthink = time + 0.1;
1755 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1758 void func_vectormamamam_findtarget()
1760 if(self.target != "")
1761 self.wp00 = find(world, targetname, self.target);
1763 if(self.target2 != "")
1764 self.wp01 = find(world, targetname, self.target2);
1766 if(self.target3 != "")
1767 self.wp02 = find(world, targetname, self.target3);
1769 if(self.target4 != "")
1770 self.wp03 = find(world, targetname, self.target4);
1772 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1773 objerror("No reference entity found, so there is nothing to move. Aborting.");
1775 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1777 local entity controller;
1778 controller = spawn();
1779 controller.classname = "func_vectormamamam_controller";
1780 controller.owner = self;
1781 controller.nextthink = time + 1;
1782 controller.think = func_vectormamamam_controller_think;
1785 void spawnfunc_func_vectormamamam()
1787 if (self.noise != "")
1789 precache_sound(self.noise);
1790 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1793 if(!self.targetfactor)
1794 self.targetfactor = 1;
1796 if(!self.target2factor)
1797 self.target2factor = 1;
1799 if(!self.target3factor)
1800 self.target3factor = 1;
1802 if(!self.target4factor)
1803 self.target4factor = 1;
1805 if(vlen(self.targetnormal))
1806 self.targetnormal = normalize(self.targetnormal);
1808 if(vlen(self.target2normal))
1809 self.target2normal = normalize(self.target2normal);
1811 if(vlen(self.target3normal))
1812 self.target3normal = normalize(self.target3normal);
1814 if(vlen(self.target4normal))
1815 self.target4normal = normalize(self.target4normal);
1817 self.blocked = generic_plat_blocked;
1818 if(self.dmg & (!self.message))
1819 self.message = " was squished";
1820 if(self.dmg && (!self.message2))
1821 self.message2 = "was squished by";
1822 if(self.dmg && (!self.dmgtime))
1823 self.dmgtime = 0.25;
1824 self.dmgtime2 = time;
1826 if(self.netname == "")
1827 self.netname = "1 0 0 0 1";
1829 if not(InitMovingBrushTrigger())
1832 // wait for targets to spawn
1833 self.nextthink = self.ltime + 999999999;
1834 self.think = SUB_Null;
1836 // Savage: Reduce bandwith, critical on e.g. nexdm02
1837 self.effects |= EF_LOWPRECISION;
1839 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);