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 void func_rotating_setactive(float astate)
333 if (astate == ACTIVE_TOGGLE)
336 self.active = ACTIVE_NOT;
338 self.active = ACTIVE_ACTIVE;
341 self.active = astate;
343 if(astate == ACTIVE_NOT)
344 self.avelocity = '0 0 0';
346 self.avelocity = self.pos1;
349 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
350 Brush model that spins in place on one axis (default Z).
351 speed : speed to rotate (in degrees per second)
352 noise : path/name of looping .wav file to play.
353 dmg : Do this mutch dmg every .dmgtime intervall when blocked
357 void spawnfunc_func_rotating()
359 if (self.noise != "")
361 precache_sound(self.noise);
362 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
365 self.active = ACTIVE_ACTIVE;
366 self.setactive = func_rotating_setactive;
370 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
371 if (self.spawnflags & 4) // X (untested)
372 self.avelocity = '0 0 1' * self.speed;
373 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
374 else if (self.spawnflags & 8) // Y (untested)
375 self.avelocity = '1 0 0' * self.speed;
376 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
378 self.avelocity = '0 1 0' * self.speed;
380 self.pos1 = self.avelocity;
382 if(self.dmg & (!self.message))
383 self.message = " was squished";
384 if(self.dmg && (!self.message2))
385 self.message2 = "was squished by";
388 if(self.dmg && (!self.dmgtime))
391 self.dmgtime2 = time;
393 if not(InitMovingBrushTrigger())
395 // no EF_LOWPRECISION here, as rounding angles is bad
397 self.blocked = generic_plat_blocked;
399 // wait for targets to spawn
400 self.nextthink = self.ltime + 999999999;
401 self.think = SUB_Null;
403 // TODO make a reset function for this one
407 void func_bobbing_controller_think()
410 self.nextthink = time + 0.1;
411 // calculate sinewave using makevectors
412 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
413 v = self.owner.destvec + self.owner.movedir * v_forward_y;
414 // * 10 so it will arrive in 0.1 sec
415 self.owner.velocity = (v - self.owner.origin) * 10;
418 void bobbing_blocked()
420 // no need to duplicate code
421 generic_plat_blocked();
424 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
425 Brush model that moves back and forth on one axis (default Z).
426 speed : how long one cycle takes in seconds (default 4)
427 height : how far the cycle moves (default 32)
428 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
429 noise : path/name of looping .wav file to play.
430 dmg : Do this mutch dmg every .dmgtime intervall when blocked
433 void spawnfunc_func_bobbing()
435 local entity controller;
436 if (self.noise != "")
438 precache_sound(self.noise);
439 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
445 // center of bobbing motion
446 self.destvec = self.origin;
447 // time scale to get degrees
448 self.cnt = 360 / self.speed;
450 // damage when blocked
451 self.blocked = bobbing_blocked;
452 if(self.dmg & (!self.message))
453 self.message = " was squished";
454 if(self.dmg && (!self.message2))
455 self.message2 = "was squished by";
456 if(self.dmg && (!self.dmgtime))
458 self.dmgtime2 = time;
461 if (self.spawnflags & 1) // X
462 self.movedir = '1 0 0' * self.height;
463 else if (self.spawnflags & 2) // Y
464 self.movedir = '0 1 0' * self.height;
466 self.movedir = '0 0 1' * self.height;
468 if not(InitMovingBrushTrigger())
471 // wait for targets to spawn
472 controller = spawn();
473 controller.classname = "func_bobbing_controller";
474 controller.owner = self;
475 controller.nextthink = time + 1;
476 controller.think = func_bobbing_controller_think;
477 self.nextthink = self.ltime + 999999999;
478 self.think = SUB_Null;
480 // Savage: Reduce bandwith, critical on e.g. nexdm02
481 self.effects |= EF_LOWPRECISION;
483 // TODO make a reset function for this one
486 // button and multiple button
489 void() button_return;
493 self.state = STATE_TOP;
494 self.nextthink = self.ltime + self.wait;
495 self.think = button_return;
496 activator = self.enemy;
498 self.frame = 1; // use alternate textures
503 self.state = STATE_BOTTOM;
508 self.state = STATE_DOWN;
509 SUB_CalcMove (self.pos1, self.speed, button_done);
510 self.frame = 0; // use normal textures
512 self.takedamage = DAMAGE_YES; // can be shot again
516 void button_blocked()
518 // do nothing, just don't come all the way back out
524 self.health = self.max_health;
525 self.takedamage = DAMAGE_NO; // will be reset upon return
527 if (self.state == STATE_UP || self.state == STATE_TOP)
530 if (self.noise != "")
531 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
533 self.state = STATE_UP;
534 SUB_CalcMove (self.pos2, self.speed, button_wait);
539 self.health = self.max_health;
540 setorigin(self, self.pos1);
541 self.frame = 0; // use normal textures
542 self.state = STATE_BOTTOM;
544 self.takedamage = DAMAGE_YES; // can be shot again
549 // if (activator.classname != "player")
551 // dprint(activator.classname);
552 // dprint(" triggered a button\n");
554 self.enemy = activator;
560 // if (activator.classname != "player")
562 // dprint(activator.classname);
563 // dprint(" touched a button\n");
567 if not(other.iscreature)
569 if(other.velocity * self.movedir < 0)
573 self.enemy = other.owner;
577 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
579 if(self.spawnflags & DOOR_NOSPLASH)
580 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
582 self.health = self.health - damage;
583 if (self.health <= 0)
585 // if (activator.classname != "player")
587 // dprint(activator.classname);
588 // dprint(" killed a button\n");
590 self.enemy = damage_attacker;
596 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
597 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.
599 "angle" determines the opening direction
600 "target" all entities with a matching targetname will be used
601 "speed" override the default 40 speed
602 "wait" override the default 1 second wait (-1 = never return)
603 "lip" override the default 4 pixel lip remaining at end of move
604 "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
611 void spawnfunc_func_button()
615 if not(InitMovingBrushTrigger())
617 self.effects |= EF_LOWPRECISION;
619 self.blocked = button_blocked;
620 self.use = button_use;
622 // if (self.health == 0) // all buttons are now shootable
626 self.max_health = self.health;
627 self.event_damage = button_damage;
628 self.takedamage = DAMAGE_YES;
631 self.touch = button_touch;
641 precache_sound(self.noise);
643 self.pos1 = self.origin;
644 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
645 self.flags |= FL_NOTARGET;
651 float DOOR_START_OPEN = 1;
652 float DOOR_DONT_LINK = 4;
653 float DOOR_TOGGLE = 32;
657 Doors are similar to buttons, but can spawn a fat trigger field around them
658 to open without a touch, and they link together to form simultanious
661 Door.owner is the master door. If there is only one door, it points to itself.
662 If multiple doors, all will point to a single one.
664 Door.enemy chains from the master door through all doors linked in the chain.
669 =============================================================================
673 =============================================================================
678 void() door_rotating_go_down;
679 void() door_rotating_go_up;
684 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
685 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
688 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
689 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
691 //Dont chamge direction for dead or dying stuff
692 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
695 if (self.state == STATE_DOWN)
696 if (self.classname == "door")
701 door_rotating_go_up ();
704 if (self.classname == "door")
709 door_rotating_go_down ();
713 //gib dying stuff just to make sure
714 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
715 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
719 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
720 // if a door has a negative wait, it would never come back if blocked,
721 // so let it just squash the object to death real fast
722 /* if (self.wait >= 0)
724 if (self.state == STATE_DOWN)
735 if (self.noise1 != "")
736 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
737 self.state = STATE_TOP;
738 if (self.spawnflags & DOOR_TOGGLE)
739 return; // don't come down automatically
740 if (self.classname == "door")
742 self.think = door_go_down;
745 self.think = door_rotating_go_down;
747 self.nextthink = self.ltime + self.wait;
750 void door_hit_bottom()
752 if (self.noise1 != "")
753 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
754 self.state = STATE_BOTTOM;
759 if (self.noise2 != "")
760 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
763 self.takedamage = DAMAGE_YES;
764 self.health = self.max_health;
767 self.state = STATE_DOWN;
768 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
773 if (self.state == STATE_UP)
774 return; // already going up
776 if (self.state == STATE_TOP)
777 { // reset top wait time
778 self.nextthink = self.ltime + self.wait;
782 if (self.noise2 != "")
783 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
784 self.state = STATE_UP;
785 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
788 oldmessage = self.message;
791 self.message = oldmessage;
796 =============================================================================
800 =============================================================================
808 if (self.owner != self)
809 objerror ("door_fire: self.owner != self");
813 if (self.spawnflags & DOOR_TOGGLE)
815 if (self.state == STATE_UP || self.state == STATE_TOP)
820 if (self.classname == "door")
826 door_rotating_go_down ();
829 } while ( (self != starte) && (self != world) );
835 // trigger all paired doors
839 if (self.classname == "door")
844 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
845 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
847 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
848 self.pos2 = '0 0 0' - self.pos2;
850 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
851 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
852 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
854 door_rotating_go_up ();
858 } while ( (self != starte) && (self != world) );
867 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
878 void door_trigger_touch()
880 if (other.health < 1)
881 if not(other.iscreature && other.deadflag == DEAD_NO)
884 if (time < self.attack_finished_single)
886 self.attack_finished_single = time + 1;
895 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
898 if(self.spawnflags & DOOR_NOSPLASH)
899 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
901 self.health = self.health - damage;
902 if (self.health <= 0)
906 self.health = self.max_health;
907 self.takedamage = DAMAGE_NO; // wil be reset upon return
923 if(other.classname != "player")
925 if (self.owner.attack_finished_single > time)
928 self.owner.attack_finished_single = time + 2;
930 if (!(self.owner.dmg) && (self.owner.message != ""))
932 if (other.flags & FL_CLIENT)
933 centerprint (other, self.owner.message);
934 play2(other, "misc/talk.wav");
939 void door_generic_plat_blocked()
942 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
943 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
946 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
947 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
949 //Dont chamge direction for dead or dying stuff
950 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
953 if (self.state == STATE_DOWN)
954 door_rotating_go_up ();
956 door_rotating_go_down ();
959 //gib dying stuff just to make sure
960 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
961 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
965 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
966 // if a door has a negative wait, it would never come back if blocked,
967 // so let it just squash the object to death real fast
968 /* if (self.wait >= 0)
970 if (self.state == STATE_DOWN)
971 door_rotating_go_up ();
973 door_rotating_go_down ();
979 void door_rotating_hit_top()
981 if (self.noise1 != "")
982 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
983 self.state = STATE_TOP;
984 if (self.spawnflags & DOOR_TOGGLE)
985 return; // don't come down automatically
986 self.think = door_rotating_go_down;
987 self.nextthink = self.ltime + self.wait;
990 void door_rotating_hit_bottom()
992 if (self.noise1 != "")
993 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
994 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
996 self.pos2 = '0 0 0' - self.pos2;
999 self.state = STATE_BOTTOM;
1002 void door_rotating_go_down()
1004 if (self.noise2 != "")
1005 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1006 if (self.max_health)
1008 self.takedamage = DAMAGE_YES;
1009 self.health = self.max_health;
1012 self.state = STATE_DOWN;
1013 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1016 void door_rotating_go_up()
1018 if (self.state == STATE_UP)
1019 return; // already going up
1021 if (self.state == STATE_TOP)
1022 { // reset top wait time
1023 self.nextthink = self.ltime + self.wait;
1026 if (self.noise2 != "")
1027 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1028 self.state = STATE_UP;
1029 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1032 oldmessage = self.message;
1035 self.message = oldmessage;
1042 =============================================================================
1046 =============================================================================
1050 entity spawn_field(vector fmins, vector fmaxs)
1052 local entity trigger;
1053 local vector t1, t2;
1056 trigger.classname = "doortriggerfield";
1057 trigger.movetype = MOVETYPE_NONE;
1058 trigger.solid = SOLID_TRIGGER;
1059 trigger.owner = self;
1060 trigger.touch = door_trigger_touch;
1064 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1069 float EntitiesTouching(entity e1, entity e2)
1071 if (e1.absmin_x > e2.absmax_x)
1073 if (e1.absmin_y > e2.absmax_y)
1075 if (e1.absmin_z > e2.absmax_z)
1077 if (e1.absmax_x < e2.absmin_x)
1079 if (e1.absmax_y < e2.absmin_y)
1081 if (e1.absmax_z < e2.absmin_z)
1096 local entity t, starte;
1097 local vector cmins, cmaxs;
1100 return; // already linked by another door
1101 if (self.spawnflags & 4)
1103 self.owner = self.enemy = self;
1111 self.trigger_field = spawn_field(self.absmin, self.absmax);
1113 return; // don't want to link this door
1116 cmins = self.absmin;
1117 cmaxs = self.absmax;
1124 self.owner = starte; // master door
1127 starte.health = self.health;
1129 starte.targetname = self.targetname;
1130 if (self.message != "")
1131 starte.message = self.message;
1133 t = find(t, classname, self.classname);
1136 self.enemy = starte; // make the chain a loop
1138 // shootable, or triggered doors just needed the owner/enemy links,
1139 // they don't spawn a field
1150 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1155 if (EntitiesTouching(self,t))
1158 objerror ("cross connected doors");
1163 if (t.absmin_x < cmins_x)
1164 cmins_x = t.absmin_x;
1165 if (t.absmin_y < cmins_y)
1166 cmins_y = t.absmin_y;
1167 if (t.absmin_z < cmins_z)
1168 cmins_z = t.absmin_z;
1169 if (t.absmax_x > cmaxs_x)
1170 cmaxs_x = t.absmax_x;
1171 if (t.absmax_y > cmaxs_y)
1172 cmaxs_y = t.absmax_y;
1173 if (t.absmax_z > cmaxs_z)
1174 cmaxs_z = t.absmax_z;
1181 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1182 if two doors touch, they are assumed to be connected and operate as a unit.
1184 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1186 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).
1188 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1189 "angle" determines the opening direction
1190 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1191 "health" if set, door must be shot open
1192 "speed" movement speed (100 default)
1193 "wait" wait before returning (3 default, -1 = never return)
1194 "lip" lip remaining at end of move (8 default)
1195 "dmg" damage to inflict when blocked (2 default)
1202 FIXME: only one sound set available at the time being
1206 void door_init_startopen()
1208 setorigin (self, self.pos2);
1209 self.pos2 = self.pos1;
1210 self.pos1 = self.origin;
1215 setorigin(self, self.pos1);
1216 self.velocity = '0 0 0';
1217 self.state = STATE_BOTTOM;
1218 self.think = SUB_Null;
1221 void spawnfunc_func_door()
1223 //if (!self.deathtype) // map makers can override this
1224 // self.deathtype = " got in the way";
1227 self.max_health = self.health;
1228 if not(InitMovingBrushTrigger())
1230 self.effects |= EF_LOWPRECISION;
1231 self.classname = "door";
1233 self.blocked = door_blocked;
1234 self.use = door_use;
1236 if(self.spawnflags & 8)
1239 if(self.dmg && (!self.message))
1240 self.message = "was squished";
1241 if(self.dmg && (!self.message2))
1242 self.message2 = "was squished by";
1244 if (self.sounds > 0)
1246 precache_sound ("plats/medplat1.wav");
1247 precache_sound ("plats/medplat2.wav");
1248 self.noise2 = "plats/medplat1.wav";
1249 self.noise1 = "plats/medplat2.wav";
1259 self.pos1 = self.origin;
1260 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1262 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1263 // but spawn in the open position
1264 if (self.spawnflags & DOOR_START_OPEN)
1265 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1267 self.state = STATE_BOTTOM;
1271 self.takedamage = DAMAGE_YES;
1272 self.event_damage = door_damage;
1278 self.touch = door_touch;
1280 // LinkDoors can't be done until all of the doors have been spawned, so
1281 // the sizes can be detected properly.
1282 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1284 self.reset = door_reset;
1287 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1288 if two doors touch, they are assumed to be connected and operate as a unit.
1290 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1292 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1293 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1294 must have set trigger_reverse to 1.
1295 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1297 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).
1299 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1300 "angle" determines the destination angle for opening. negative values reverse the direction.
1301 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1302 "health" if set, door must be shot open
1303 "speed" movement speed (100 default)
1304 "wait" wait before returning (3 default, -1 = never return)
1305 "dmg" damage to inflict when blocked (2 default)
1312 FIXME: only one sound set available at the time being
1315 void door_rotating_reset()
1317 self.angles = self.pos1;
1318 self.avelocity = '0 0 0';
1319 self.state = STATE_BOTTOM;
1320 self.think = SUB_Null;
1323 void door_rotating_init_startopen()
1325 self.angles = self.movedir;
1326 self.pos2 = '0 0 0';
1327 self.pos1 = self.movedir;
1331 void spawnfunc_func_door_rotating()
1334 //if (!self.deathtype) // map makers can override this
1335 // self.deathtype = " got in the way";
1337 // I abuse "movedir" for denoting the axis for now
1338 if (self.spawnflags & 64) // X (untested)
1339 self.movedir = '0 0 1';
1340 else if (self.spawnflags & 128) // Y (untested)
1341 self.movedir = '1 0 0';
1343 self.movedir = '0 1 0';
1345 if (self.angles_y==0) self.angles_y = 90;
1347 self.movedir = self.movedir * self.angles_y;
1348 self.angles = '0 0 0';
1350 self.max_health = self.health;
1351 if not(InitMovingBrushTrigger())
1353 //self.effects |= EF_LOWPRECISION;
1354 self.classname = "door_rotating";
1356 self.blocked = door_blocked;
1357 self.use = door_use;
1359 if(self.spawnflags & 8)
1362 if(self.dmg && (!self.message))
1363 self.message = "was squished";
1364 if(self.dmg && (!self.message2))
1365 self.message2 = "was squished by";
1367 if (self.sounds > 0)
1369 precache_sound ("plats/medplat1.wav");
1370 precache_sound ("plats/medplat2.wav");
1371 self.noise2 = "plats/medplat1.wav";
1372 self.noise1 = "plats/medplat2.wav";
1379 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1381 self.pos1 = '0 0 0';
1382 self.pos2 = self.movedir;
1384 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1385 // but spawn in the open position
1386 if (self.spawnflags & DOOR_START_OPEN)
1387 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1389 self.state = STATE_BOTTOM;
1393 self.takedamage = DAMAGE_YES;
1394 self.event_damage = door_damage;
1400 self.touch = door_touch;
1402 // LinkDoors can't be done until all of the doors have been spawned, so
1403 // the sizes can be detected properly.
1404 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1406 self.reset = door_rotating_reset;
1410 =============================================================================
1414 =============================================================================
1417 void() fd_secret_move1;
1418 void() fd_secret_move2;
1419 void() fd_secret_move3;
1420 void() fd_secret_move4;
1421 void() fd_secret_move5;
1422 void() fd_secret_move6;
1423 void() fd_secret_done;
1425 float SECRET_OPEN_ONCE = 1; // stays open
1426 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1427 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1428 float SECRET_NO_SHOOT = 8; // only opened by trigger
1429 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1432 void fd_secret_use()
1435 string message_save;
1437 self.health = 10000;
1438 self.bot_attack = TRUE;
1440 // exit if still moving around...
1441 if (self.origin != self.oldorigin)
1444 message_save = self.message;
1445 self.message = ""; // no more message
1446 SUB_UseTargets(); // fire all targets / killtargets
1447 self.message = message_save;
1449 self.velocity = '0 0 0';
1451 // Make a sound, wait a little...
1453 if (self.noise1 != "")
1454 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1455 self.nextthink = self.ltime + 0.1;
1457 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1458 makevectors(self.mangle);
1462 if (self.spawnflags & SECRET_1ST_DOWN)
1463 self.t_width = fabs(v_up * self.size);
1465 self.t_width = fabs(v_right * self.size);
1469 self.t_length = fabs(v_forward * self.size);
1471 if (self.spawnflags & SECRET_1ST_DOWN)
1472 self.dest1 = self.origin - v_up * self.t_width;
1474 self.dest1 = self.origin + v_right * (self.t_width * temp);
1476 self.dest2 = self.dest1 + v_forward * self.t_length;
1477 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1478 if (self.noise2 != "")
1479 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1482 // Wait after first movement...
1483 void fd_secret_move1()
1485 self.nextthink = self.ltime + 1.0;
1486 self.think = fd_secret_move2;
1487 if (self.noise3 != "")
1488 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1491 // Start moving sideways w/sound...
1492 void fd_secret_move2()
1494 if (self.noise2 != "")
1495 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1496 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1499 // Wait here until time to go back...
1500 void fd_secret_move3()
1502 if (self.noise3 != "")
1503 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1504 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1506 self.nextthink = self.ltime + self.wait;
1507 self.think = fd_secret_move4;
1512 void fd_secret_move4()
1514 if (self.noise2 != "")
1515 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1516 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1520 void fd_secret_move5()
1522 self.nextthink = self.ltime + 1.0;
1523 self.think = fd_secret_move6;
1524 if (self.noise3 != "")
1525 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1528 void fd_secret_move6()
1530 if (self.noise2 != "")
1531 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1532 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1535 void fd_secret_done()
1537 if (self.spawnflags&SECRET_YES_SHOOT)
1539 self.health = 10000;
1540 self.takedamage = DAMAGE_YES;
1541 //self.th_pain = fd_secret_use;
1543 if (self.noise3 != "")
1544 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1547 void secret_blocked()
1549 if (time < self.attack_finished_single)
1551 self.attack_finished_single = time + 0.5;
1552 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1564 if not(other.iscreature)
1566 if (self.attack_finished_single > time)
1569 self.attack_finished_single = time + 2;
1573 if (other.flags & FL_CLIENT)
1574 centerprint (other, self.message);
1575 play2(other, "misc/talk.wav");
1581 if (self.spawnflags&SECRET_YES_SHOOT)
1583 self.health = 10000;
1584 self.takedamage = DAMAGE_YES;
1586 setorigin(self, self.oldorigin);
1587 self.think = SUB_Null;
1590 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1591 Basic secret door. Slides back, then to the side. Angle determines direction.
1592 wait = # of seconds before coming back
1593 1st_left = 1st move is left of arrow
1594 1st_down = 1st move is down from arrow
1595 always_shoot = even if targeted, keep shootable
1596 t_width = override WIDTH to move back (or height if going down)
1597 t_length = override LENGTH to move sideways
1598 "dmg" damage to inflict when blocked (2 default)
1600 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1607 void spawnfunc_func_door_secret()
1609 /*if (!self.deathtype) // map makers can override this
1610 self.deathtype = " got in the way";*/
1616 self.mangle = self.angles;
1617 self.angles = '0 0 0';
1618 self.classname = "door";
1619 if not(InitMovingBrushTrigger())
1621 self.effects |= EF_LOWPRECISION;
1623 self.touch = secret_touch;
1624 self.blocked = secret_blocked;
1626 self.use = fd_secret_use;
1631 self.spawnflags |= SECRET_YES_SHOOT;
1633 if(self.spawnflags&SECRET_YES_SHOOT)
1635 self.health = 10000;
1636 self.takedamage = DAMAGE_YES;
1637 self.event_damage = fd_secret_use;
1639 self.oldorigin = self.origin;
1641 self.wait = 5; // 5 seconds before closing
1643 self.reset = secret_reset;
1647 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1648 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1649 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
1650 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1651 height: amplitude modifier (default 32)
1652 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1653 noise: path/name of looping .wav file to play.
1654 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1658 void func_fourier_controller_think()
1663 self.nextthink = time + 0.1;
1665 n = floor((tokenize_console(self.owner.netname)) / 5);
1666 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1668 v = self.owner.destvec;
1670 for(i = 0; i < n; ++i)
1672 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1673 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;
1676 // * 10 so it will arrive in 0.1 sec
1677 self.owner.velocity = (v - self.owner.origin) * 10;
1680 void spawnfunc_func_fourier()
1682 local entity controller;
1683 if (self.noise != "")
1685 precache_sound(self.noise);
1686 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1693 self.destvec = self.origin;
1694 self.cnt = 360 / self.speed;
1696 self.blocked = generic_plat_blocked;
1697 if(self.dmg & (!self.message))
1698 self.message = " was squished";
1699 if(self.dmg && (!self.message2))
1700 self.message2 = "was squished by";
1701 if(self.dmg && (!self.dmgtime))
1702 self.dmgtime = 0.25;
1703 self.dmgtime2 = time;
1705 if(self.netname == "")
1706 self.netname = "1 0 0 0 1";
1708 if not(InitMovingBrushTrigger())
1711 // wait for targets to spawn
1712 controller = spawn();
1713 controller.classname = "func_fourier_controller";
1714 controller.owner = self;
1715 controller.nextthink = time + 1;
1716 controller.think = func_fourier_controller_think;
1717 self.nextthink = self.ltime + 999999999;
1718 self.think = SUB_Null;
1720 // Savage: Reduce bandwith, critical on e.g. nexdm02
1721 self.effects |= EF_LOWPRECISION;
1723 // TODO make a reset function for this one
1726 // reusing some fields havocbots declared
1727 .entity wp00, wp01, wp02, wp03;
1729 .float targetfactor, target2factor, target3factor, target4factor;
1730 .vector targetnormal, target2normal, target3normal, target4normal;
1732 vector func_vectormamamam_origin(entity o, float t)
1744 p = e.origin + t * e.velocity;
1746 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1748 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1754 p = e.origin + t * e.velocity;
1756 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1758 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1764 p = e.origin + t * e.velocity;
1766 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1768 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1774 p = e.origin + t * e.velocity;
1776 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1778 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1784 void func_vectormamamam_controller_think()
1786 self.nextthink = time + 0.1;
1787 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1790 void func_vectormamamam_findtarget()
1792 if(self.target != "")
1793 self.wp00 = find(world, targetname, self.target);
1795 if(self.target2 != "")
1796 self.wp01 = find(world, targetname, self.target2);
1798 if(self.target3 != "")
1799 self.wp02 = find(world, targetname, self.target3);
1801 if(self.target4 != "")
1802 self.wp03 = find(world, targetname, self.target4);
1804 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1805 objerror("No reference entity found, so there is nothing to move. Aborting.");
1807 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1809 local entity controller;
1810 controller = spawn();
1811 controller.classname = "func_vectormamamam_controller";
1812 controller.owner = self;
1813 controller.nextthink = time + 1;
1814 controller.think = func_vectormamamam_controller_think;
1817 void spawnfunc_func_vectormamamam()
1819 if (self.noise != "")
1821 precache_sound(self.noise);
1822 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1825 if(!self.targetfactor)
1826 self.targetfactor = 1;
1828 if(!self.target2factor)
1829 self.target2factor = 1;
1831 if(!self.target3factor)
1832 self.target3factor = 1;
1834 if(!self.target4factor)
1835 self.target4factor = 1;
1837 if(vlen(self.targetnormal))
1838 self.targetnormal = normalize(self.targetnormal);
1840 if(vlen(self.target2normal))
1841 self.target2normal = normalize(self.target2normal);
1843 if(vlen(self.target3normal))
1844 self.target3normal = normalize(self.target3normal);
1846 if(vlen(self.target4normal))
1847 self.target4normal = normalize(self.target4normal);
1849 self.blocked = generic_plat_blocked;
1850 if(self.dmg & (!self.message))
1851 self.message = " was squished";
1852 if(self.dmg && (!self.message2))
1853 self.message2 = "was squished by";
1854 if(self.dmg && (!self.dmgtime))
1855 self.dmgtime = 0.25;
1856 self.dmgtime2 = time;
1858 if(self.netname == "")
1859 self.netname = "1 0 0 0 1";
1861 if not(InitMovingBrushTrigger())
1864 // wait for targets to spawn
1865 self.nextthink = self.ltime + 999999999;
1866 self.think = SUB_Null;
1868 // Savage: Reduce bandwith, critical on e.g. nexdm02
1869 self.effects |= EF_LOWPRECISION;
1871 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);