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 if(self.spawnflags & DOOR_NOSPLASH)
555 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
557 self.health = self.health - damage;
558 if (self.health <= 0)
560 // if (activator.classname != "player")
562 // dprint(activator.classname);
563 // dprint(" killed a button\n");
565 self.enemy = damage_attacker;
571 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
572 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.
574 "angle" determines the opening direction
575 "target" all entities with a matching targetname will be used
576 "speed" override the default 40 speed
577 "wait" override the default 1 second wait (-1 = never return)
578 "lip" override the default 4 pixel lip remaining at end of move
579 "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
586 void spawnfunc_func_button()
590 if not(InitMovingBrushTrigger())
592 self.effects |= EF_LOWPRECISION;
594 self.blocked = button_blocked;
595 self.use = button_use;
597 // if (self.health == 0) // all buttons are now shootable
601 self.max_health = self.health;
602 self.event_damage = button_damage;
603 self.takedamage = DAMAGE_YES;
606 self.touch = button_touch;
616 precache_sound(self.noise);
618 self.pos1 = self.origin;
619 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
620 self.flags |= FL_NOTARGET;
626 float DOOR_START_OPEN = 1;
627 float DOOR_DONT_LINK = 4;
628 float DOOR_TOGGLE = 32;
632 Doors are similar to buttons, but can spawn a fat trigger field around them
633 to open without a touch, and they link together to form simultanious
636 Door.owner is the master door. If there is only one door, it points to itself.
637 If multiple doors, all will point to a single one.
639 Door.enemy chains from the master door through all doors linked in the chain.
644 =============================================================================
648 =============================================================================
653 void() door_rotating_go_down;
654 void() door_rotating_go_up;
659 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
660 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
663 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
664 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
666 //Dont chamge direction for dead or dying stuff
667 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
670 if (self.state == STATE_DOWN)
671 if (self.classname == "door")
676 door_rotating_go_up ();
679 if (self.classname == "door")
684 door_rotating_go_down ();
688 //gib dying stuff just to make sure
689 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
690 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
694 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
695 // if a door has a negative wait, it would never come back if blocked,
696 // so let it just squash the object to death real fast
697 /* if (self.wait >= 0)
699 if (self.state == STATE_DOWN)
710 if (self.noise1 != "")
711 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
712 self.state = STATE_TOP;
713 if (self.spawnflags & DOOR_TOGGLE)
714 return; // don't come down automatically
715 if (self.classname == "door")
717 self.think = door_go_down;
720 self.think = door_rotating_go_down;
722 self.nextthink = self.ltime + self.wait;
725 void door_hit_bottom()
727 if (self.noise1 != "")
728 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
729 self.state = STATE_BOTTOM;
734 if (self.noise2 != "")
735 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
738 self.takedamage = DAMAGE_YES;
739 self.health = self.max_health;
742 self.state = STATE_DOWN;
743 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
748 if (self.state == STATE_UP)
749 return; // already going up
751 if (self.state == STATE_TOP)
752 { // reset top wait time
753 self.nextthink = self.ltime + self.wait;
757 if (self.noise2 != "")
758 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
759 self.state = STATE_UP;
760 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
763 oldmessage = self.message;
766 self.message = oldmessage;
771 =============================================================================
775 =============================================================================
783 if (self.owner != self)
784 objerror ("door_fire: self.owner != self");
788 if (self.spawnflags & DOOR_TOGGLE)
790 if (self.state == STATE_UP || self.state == STATE_TOP)
795 if (self.classname == "door")
801 door_rotating_go_down ();
804 } while ( (self != starte) && (self != world) );
810 // trigger all paired doors
814 if (self.classname == "door")
819 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
820 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
822 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
823 self.pos2 = '0 0 0' - self.pos2;
825 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
826 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
827 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
829 door_rotating_go_up ();
833 } while ( (self != starte) && (self != world) );
842 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
853 void door_trigger_touch()
855 if (other.health < 1)
856 if not(other.iscreature && other.deadflag == DEAD_NO)
859 if (time < self.attack_finished_single)
861 self.attack_finished_single = time + 1;
870 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
873 if(self.spawnflags & DOOR_NOSPLASH)
874 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
876 self.health = self.health - damage;
877 if (self.health <= 0)
881 self.health = self.max_health;
882 self.takedamage = DAMAGE_NO; // wil be reset upon return
898 if(other.classname != "player")
900 if (self.owner.attack_finished_single > time)
903 self.owner.attack_finished_single = time + 2;
905 if (!(self.owner.dmg) && (self.owner.message != ""))
907 if (other.flags & FL_CLIENT)
908 centerprint (other, self.owner.message);
909 play2(other, "misc/talk.wav");
914 void door_generic_plat_blocked()
917 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
918 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
921 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
922 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
924 //Dont chamge direction for dead or dying stuff
925 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
928 if (self.state == STATE_DOWN)
929 door_rotating_go_up ();
931 door_rotating_go_down ();
934 //gib dying stuff just to make sure
935 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
936 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
940 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
941 // if a door has a negative wait, it would never come back if blocked,
942 // so let it just squash the object to death real fast
943 /* if (self.wait >= 0)
945 if (self.state == STATE_DOWN)
946 door_rotating_go_up ();
948 door_rotating_go_down ();
954 void door_rotating_hit_top()
956 if (self.noise1 != "")
957 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
958 self.state = STATE_TOP;
959 if (self.spawnflags & DOOR_TOGGLE)
960 return; // don't come down automatically
961 self.think = door_rotating_go_down;
962 self.nextthink = self.ltime + self.wait;
965 void door_rotating_hit_bottom()
967 if (self.noise1 != "")
968 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
969 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
971 self.pos2 = '0 0 0' - self.pos2;
974 self.state = STATE_BOTTOM;
977 void door_rotating_go_down()
979 if (self.noise2 != "")
980 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
983 self.takedamage = DAMAGE_YES;
984 self.health = self.max_health;
987 self.state = STATE_DOWN;
988 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
991 void door_rotating_go_up()
993 if (self.state == STATE_UP)
994 return; // already going up
996 if (self.state == STATE_TOP)
997 { // reset top wait time
998 self.nextthink = self.ltime + self.wait;
1001 if (self.noise2 != "")
1002 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1003 self.state = STATE_UP;
1004 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1007 oldmessage = self.message;
1010 self.message = oldmessage;
1017 =============================================================================
1021 =============================================================================
1025 entity spawn_field(vector fmins, vector fmaxs)
1027 local entity trigger;
1028 local vector t1, t2;
1031 trigger.classname = "doortriggerfield";
1032 trigger.movetype = MOVETYPE_NONE;
1033 trigger.solid = SOLID_TRIGGER;
1034 trigger.owner = self;
1035 trigger.touch = door_trigger_touch;
1039 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1044 float EntitiesTouching(entity e1, entity e2)
1046 if (e1.absmin_x > e2.absmax_x)
1048 if (e1.absmin_y > e2.absmax_y)
1050 if (e1.absmin_z > e2.absmax_z)
1052 if (e1.absmax_x < e2.absmin_x)
1054 if (e1.absmax_y < e2.absmin_y)
1056 if (e1.absmax_z < e2.absmin_z)
1071 local entity t, starte;
1072 local vector cmins, cmaxs;
1075 return; // already linked by another door
1076 if (self.spawnflags & 4)
1078 self.owner = self.enemy = self;
1086 self.trigger_field = spawn_field(self.absmin, self.absmax);
1088 return; // don't want to link this door
1091 cmins = self.absmin;
1092 cmaxs = self.absmax;
1099 self.owner = starte; // master door
1102 starte.health = self.health;
1104 starte.targetname = self.targetname;
1105 if (self.message != "")
1106 starte.message = self.message;
1108 t = find(t, classname, self.classname);
1111 self.enemy = starte; // make the chain a loop
1113 // shootable, or triggered doors just needed the owner/enemy links,
1114 // they don't spawn a field
1125 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1130 if (EntitiesTouching(self,t))
1133 objerror ("cross connected doors");
1138 if (t.absmin_x < cmins_x)
1139 cmins_x = t.absmin_x;
1140 if (t.absmin_y < cmins_y)
1141 cmins_y = t.absmin_y;
1142 if (t.absmin_z < cmins_z)
1143 cmins_z = t.absmin_z;
1144 if (t.absmax_x > cmaxs_x)
1145 cmaxs_x = t.absmax_x;
1146 if (t.absmax_y > cmaxs_y)
1147 cmaxs_y = t.absmax_y;
1148 if (t.absmax_z > cmaxs_z)
1149 cmaxs_z = t.absmax_z;
1156 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1157 if two doors touch, they are assumed to be connected and operate as a unit.
1159 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1161 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).
1163 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1164 "angle" determines the opening direction
1165 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1166 "health" if set, door must be shot open
1167 "speed" movement speed (100 default)
1168 "wait" wait before returning (3 default, -1 = never return)
1169 "lip" lip remaining at end of move (8 default)
1170 "dmg" damage to inflict when blocked (2 default)
1177 FIXME: only one sound set available at the time being
1181 void door_init_startopen()
1183 setorigin (self, self.pos2);
1184 self.pos2 = self.pos1;
1185 self.pos1 = self.origin;
1190 setorigin(self, self.pos1);
1191 self.velocity = '0 0 0';
1192 self.state = STATE_BOTTOM;
1193 self.think = SUB_Null;
1196 void spawnfunc_func_door()
1198 //if (!self.deathtype) // map makers can override this
1199 // self.deathtype = " got in the way";
1202 self.max_health = self.health;
1203 if not(InitMovingBrushTrigger())
1205 self.effects |= EF_LOWPRECISION;
1206 self.classname = "door";
1208 self.blocked = door_blocked;
1209 self.use = door_use;
1211 if(self.spawnflags & 8)
1214 if(self.dmg && (!self.message))
1215 self.message = "was squished";
1216 if(self.dmg && (!self.message2))
1217 self.message2 = "was squished by";
1219 if (self.sounds > 0)
1221 precache_sound ("plats/medplat1.wav");
1222 precache_sound ("plats/medplat2.wav");
1223 self.noise2 = "plats/medplat1.wav";
1224 self.noise1 = "plats/medplat2.wav";
1234 self.pos1 = self.origin;
1235 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1237 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1238 // but spawn in the open position
1239 if (self.spawnflags & DOOR_START_OPEN)
1240 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1242 self.state = STATE_BOTTOM;
1246 self.takedamage = DAMAGE_YES;
1247 self.event_damage = door_damage;
1253 self.touch = door_touch;
1255 // LinkDoors can't be done until all of the doors have been spawned, so
1256 // the sizes can be detected properly.
1257 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1259 self.reset = door_reset;
1262 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1263 if two doors touch, they are assumed to be connected and operate as a unit.
1265 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1267 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1268 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1269 must have set trigger_reverse to 1.
1270 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1272 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).
1274 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1275 "angle" determines the destination angle for opening. negative values reverse the direction.
1276 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1277 "health" if set, door must be shot open
1278 "speed" movement speed (100 default)
1279 "wait" wait before returning (3 default, -1 = never return)
1280 "dmg" damage to inflict when blocked (2 default)
1287 FIXME: only one sound set available at the time being
1290 void door_rotating_reset()
1292 self.angles = self.pos1;
1293 self.avelocity = '0 0 0';
1294 self.state = STATE_BOTTOM;
1295 self.think = SUB_Null;
1298 void door_rotating_init_startopen()
1300 self.angles = self.movedir;
1301 self.pos2 = '0 0 0';
1302 self.pos1 = self.movedir;
1306 void spawnfunc_func_door_rotating()
1309 //if (!self.deathtype) // map makers can override this
1310 // self.deathtype = " got in the way";
1312 // I abuse "movedir" for denoting the axis for now
1313 if (self.spawnflags & 64) // X (untested)
1314 self.movedir = '0 0 1';
1315 else if (self.spawnflags & 128) // Y (untested)
1316 self.movedir = '1 0 0';
1318 self.movedir = '0 1 0';
1320 if (self.angles_y==0) self.angles_y = 90;
1322 self.movedir = self.movedir * self.angles_y;
1323 self.angles = '0 0 0';
1325 self.max_health = self.health;
1326 if not(InitMovingBrushTrigger())
1328 //self.effects |= EF_LOWPRECISION;
1329 self.classname = "door_rotating";
1331 self.blocked = door_blocked;
1332 self.use = door_use;
1334 if(self.spawnflags & 8)
1337 if(self.dmg && (!self.message))
1338 self.message = "was squished";
1339 if(self.dmg && (!self.message2))
1340 self.message2 = "was squished by";
1342 if (self.sounds > 0)
1344 precache_sound ("plats/medplat1.wav");
1345 precache_sound ("plats/medplat2.wav");
1346 self.noise2 = "plats/medplat1.wav";
1347 self.noise1 = "plats/medplat2.wav";
1354 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1356 self.pos1 = '0 0 0';
1357 self.pos2 = self.movedir;
1359 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1360 // but spawn in the open position
1361 if (self.spawnflags & DOOR_START_OPEN)
1362 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1364 self.state = STATE_BOTTOM;
1368 self.takedamage = DAMAGE_YES;
1369 self.event_damage = door_damage;
1375 self.touch = door_touch;
1377 // LinkDoors can't be done until all of the doors have been spawned, so
1378 // the sizes can be detected properly.
1379 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1381 self.reset = door_rotating_reset;
1385 =============================================================================
1389 =============================================================================
1392 void() fd_secret_move1;
1393 void() fd_secret_move2;
1394 void() fd_secret_move3;
1395 void() fd_secret_move4;
1396 void() fd_secret_move5;
1397 void() fd_secret_move6;
1398 void() fd_secret_done;
1400 float SECRET_OPEN_ONCE = 1; // stays open
1401 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1402 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1403 float SECRET_NO_SHOOT = 8; // only opened by trigger
1404 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1407 void fd_secret_use()
1410 string message_save;
1412 self.health = 10000;
1413 self.bot_attack = TRUE;
1415 // exit if still moving around...
1416 if (self.origin != self.oldorigin)
1419 message_save = self.message;
1420 self.message = ""; // no more message
1421 SUB_UseTargets(); // fire all targets / killtargets
1422 self.message = message_save;
1424 self.velocity = '0 0 0';
1426 // Make a sound, wait a little...
1428 if (self.noise1 != "")
1429 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1430 self.nextthink = self.ltime + 0.1;
1432 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1433 makevectors(self.mangle);
1437 if (self.spawnflags & SECRET_1ST_DOWN)
1438 self.t_width = fabs(v_up * self.size);
1440 self.t_width = fabs(v_right * self.size);
1444 self.t_length = fabs(v_forward * self.size);
1446 if (self.spawnflags & SECRET_1ST_DOWN)
1447 self.dest1 = self.origin - v_up * self.t_width;
1449 self.dest1 = self.origin + v_right * (self.t_width * temp);
1451 self.dest2 = self.dest1 + v_forward * self.t_length;
1452 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1453 if (self.noise2 != "")
1454 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1457 // Wait after first movement...
1458 void fd_secret_move1()
1460 self.nextthink = self.ltime + 1.0;
1461 self.think = fd_secret_move2;
1462 if (self.noise3 != "")
1463 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1466 // Start moving sideways w/sound...
1467 void fd_secret_move2()
1469 if (self.noise2 != "")
1470 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1471 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1474 // Wait here until time to go back...
1475 void fd_secret_move3()
1477 if (self.noise3 != "")
1478 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1479 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1481 self.nextthink = self.ltime + self.wait;
1482 self.think = fd_secret_move4;
1487 void fd_secret_move4()
1489 if (self.noise2 != "")
1490 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1491 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1495 void fd_secret_move5()
1497 self.nextthink = self.ltime + 1.0;
1498 self.think = fd_secret_move6;
1499 if (self.noise3 != "")
1500 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1503 void fd_secret_move6()
1505 if (self.noise2 != "")
1506 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1507 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1510 void fd_secret_done()
1512 if (self.spawnflags&SECRET_YES_SHOOT)
1514 self.health = 10000;
1515 self.takedamage = DAMAGE_YES;
1516 //self.th_pain = fd_secret_use;
1518 if (self.noise3 != "")
1519 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1522 void secret_blocked()
1524 if (time < self.attack_finished_single)
1526 self.attack_finished_single = time + 0.5;
1527 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1539 if not(other.iscreature)
1541 if (self.attack_finished_single > time)
1544 self.attack_finished_single = time + 2;
1548 if (other.flags & FL_CLIENT)
1549 centerprint (other, self.message);
1550 play2(other, "misc/talk.wav");
1556 if (self.spawnflags&SECRET_YES_SHOOT)
1558 self.health = 10000;
1559 self.takedamage = DAMAGE_YES;
1561 setorigin(self, self.oldorigin);
1562 self.think = SUB_Null;
1565 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1566 Basic secret door. Slides back, then to the side. Angle determines direction.
1567 wait = # of seconds before coming back
1568 1st_left = 1st move is left of arrow
1569 1st_down = 1st move is down from arrow
1570 always_shoot = even if targeted, keep shootable
1571 t_width = override WIDTH to move back (or height if going down)
1572 t_length = override LENGTH to move sideways
1573 "dmg" damage to inflict when blocked (2 default)
1575 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1582 void spawnfunc_func_door_secret()
1584 /*if (!self.deathtype) // map makers can override this
1585 self.deathtype = " got in the way";*/
1591 self.mangle = self.angles;
1592 self.angles = '0 0 0';
1593 self.classname = "door";
1594 if not(InitMovingBrushTrigger())
1596 self.effects |= EF_LOWPRECISION;
1598 self.touch = secret_touch;
1599 self.blocked = secret_blocked;
1601 self.use = fd_secret_use;
1606 self.spawnflags |= SECRET_YES_SHOOT;
1608 if(self.spawnflags&SECRET_YES_SHOOT)
1610 self.health = 10000;
1611 self.takedamage = DAMAGE_YES;
1612 self.event_damage = fd_secret_use;
1614 self.oldorigin = self.origin;
1616 self.wait = 5; // 5 seconds before closing
1618 self.reset = secret_reset;
1622 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1623 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1624 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
1625 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1626 height: amplitude modifier (default 32)
1627 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1628 noise: path/name of looping .wav file to play.
1629 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1633 void func_fourier_controller_think()
1638 self.nextthink = time + 0.1;
1640 n = floor((tokenize_console(self.owner.netname)) / 5);
1641 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1643 v = self.owner.destvec;
1645 for(i = 0; i < n; ++i)
1647 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1648 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;
1651 // * 10 so it will arrive in 0.1 sec
1652 self.owner.velocity = (v - self.owner.origin) * 10;
1655 void spawnfunc_func_fourier()
1657 local entity controller;
1658 if (self.noise != "")
1660 precache_sound(self.noise);
1661 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1668 self.destvec = self.origin;
1669 self.cnt = 360 / self.speed;
1671 self.blocked = generic_plat_blocked;
1672 if(self.dmg & (!self.message))
1673 self.message = " was squished";
1674 if(self.dmg && (!self.message2))
1675 self.message2 = "was squished by";
1676 if(self.dmg && (!self.dmgtime))
1677 self.dmgtime = 0.25;
1678 self.dmgtime2 = time;
1680 if(self.netname == "")
1681 self.netname = "1 0 0 0 1";
1683 if not(InitMovingBrushTrigger())
1686 // wait for targets to spawn
1687 controller = spawn();
1688 controller.classname = "func_fourier_controller";
1689 controller.owner = self;
1690 controller.nextthink = time + 1;
1691 controller.think = func_fourier_controller_think;
1692 self.nextthink = self.ltime + 999999999;
1693 self.think = SUB_Null;
1695 // Savage: Reduce bandwith, critical on e.g. nexdm02
1696 self.effects |= EF_LOWPRECISION;
1698 // TODO make a reset function for this one
1701 // reusing some fields havocbots declared
1702 .entity wp00, wp01, wp02, wp03;
1704 .float targetfactor, target2factor, target3factor, target4factor;
1705 .vector targetnormal, target2normal, target3normal, target4normal;
1707 vector func_vectormamamam_origin(entity o, float t)
1719 p = e.origin + t * e.velocity;
1721 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1723 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1729 p = e.origin + t * e.velocity;
1731 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1733 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1739 p = e.origin + t * e.velocity;
1741 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1743 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1749 p = e.origin + t * e.velocity;
1751 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1753 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1759 void func_vectormamamam_controller_think()
1761 self.nextthink = time + 0.1;
1762 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1765 void func_vectormamamam_findtarget()
1767 if(self.target != "")
1768 self.wp00 = find(world, targetname, self.target);
1770 if(self.target2 != "")
1771 self.wp01 = find(world, targetname, self.target2);
1773 if(self.target3 != "")
1774 self.wp02 = find(world, targetname, self.target3);
1776 if(self.target4 != "")
1777 self.wp03 = find(world, targetname, self.target4);
1779 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1780 objerror("No reference entity found, so there is nothing to move. Aborting.");
1782 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1784 local entity controller;
1785 controller = spawn();
1786 controller.classname = "func_vectormamamam_controller";
1787 controller.owner = self;
1788 controller.nextthink = time + 1;
1789 controller.think = func_vectormamamam_controller_think;
1792 void spawnfunc_func_vectormamamam()
1794 if (self.noise != "")
1796 precache_sound(self.noise);
1797 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1800 if(!self.targetfactor)
1801 self.targetfactor = 1;
1803 if(!self.target2factor)
1804 self.target2factor = 1;
1806 if(!self.target3factor)
1807 self.target3factor = 1;
1809 if(!self.target4factor)
1810 self.target4factor = 1;
1812 if(vlen(self.targetnormal))
1813 self.targetnormal = normalize(self.targetnormal);
1815 if(vlen(self.target2normal))
1816 self.target2normal = normalize(self.target2normal);
1818 if(vlen(self.target3normal))
1819 self.target3normal = normalize(self.target3normal);
1821 if(vlen(self.target4normal))
1822 self.target4normal = normalize(self.target4normal);
1824 self.blocked = generic_plat_blocked;
1825 if(self.dmg & (!self.message))
1826 self.message = " was squished";
1827 if(self.dmg && (!self.message2))
1828 self.message2 = "was squished by";
1829 if(self.dmg && (!self.dmgtime))
1830 self.dmgtime = 0.25;
1831 self.dmgtime2 = time;
1833 if(self.netname == "")
1834 self.netname = "1 0 0 0 1";
1836 if not(InitMovingBrushTrigger())
1839 // wait for targets to spawn
1840 self.nextthink = self.ltime + 999999999;
1841 self.think = SUB_Null;
1843 // Savage: Reduce bandwith, critical on e.g. nexdm02
1844 self.effects |= EF_LOWPRECISION;
1846 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);