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);
617 self.flags |= FL_NOTARGET;
623 float DOOR_START_OPEN = 1;
624 float DOOR_DONT_LINK = 4;
625 float DOOR_TOGGLE = 32;
629 Doors are similar to buttons, but can spawn a fat trigger field around them
630 to open without a touch, and they link together to form simultanious
633 Door.owner is the master door. If there is only one door, it points to itself.
634 If multiple doors, all will point to a single one.
636 Door.enemy chains from the master door through all doors linked in the chain.
641 =============================================================================
645 =============================================================================
650 void() door_rotating_go_down;
651 void() door_rotating_go_up;
656 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
657 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
660 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
661 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
663 //Dont chamge direction for dead or dying stuff
664 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
667 if (self.state == STATE_DOWN)
668 if (self.classname == "door")
673 door_rotating_go_up ();
676 if (self.classname == "door")
681 door_rotating_go_down ();
685 //gib dying stuff just to make sure
686 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
687 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
691 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
692 // if a door has a negative wait, it would never come back if blocked,
693 // so let it just squash the object to death real fast
694 /* if (self.wait >= 0)
696 if (self.state == STATE_DOWN)
707 if (self.noise1 != "")
708 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
709 self.state = STATE_TOP;
710 if (self.spawnflags & DOOR_TOGGLE)
711 return; // don't come down automatically
712 if (self.classname == "door")
714 self.think = door_go_down;
717 self.think = door_rotating_go_down;
719 self.nextthink = self.ltime + self.wait;
722 void door_hit_bottom()
724 if (self.noise1 != "")
725 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
726 self.state = STATE_BOTTOM;
731 if (self.noise2 != "")
732 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
735 self.takedamage = DAMAGE_YES;
736 self.health = self.max_health;
739 self.state = STATE_DOWN;
740 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
745 if (self.state == STATE_UP)
746 return; // already going up
748 if (self.state == STATE_TOP)
749 { // reset top wait time
750 self.nextthink = self.ltime + self.wait;
754 if (self.noise2 != "")
755 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
756 self.state = STATE_UP;
757 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
760 oldmessage = self.message;
763 self.message = oldmessage;
768 =============================================================================
772 =============================================================================
780 if (self.owner != self)
781 objerror ("door_fire: self.owner != self");
785 if (self.spawnflags & DOOR_TOGGLE)
787 if (self.state == STATE_UP || self.state == STATE_TOP)
792 if (self.classname == "door")
798 door_rotating_go_down ();
801 } while ( (self != starte) && (self != world) );
807 // trigger all paired doors
811 if (self.classname == "door")
816 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
817 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
819 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
820 self.pos2 = '0 0 0' - self.pos2;
822 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
823 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
824 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
826 door_rotating_go_up ();
830 } while ( (self != starte) && (self != world) );
839 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
850 void door_trigger_touch()
852 if (other.health < 1)
853 if not(other.iscreature && other.deadflag == DEAD_NO)
856 if (time < self.attack_finished_single)
858 self.attack_finished_single = time + 1;
867 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
870 self.health = self.health - damage;
871 if (self.health <= 0)
875 self.health = self.max_health;
876 self.takedamage = DAMAGE_NO; // wil be reset upon return
892 if(other.classname != "player")
894 if (self.owner.attack_finished_single > time)
897 self.owner.attack_finished_single = time + 2;
899 if (!(self.owner.dmg) && (self.owner.message != ""))
901 if (other.flags & FL_CLIENT)
902 centerprint (other, self.owner.message);
903 play2(other, "misc/talk.wav");
908 void door_generic_plat_blocked()
911 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
912 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
915 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
916 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
918 //Dont chamge direction for dead or dying stuff
919 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
922 if (self.state == STATE_DOWN)
923 door_rotating_go_up ();
925 door_rotating_go_down ();
928 //gib dying stuff just to make sure
929 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
930 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
934 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
935 // if a door has a negative wait, it would never come back if blocked,
936 // so let it just squash the object to death real fast
937 /* if (self.wait >= 0)
939 if (self.state == STATE_DOWN)
940 door_rotating_go_up ();
942 door_rotating_go_down ();
948 void door_rotating_hit_top()
950 if (self.noise1 != "")
951 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
952 self.state = STATE_TOP;
953 if (self.spawnflags & DOOR_TOGGLE)
954 return; // don't come down automatically
955 self.think = door_rotating_go_down;
956 self.nextthink = self.ltime + self.wait;
959 void door_rotating_hit_bottom()
961 if (self.noise1 != "")
962 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
963 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
965 self.pos2 = '0 0 0' - self.pos2;
968 self.state = STATE_BOTTOM;
971 void door_rotating_go_down()
973 if (self.noise2 != "")
974 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
977 self.takedamage = DAMAGE_YES;
978 self.health = self.max_health;
981 self.state = STATE_DOWN;
982 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
985 void door_rotating_go_up()
987 if (self.state == STATE_UP)
988 return; // already going up
990 if (self.state == STATE_TOP)
991 { // reset top wait time
992 self.nextthink = self.ltime + self.wait;
995 if (self.noise2 != "")
996 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
997 self.state = STATE_UP;
998 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1001 oldmessage = self.message;
1004 self.message = oldmessage;
1011 =============================================================================
1015 =============================================================================
1019 entity spawn_field(vector fmins, vector fmaxs)
1021 local entity trigger;
1022 local vector t1, t2;
1025 trigger.classname = "doortriggerfield";
1026 trigger.movetype = MOVETYPE_NONE;
1027 trigger.solid = SOLID_TRIGGER;
1028 trigger.owner = self;
1029 trigger.touch = door_trigger_touch;
1033 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1038 float EntitiesTouching(entity e1, entity e2)
1040 if (e1.absmin_x > e2.absmax_x)
1042 if (e1.absmin_y > e2.absmax_y)
1044 if (e1.absmin_z > e2.absmax_z)
1046 if (e1.absmax_x < e2.absmin_x)
1048 if (e1.absmax_y < e2.absmin_y)
1050 if (e1.absmax_z < e2.absmin_z)
1065 local entity t, starte;
1066 local vector cmins, cmaxs;
1069 return; // already linked by another door
1070 if (self.spawnflags & 4)
1072 self.owner = self.enemy = self;
1080 self.trigger_field = spawn_field(self.absmin, self.absmax);
1082 return; // don't want to link this door
1085 cmins = self.absmin;
1086 cmaxs = self.absmax;
1093 self.owner = starte; // master door
1096 starte.health = self.health;
1098 starte.targetname = self.targetname;
1099 if (self.message != "")
1100 starte.message = self.message;
1102 t = find(t, classname, self.classname);
1105 self.enemy = starte; // make the chain a loop
1107 // shootable, or triggered doors just needed the owner/enemy links,
1108 // they don't spawn a field
1119 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1124 if (EntitiesTouching(self,t))
1127 objerror ("cross connected doors");
1132 if (t.absmin_x < cmins_x)
1133 cmins_x = t.absmin_x;
1134 if (t.absmin_y < cmins_y)
1135 cmins_y = t.absmin_y;
1136 if (t.absmin_z < cmins_z)
1137 cmins_z = t.absmin_z;
1138 if (t.absmax_x > cmaxs_x)
1139 cmaxs_x = t.absmax_x;
1140 if (t.absmax_y > cmaxs_y)
1141 cmaxs_y = t.absmax_y;
1142 if (t.absmax_z > cmaxs_z)
1143 cmaxs_z = t.absmax_z;
1150 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1151 if two doors touch, they are assumed to be connected and operate as a unit.
1153 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1155 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).
1157 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1158 "angle" determines the opening direction
1159 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1160 "health" if set, door must be shot open
1161 "speed" movement speed (100 default)
1162 "wait" wait before returning (3 default, -1 = never return)
1163 "lip" lip remaining at end of move (8 default)
1164 "dmg" damage to inflict when blocked (2 default)
1171 FIXME: only one sound set available at the time being
1175 void door_init_startopen()
1177 setorigin (self, self.pos2);
1178 self.pos2 = self.pos1;
1179 self.pos1 = self.origin;
1184 setorigin(self, self.pos1);
1185 self.velocity = '0 0 0';
1186 self.state = STATE_BOTTOM;
1187 self.think = SUB_Null;
1190 void spawnfunc_func_door()
1192 //if (!self.deathtype) // map makers can override this
1193 // self.deathtype = " got in the way";
1196 self.max_health = self.health;
1197 if not(InitMovingBrushTrigger())
1199 self.effects |= EF_LOWPRECISION;
1200 self.classname = "door";
1202 self.blocked = door_blocked;
1203 self.use = door_use;
1205 if(self.spawnflags & 8)
1208 if(self.dmg && (!self.message))
1209 self.message = "was squished";
1210 if(self.dmg && (!self.message2))
1211 self.message2 = "was squished by";
1213 if (self.sounds > 0)
1215 precache_sound ("plats/medplat1.wav");
1216 precache_sound ("plats/medplat2.wav");
1217 self.noise2 = "plats/medplat1.wav";
1218 self.noise1 = "plats/medplat2.wav";
1228 self.pos1 = self.origin;
1229 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1231 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1232 // but spawn in the open position
1233 if (self.spawnflags & DOOR_START_OPEN)
1234 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1236 self.state = STATE_BOTTOM;
1240 self.takedamage = DAMAGE_YES;
1241 self.event_damage = door_damage;
1247 self.touch = door_touch;
1249 // LinkDoors can't be done until all of the doors have been spawned, so
1250 // the sizes can be detected properly.
1251 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1253 self.reset = door_reset;
1256 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1257 if two doors touch, they are assumed to be connected and operate as a unit.
1259 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1261 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1262 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1263 must have set trigger_reverse to 1.
1264 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1266 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).
1268 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1269 "angle" determines the destination angle for opening. negative values reverse the direction.
1270 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1271 "health" if set, door must be shot open
1272 "speed" movement speed (100 default)
1273 "wait" wait before returning (3 default, -1 = never return)
1274 "dmg" damage to inflict when blocked (2 default)
1281 FIXME: only one sound set available at the time being
1284 void door_rotating_reset()
1286 self.angles = self.pos1;
1287 self.avelocity = '0 0 0';
1288 self.state = STATE_BOTTOM;
1289 self.think = SUB_Null;
1292 void door_rotating_init_startopen()
1294 self.angles = self.movedir;
1295 self.pos2 = '0 0 0';
1296 self.pos1 = self.movedir;
1300 void spawnfunc_func_door_rotating()
1303 //if (!self.deathtype) // map makers can override this
1304 // self.deathtype = " got in the way";
1306 // I abuse "movedir" for denoting the axis for now
1307 if (self.spawnflags & 64) // X (untested)
1308 self.movedir = '0 0 1';
1309 else if (self.spawnflags & 128) // Y (untested)
1310 self.movedir = '1 0 0';
1312 self.movedir = '0 1 0';
1314 if (self.angles_y==0) self.angles_y = 90;
1316 self.movedir = self.movedir * self.angles_y;
1317 self.angles = '0 0 0';
1319 self.max_health = self.health;
1320 if not(InitMovingBrushTrigger())
1322 //self.effects |= EF_LOWPRECISION;
1323 self.classname = "door_rotating";
1325 self.blocked = door_blocked;
1326 self.use = door_use;
1328 if(self.spawnflags & 8)
1331 if(self.dmg && (!self.message))
1332 self.message = "was squished";
1333 if(self.dmg && (!self.message2))
1334 self.message2 = "was squished by";
1336 if (self.sounds > 0)
1338 precache_sound ("plats/medplat1.wav");
1339 precache_sound ("plats/medplat2.wav");
1340 self.noise2 = "plats/medplat1.wav";
1341 self.noise1 = "plats/medplat2.wav";
1348 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1350 self.pos1 = '0 0 0';
1351 self.pos2 = self.movedir;
1353 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1354 // but spawn in the open position
1355 if (self.spawnflags & DOOR_START_OPEN)
1356 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1358 self.state = STATE_BOTTOM;
1362 self.takedamage = DAMAGE_YES;
1363 self.event_damage = door_damage;
1369 self.touch = door_touch;
1371 // LinkDoors can't be done until all of the doors have been spawned, so
1372 // the sizes can be detected properly.
1373 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1375 self.reset = door_rotating_reset;
1379 =============================================================================
1383 =============================================================================
1386 void() fd_secret_move1;
1387 void() fd_secret_move2;
1388 void() fd_secret_move3;
1389 void() fd_secret_move4;
1390 void() fd_secret_move5;
1391 void() fd_secret_move6;
1392 void() fd_secret_done;
1394 float SECRET_OPEN_ONCE = 1; // stays open
1395 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1396 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1397 float SECRET_NO_SHOOT = 8; // only opened by trigger
1398 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1401 void fd_secret_use()
1404 string message_save;
1406 self.health = 10000;
1407 self.bot_attack = TRUE;
1409 // exit if still moving around...
1410 if (self.origin != self.oldorigin)
1413 message_save = self.message;
1414 self.message = ""; // no more message
1415 SUB_UseTargets(); // fire all targets / killtargets
1416 self.message = message_save;
1418 self.velocity = '0 0 0';
1420 // Make a sound, wait a little...
1422 if (self.noise1 != "")
1423 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1424 self.nextthink = self.ltime + 0.1;
1426 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1427 makevectors(self.mangle);
1431 if (self.spawnflags & SECRET_1ST_DOWN)
1432 self.t_width = fabs(v_up * self.size);
1434 self.t_width = fabs(v_right * self.size);
1438 self.t_length = fabs(v_forward * self.size);
1440 if (self.spawnflags & SECRET_1ST_DOWN)
1441 self.dest1 = self.origin - v_up * self.t_width;
1443 self.dest1 = self.origin + v_right * (self.t_width * temp);
1445 self.dest2 = self.dest1 + v_forward * self.t_length;
1446 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1447 if (self.noise2 != "")
1448 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1451 // Wait after first movement...
1452 void fd_secret_move1()
1454 self.nextthink = self.ltime + 1.0;
1455 self.think = fd_secret_move2;
1456 if (self.noise3 != "")
1457 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1460 // Start moving sideways w/sound...
1461 void fd_secret_move2()
1463 if (self.noise2 != "")
1464 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1465 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1468 // Wait here until time to go back...
1469 void fd_secret_move3()
1471 if (self.noise3 != "")
1472 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1473 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1475 self.nextthink = self.ltime + self.wait;
1476 self.think = fd_secret_move4;
1481 void fd_secret_move4()
1483 if (self.noise2 != "")
1484 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1485 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1489 void fd_secret_move5()
1491 self.nextthink = self.ltime + 1.0;
1492 self.think = fd_secret_move6;
1493 if (self.noise3 != "")
1494 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1497 void fd_secret_move6()
1499 if (self.noise2 != "")
1500 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1501 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1504 void fd_secret_done()
1506 if (self.spawnflags&SECRET_YES_SHOOT)
1508 self.health = 10000;
1509 self.takedamage = DAMAGE_YES;
1510 //self.th_pain = fd_secret_use;
1512 if (self.noise3 != "")
1513 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1516 void secret_blocked()
1518 if (time < self.attack_finished_single)
1520 self.attack_finished_single = time + 0.5;
1521 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1533 if not(other.iscreature)
1535 if (self.attack_finished_single > time)
1538 self.attack_finished_single = time + 2;
1542 if (other.flags & FL_CLIENT)
1543 centerprint (other, self.message);
1544 play2(other, "misc/talk.wav");
1550 if (self.spawnflags&SECRET_YES_SHOOT)
1552 self.health = 10000;
1553 self.takedamage = DAMAGE_YES;
1555 setorigin(self, self.oldorigin);
1556 self.think = SUB_Null;
1559 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1560 Basic secret door. Slides back, then to the side. Angle determines direction.
1561 wait = # of seconds before coming back
1562 1st_left = 1st move is left of arrow
1563 1st_down = 1st move is down from arrow
1564 always_shoot = even if targeted, keep shootable
1565 t_width = override WIDTH to move back (or height if going down)
1566 t_length = override LENGTH to move sideways
1567 "dmg" damage to inflict when blocked (2 default)
1569 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1576 void spawnfunc_func_door_secret()
1578 /*if (!self.deathtype) // map makers can override this
1579 self.deathtype = " got in the way";*/
1585 self.mangle = self.angles;
1586 self.angles = '0 0 0';
1587 self.classname = "door";
1588 if not(InitMovingBrushTrigger())
1590 self.effects |= EF_LOWPRECISION;
1592 self.touch = secret_touch;
1593 self.blocked = secret_blocked;
1595 self.use = fd_secret_use;
1600 self.spawnflags |= SECRET_YES_SHOOT;
1602 if(self.spawnflags&SECRET_YES_SHOOT)
1604 self.health = 10000;
1605 self.takedamage = DAMAGE_YES;
1606 self.event_damage = fd_secret_use;
1608 self.oldorigin = self.origin;
1610 self.wait = 5; // 5 seconds before closing
1612 self.reset = secret_reset;
1616 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1617 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1618 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
1619 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1620 height: amplitude modifier (default 32)
1621 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1622 noise: path/name of looping .wav file to play.
1623 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1627 void func_fourier_controller_think()
1632 self.nextthink = time + 0.1;
1634 n = floor((tokenize_console(self.owner.netname)) / 5);
1635 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1637 v = self.owner.destvec;
1639 for(i = 0; i < n; ++i)
1641 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1642 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;
1645 // * 10 so it will arrive in 0.1 sec
1646 self.owner.velocity = (v - self.owner.origin) * 10;
1649 void spawnfunc_func_fourier()
1651 local entity controller;
1652 if (self.noise != "")
1654 precache_sound(self.noise);
1655 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1662 self.destvec = self.origin;
1663 self.cnt = 360 / self.speed;
1665 self.blocked = generic_plat_blocked;
1666 if(self.dmg & (!self.message))
1667 self.message = " was squished";
1668 if(self.dmg && (!self.message2))
1669 self.message2 = "was squished by";
1670 if(self.dmg && (!self.dmgtime))
1671 self.dmgtime = 0.25;
1672 self.dmgtime2 = time;
1674 if(self.netname == "")
1675 self.netname = "1 0 0 0 1";
1677 if not(InitMovingBrushTrigger())
1680 // wait for targets to spawn
1681 controller = spawn();
1682 controller.classname = "func_fourier_controller";
1683 controller.owner = self;
1684 controller.nextthink = time + 1;
1685 controller.think = func_fourier_controller_think;
1686 self.nextthink = self.ltime + 999999999;
1687 self.think = SUB_Null;
1689 // Savage: Reduce bandwith, critical on e.g. nexdm02
1690 self.effects |= EF_LOWPRECISION;
1692 // TODO make a reset function for this one
1695 // reusing some fields havocbots declared
1696 .entity wp00, wp01, wp02, wp03;
1698 .float targetfactor, target2factor, target3factor, target4factor;
1699 .vector targetnormal, target2normal, target3normal, target4normal;
1701 vector func_vectormamamam_origin(entity o, float t)
1713 p = e.origin + t * e.velocity;
1715 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1717 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1723 p = e.origin + t * e.velocity;
1725 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1727 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1733 p = e.origin + t * e.velocity;
1735 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1737 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1743 p = e.origin + t * e.velocity;
1745 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1747 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1753 void func_vectormamamam_controller_think()
1755 self.nextthink = time + 0.1;
1756 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1759 void func_vectormamamam_findtarget()
1761 if(self.target != "")
1762 self.wp00 = find(world, targetname, self.target);
1764 if(self.target2 != "")
1765 self.wp01 = find(world, targetname, self.target2);
1767 if(self.target3 != "")
1768 self.wp02 = find(world, targetname, self.target3);
1770 if(self.target4 != "")
1771 self.wp03 = find(world, targetname, self.target4);
1773 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1774 objerror("No reference entity found, so there is nothing to move. Aborting.");
1776 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1778 local entity controller;
1779 controller = spawn();
1780 controller.classname = "func_vectormamamam_controller";
1781 controller.owner = self;
1782 controller.nextthink = time + 1;
1783 controller.think = func_vectormamamam_controller_think;
1786 void spawnfunc_func_vectormamamam()
1788 if (self.noise != "")
1790 precache_sound(self.noise);
1791 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1794 if(!self.targetfactor)
1795 self.targetfactor = 1;
1797 if(!self.target2factor)
1798 self.target2factor = 1;
1800 if(!self.target3factor)
1801 self.target3factor = 1;
1803 if(!self.target4factor)
1804 self.target4factor = 1;
1806 if(vlen(self.targetnormal))
1807 self.targetnormal = normalize(self.targetnormal);
1809 if(vlen(self.target2normal))
1810 self.target2normal = normalize(self.target2normal);
1812 if(vlen(self.target3normal))
1813 self.target3normal = normalize(self.target3normal);
1815 if(vlen(self.target4normal))
1816 self.target4normal = normalize(self.target4normal);
1818 self.blocked = generic_plat_blocked;
1819 if(self.dmg & (!self.message))
1820 self.message = " was squished";
1821 if(self.dmg && (!self.message2))
1822 self.message2 = "was squished by";
1823 if(self.dmg && (!self.dmgtime))
1824 self.dmgtime = 0.25;
1825 self.dmgtime2 = time;
1827 if(self.netname == "")
1828 self.netname = "1 0 0 0 1";
1830 if not(InitMovingBrushTrigger())
1833 // wait for targets to spawn
1834 self.nextthink = self.ltime + 999999999;
1835 self.think = SUB_Null;
1837 // Savage: Reduce bandwith, critical on e.g. nexdm02
1838 self.effects |= EF_LOWPRECISION;
1840 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);