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)
335 if(self.active == ACTIVE_ACTIVE)
336 self.active = ACTIVE_NOT;
338 self.active = ACTIVE_ACTIVE;
341 self.active = astate;
343 if(self.active == 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;
412 if not (self.owner.active == ACTIVE_ACTIVE)
414 self.owner.velocity = '0 0 0';
418 // calculate sinewave using makevectors
419 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
420 v = self.owner.destvec + self.owner.movedir * v_forward_y;
421 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
422 // * 10 so it will arrive in 0.1 sec
423 self.owner.velocity = (v - self.owner.origin) * 10;
426 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
427 Brush model that moves back and forth on one axis (default Z).
428 speed : how long one cycle takes in seconds (default 4)
429 height : how far the cycle moves (default 32)
430 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
431 noise : path/name of looping .wav file to play.
432 dmg : Do this mutch dmg every .dmgtime intervall when blocked
435 void spawnfunc_func_bobbing()
437 local entity controller;
438 if (self.noise != "")
440 precache_sound(self.noise);
441 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
447 // center of bobbing motion
448 self.destvec = self.origin;
449 // time scale to get degrees
450 self.cnt = 360 / self.speed;
452 self.active = ACTIVE_ACTIVE;
454 // damage when blocked
455 self.blocked = generic_plat_blocked;
456 if(self.dmg & (!self.message))
457 self.message = " was squished";
458 if(self.dmg && (!self.message2))
459 self.message2 = "was squished by";
460 if(self.dmg && (!self.dmgtime))
462 self.dmgtime2 = time;
465 if (self.spawnflags & 1) // X
466 self.movedir = '1 0 0' * self.height;
467 else if (self.spawnflags & 2) // Y
468 self.movedir = '0 1 0' * self.height;
470 self.movedir = '0 0 1' * self.height;
472 if not(InitMovingBrushTrigger())
475 // wait for targets to spawn
476 controller = spawn();
477 controller.classname = "func_bobbing_controller";
478 controller.owner = self;
479 controller.nextthink = time + 1;
480 controller.think = func_bobbing_controller_think;
481 self.nextthink = self.ltime + 999999999;
482 self.think = SUB_Null;
484 // Savage: Reduce bandwith, critical on e.g. nexdm02
485 self.effects |= EF_LOWPRECISION;
487 // TODO make a reset function for this one
490 // button and multiple button
493 void() button_return;
497 self.state = STATE_TOP;
498 self.nextthink = self.ltime + self.wait;
499 self.think = button_return;
500 activator = self.enemy;
502 self.frame = 1; // use alternate textures
507 self.state = STATE_BOTTOM;
512 self.state = STATE_DOWN;
513 SUB_CalcMove (self.pos1, self.speed, button_done);
514 self.frame = 0; // use normal textures
516 self.takedamage = DAMAGE_YES; // can be shot again
520 void button_blocked()
522 // do nothing, just don't come all the way back out
528 self.health = self.max_health;
529 self.takedamage = DAMAGE_NO; // will be reset upon return
531 if (self.state == STATE_UP || self.state == STATE_TOP)
534 if (self.noise != "")
535 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
537 self.state = STATE_UP;
538 SUB_CalcMove (self.pos2, self.speed, button_wait);
543 self.health = self.max_health;
544 setorigin(self, self.pos1);
545 self.frame = 0; // use normal textures
546 self.state = STATE_BOTTOM;
548 self.takedamage = DAMAGE_YES; // can be shot again
553 // if (activator.classname != "player")
555 // dprint(activator.classname);
556 // dprint(" triggered a button\n");
559 if not (self.active == ACTIVE_ACTIVE)
562 self.enemy = activator;
568 // if (activator.classname != "player")
570 // dprint(activator.classname);
571 // dprint(" touched a button\n");
575 if not(other.iscreature)
577 if(other.velocity * self.movedir < 0)
581 self.enemy = other.owner;
585 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
587 if(self.spawnflags & DOOR_NOSPLASH)
588 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
590 self.health = self.health - damage;
591 if (self.health <= 0)
593 // if (activator.classname != "player")
595 // dprint(activator.classname);
596 // dprint(" killed a button\n");
598 self.enemy = damage_attacker;
604 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
605 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.
607 "angle" determines the opening direction
608 "target" all entities with a matching targetname will be used
609 "speed" override the default 40 speed
610 "wait" override the default 1 second wait (-1 = never return)
611 "lip" override the default 4 pixel lip remaining at end of move
612 "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
619 void spawnfunc_func_button()
623 if not(InitMovingBrushTrigger())
625 self.effects |= EF_LOWPRECISION;
627 self.blocked = button_blocked;
628 self.use = button_use;
630 // if (self.health == 0) // all buttons are now shootable
634 self.max_health = self.health;
635 self.event_damage = button_damage;
636 self.takedamage = DAMAGE_YES;
639 self.touch = button_touch;
649 precache_sound(self.noise);
651 self.active = ACTIVE_ACTIVE;
653 self.pos1 = self.origin;
654 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
655 self.flags |= FL_NOTARGET;
661 float DOOR_START_OPEN = 1;
662 float DOOR_DONT_LINK = 4;
663 float DOOR_TOGGLE = 32;
667 Doors are similar to buttons, but can spawn a fat trigger field around them
668 to open without a touch, and they link together to form simultanious
671 Door.owner is the master door. If there is only one door, it points to itself.
672 If multiple doors, all will point to a single one.
674 Door.enemy chains from the master door through all doors linked in the chain.
679 =============================================================================
683 =============================================================================
688 void() door_rotating_go_down;
689 void() door_rotating_go_up;
694 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
695 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
698 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
699 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
701 //Dont chamge direction for dead or dying stuff
702 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
705 if (self.state == STATE_DOWN)
706 if (self.classname == "door")
711 door_rotating_go_up ();
714 if (self.classname == "door")
719 door_rotating_go_down ();
723 //gib dying stuff just to make sure
724 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
725 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
729 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
730 // if a door has a negative wait, it would never come back if blocked,
731 // so let it just squash the object to death real fast
732 /* if (self.wait >= 0)
734 if (self.state == STATE_DOWN)
745 if (self.noise1 != "")
746 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
747 self.state = STATE_TOP;
748 if (self.spawnflags & DOOR_TOGGLE)
749 return; // don't come down automatically
750 if (self.classname == "door")
752 self.think = door_go_down;
755 self.think = door_rotating_go_down;
757 self.nextthink = self.ltime + self.wait;
760 void door_hit_bottom()
762 if (self.noise1 != "")
763 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
764 self.state = STATE_BOTTOM;
769 if (self.noise2 != "")
770 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
773 self.takedamage = DAMAGE_YES;
774 self.health = self.max_health;
777 self.state = STATE_DOWN;
778 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
783 if (self.state == STATE_UP)
784 return; // already going up
786 if (self.state == STATE_TOP)
787 { // reset top wait time
788 self.nextthink = self.ltime + self.wait;
792 if (self.noise2 != "")
793 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
794 self.state = STATE_UP;
795 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
798 oldmessage = self.message;
801 self.message = oldmessage;
806 =============================================================================
810 =============================================================================
818 if (self.owner != self)
819 objerror ("door_fire: self.owner != self");
823 if (self.spawnflags & DOOR_TOGGLE)
825 if (self.state == STATE_UP || self.state == STATE_TOP)
830 if (self.classname == "door")
836 door_rotating_go_down ();
839 } while ( (self != starte) && (self != world) );
845 // trigger all paired doors
849 if (self.classname == "door")
854 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
855 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
857 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
858 self.pos2 = '0 0 0' - self.pos2;
860 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
861 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
862 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
864 door_rotating_go_up ();
868 } while ( (self != starte) && (self != world) );
877 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
888 void door_trigger_touch()
890 if (other.health < 1)
891 if not(other.iscreature && other.deadflag == DEAD_NO)
894 if (time < self.attack_finished_single)
896 self.attack_finished_single = time + 1;
905 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
908 if(self.spawnflags & DOOR_NOSPLASH)
909 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
911 self.health = self.health - damage;
912 if (self.health <= 0)
916 self.health = self.max_health;
917 self.takedamage = DAMAGE_NO; // wil be reset upon return
933 if(other.classname != "player")
935 if (self.owner.attack_finished_single > time)
938 self.owner.attack_finished_single = time + 2;
940 if (!(self.owner.dmg) && (self.owner.message != ""))
942 if (other.flags & FL_CLIENT)
943 centerprint (other, self.owner.message);
944 play2(other, "misc/talk.wav");
949 void door_generic_plat_blocked()
952 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
953 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
956 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
957 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
959 //Dont chamge direction for dead or dying stuff
960 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
963 if (self.state == STATE_DOWN)
964 door_rotating_go_up ();
966 door_rotating_go_down ();
969 //gib dying stuff just to make sure
970 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
971 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
975 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
976 // if a door has a negative wait, it would never come back if blocked,
977 // so let it just squash the object to death real fast
978 /* if (self.wait >= 0)
980 if (self.state == STATE_DOWN)
981 door_rotating_go_up ();
983 door_rotating_go_down ();
989 void door_rotating_hit_top()
991 if (self.noise1 != "")
992 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
993 self.state = STATE_TOP;
994 if (self.spawnflags & DOOR_TOGGLE)
995 return; // don't come down automatically
996 self.think = door_rotating_go_down;
997 self.nextthink = self.ltime + self.wait;
1000 void door_rotating_hit_bottom()
1002 if (self.noise1 != "")
1003 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1004 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1006 self.pos2 = '0 0 0' - self.pos2;
1009 self.state = STATE_BOTTOM;
1012 void door_rotating_go_down()
1014 if (self.noise2 != "")
1015 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1016 if (self.max_health)
1018 self.takedamage = DAMAGE_YES;
1019 self.health = self.max_health;
1022 self.state = STATE_DOWN;
1023 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1026 void door_rotating_go_up()
1028 if (self.state == STATE_UP)
1029 return; // already going up
1031 if (self.state == STATE_TOP)
1032 { // reset top wait time
1033 self.nextthink = self.ltime + self.wait;
1036 if (self.noise2 != "")
1037 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1038 self.state = STATE_UP;
1039 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1042 oldmessage = self.message;
1045 self.message = oldmessage;
1052 =============================================================================
1056 =============================================================================
1060 entity spawn_field(vector fmins, vector fmaxs)
1062 local entity trigger;
1063 local vector t1, t2;
1066 trigger.classname = "doortriggerfield";
1067 trigger.movetype = MOVETYPE_NONE;
1068 trigger.solid = SOLID_TRIGGER;
1069 trigger.owner = self;
1070 trigger.touch = door_trigger_touch;
1074 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1079 float EntitiesTouching(entity e1, entity e2)
1081 if (e1.absmin_x > e2.absmax_x)
1083 if (e1.absmin_y > e2.absmax_y)
1085 if (e1.absmin_z > e2.absmax_z)
1087 if (e1.absmax_x < e2.absmin_x)
1089 if (e1.absmax_y < e2.absmin_y)
1091 if (e1.absmax_z < e2.absmin_z)
1106 local entity t, starte;
1107 local vector cmins, cmaxs;
1110 return; // already linked by another door
1111 if (self.spawnflags & 4)
1113 self.owner = self.enemy = self;
1121 self.trigger_field = spawn_field(self.absmin, self.absmax);
1123 return; // don't want to link this door
1126 cmins = self.absmin;
1127 cmaxs = self.absmax;
1134 self.owner = starte; // master door
1137 starte.health = self.health;
1139 starte.targetname = self.targetname;
1140 if (self.message != "")
1141 starte.message = self.message;
1143 t = find(t, classname, self.classname);
1146 self.enemy = starte; // make the chain a loop
1148 // shootable, or triggered doors just needed the owner/enemy links,
1149 // they don't spawn a field
1160 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1165 if (EntitiesTouching(self,t))
1168 objerror ("cross connected doors");
1173 if (t.absmin_x < cmins_x)
1174 cmins_x = t.absmin_x;
1175 if (t.absmin_y < cmins_y)
1176 cmins_y = t.absmin_y;
1177 if (t.absmin_z < cmins_z)
1178 cmins_z = t.absmin_z;
1179 if (t.absmax_x > cmaxs_x)
1180 cmaxs_x = t.absmax_x;
1181 if (t.absmax_y > cmaxs_y)
1182 cmaxs_y = t.absmax_y;
1183 if (t.absmax_z > cmaxs_z)
1184 cmaxs_z = t.absmax_z;
1191 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1192 if two doors touch, they are assumed to be connected and operate as a unit.
1194 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1196 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).
1198 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1199 "angle" determines the opening direction
1200 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1201 "health" if set, door must be shot open
1202 "speed" movement speed (100 default)
1203 "wait" wait before returning (3 default, -1 = never return)
1204 "lip" lip remaining at end of move (8 default)
1205 "dmg" damage to inflict when blocked (2 default)
1212 FIXME: only one sound set available at the time being
1216 void door_init_startopen()
1218 setorigin (self, self.pos2);
1219 self.pos2 = self.pos1;
1220 self.pos1 = self.origin;
1225 setorigin(self, self.pos1);
1226 self.velocity = '0 0 0';
1227 self.state = STATE_BOTTOM;
1228 self.think = SUB_Null;
1231 void spawnfunc_func_door()
1233 //if (!self.deathtype) // map makers can override this
1234 // self.deathtype = " got in the way";
1237 self.max_health = self.health;
1238 if not(InitMovingBrushTrigger())
1240 self.effects |= EF_LOWPRECISION;
1241 self.classname = "door";
1243 self.blocked = door_blocked;
1244 self.use = door_use;
1246 if(self.spawnflags & 8)
1249 if(self.dmg && (!self.message))
1250 self.message = "was squished";
1251 if(self.dmg && (!self.message2))
1252 self.message2 = "was squished by";
1254 if (self.sounds > 0)
1256 precache_sound ("plats/medplat1.wav");
1257 precache_sound ("plats/medplat2.wav");
1258 self.noise2 = "plats/medplat1.wav";
1259 self.noise1 = "plats/medplat2.wav";
1269 self.pos1 = self.origin;
1270 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1272 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1273 // but spawn in the open position
1274 if (self.spawnflags & DOOR_START_OPEN)
1275 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1277 self.state = STATE_BOTTOM;
1281 self.takedamage = DAMAGE_YES;
1282 self.event_damage = door_damage;
1288 self.touch = door_touch;
1290 // LinkDoors can't be done until all of the doors have been spawned, so
1291 // the sizes can be detected properly.
1292 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1294 self.reset = door_reset;
1297 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1298 if two doors touch, they are assumed to be connected and operate as a unit.
1300 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1302 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1303 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1304 must have set trigger_reverse to 1.
1305 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1307 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).
1309 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1310 "angle" determines the destination angle for opening. negative values reverse the direction.
1311 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1312 "health" if set, door must be shot open
1313 "speed" movement speed (100 default)
1314 "wait" wait before returning (3 default, -1 = never return)
1315 "dmg" damage to inflict when blocked (2 default)
1322 FIXME: only one sound set available at the time being
1325 void door_rotating_reset()
1327 self.angles = self.pos1;
1328 self.avelocity = '0 0 0';
1329 self.state = STATE_BOTTOM;
1330 self.think = SUB_Null;
1333 void door_rotating_init_startopen()
1335 self.angles = self.movedir;
1336 self.pos2 = '0 0 0';
1337 self.pos1 = self.movedir;
1341 void spawnfunc_func_door_rotating()
1344 //if (!self.deathtype) // map makers can override this
1345 // self.deathtype = " got in the way";
1347 // I abuse "movedir" for denoting the axis for now
1348 if (self.spawnflags & 64) // X (untested)
1349 self.movedir = '0 0 1';
1350 else if (self.spawnflags & 128) // Y (untested)
1351 self.movedir = '1 0 0';
1353 self.movedir = '0 1 0';
1355 if (self.angles_y==0) self.angles_y = 90;
1357 self.movedir = self.movedir * self.angles_y;
1358 self.angles = '0 0 0';
1360 self.max_health = self.health;
1361 if not(InitMovingBrushTrigger())
1363 //self.effects |= EF_LOWPRECISION;
1364 self.classname = "door_rotating";
1366 self.blocked = door_blocked;
1367 self.use = door_use;
1369 if(self.spawnflags & 8)
1372 if(self.dmg && (!self.message))
1373 self.message = "was squished";
1374 if(self.dmg && (!self.message2))
1375 self.message2 = "was squished by";
1377 if (self.sounds > 0)
1379 precache_sound ("plats/medplat1.wav");
1380 precache_sound ("plats/medplat2.wav");
1381 self.noise2 = "plats/medplat1.wav";
1382 self.noise1 = "plats/medplat2.wav";
1389 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1391 self.pos1 = '0 0 0';
1392 self.pos2 = self.movedir;
1394 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1395 // but spawn in the open position
1396 if (self.spawnflags & DOOR_START_OPEN)
1397 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1399 self.state = STATE_BOTTOM;
1403 self.takedamage = DAMAGE_YES;
1404 self.event_damage = door_damage;
1410 self.touch = door_touch;
1412 // LinkDoors can't be done until all of the doors have been spawned, so
1413 // the sizes can be detected properly.
1414 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1416 self.reset = door_rotating_reset;
1420 =============================================================================
1424 =============================================================================
1427 void() fd_secret_move1;
1428 void() fd_secret_move2;
1429 void() fd_secret_move3;
1430 void() fd_secret_move4;
1431 void() fd_secret_move5;
1432 void() fd_secret_move6;
1433 void() fd_secret_done;
1435 float SECRET_OPEN_ONCE = 1; // stays open
1436 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1437 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1438 float SECRET_NO_SHOOT = 8; // only opened by trigger
1439 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1442 void fd_secret_use()
1445 string message_save;
1447 self.health = 10000;
1448 self.bot_attack = TRUE;
1450 // exit if still moving around...
1451 if (self.origin != self.oldorigin)
1454 message_save = self.message;
1455 self.message = ""; // no more message
1456 SUB_UseTargets(); // fire all targets / killtargets
1457 self.message = message_save;
1459 self.velocity = '0 0 0';
1461 // Make a sound, wait a little...
1463 if (self.noise1 != "")
1464 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1465 self.nextthink = self.ltime + 0.1;
1467 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1468 makevectors(self.mangle);
1472 if (self.spawnflags & SECRET_1ST_DOWN)
1473 self.t_width = fabs(v_up * self.size);
1475 self.t_width = fabs(v_right * self.size);
1479 self.t_length = fabs(v_forward * self.size);
1481 if (self.spawnflags & SECRET_1ST_DOWN)
1482 self.dest1 = self.origin - v_up * self.t_width;
1484 self.dest1 = self.origin + v_right * (self.t_width * temp);
1486 self.dest2 = self.dest1 + v_forward * self.t_length;
1487 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1488 if (self.noise2 != "")
1489 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1492 // Wait after first movement...
1493 void fd_secret_move1()
1495 self.nextthink = self.ltime + 1.0;
1496 self.think = fd_secret_move2;
1497 if (self.noise3 != "")
1498 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1501 // Start moving sideways w/sound...
1502 void fd_secret_move2()
1504 if (self.noise2 != "")
1505 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1506 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1509 // Wait here until time to go back...
1510 void fd_secret_move3()
1512 if (self.noise3 != "")
1513 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1514 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1516 self.nextthink = self.ltime + self.wait;
1517 self.think = fd_secret_move4;
1522 void fd_secret_move4()
1524 if (self.noise2 != "")
1525 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1526 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1530 void fd_secret_move5()
1532 self.nextthink = self.ltime + 1.0;
1533 self.think = fd_secret_move6;
1534 if (self.noise3 != "")
1535 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1538 void fd_secret_move6()
1540 if (self.noise2 != "")
1541 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1542 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1545 void fd_secret_done()
1547 if (self.spawnflags&SECRET_YES_SHOOT)
1549 self.health = 10000;
1550 self.takedamage = DAMAGE_YES;
1551 //self.th_pain = fd_secret_use;
1553 if (self.noise3 != "")
1554 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1557 void secret_blocked()
1559 if (time < self.attack_finished_single)
1561 self.attack_finished_single = time + 0.5;
1562 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1574 if not(other.iscreature)
1576 if (self.attack_finished_single > time)
1579 self.attack_finished_single = time + 2;
1583 if (other.flags & FL_CLIENT)
1584 centerprint (other, self.message);
1585 play2(other, "misc/talk.wav");
1591 if (self.spawnflags&SECRET_YES_SHOOT)
1593 self.health = 10000;
1594 self.takedamage = DAMAGE_YES;
1596 setorigin(self, self.oldorigin);
1597 self.think = SUB_Null;
1600 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1601 Basic secret door. Slides back, then to the side. Angle determines direction.
1602 wait = # of seconds before coming back
1603 1st_left = 1st move is left of arrow
1604 1st_down = 1st move is down from arrow
1605 always_shoot = even if targeted, keep shootable
1606 t_width = override WIDTH to move back (or height if going down)
1607 t_length = override LENGTH to move sideways
1608 "dmg" damage to inflict when blocked (2 default)
1610 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1617 void spawnfunc_func_door_secret()
1619 /*if (!self.deathtype) // map makers can override this
1620 self.deathtype = " got in the way";*/
1626 self.mangle = self.angles;
1627 self.angles = '0 0 0';
1628 self.classname = "door";
1629 if not(InitMovingBrushTrigger())
1631 self.effects |= EF_LOWPRECISION;
1633 self.touch = secret_touch;
1634 self.blocked = secret_blocked;
1636 self.use = fd_secret_use;
1641 self.spawnflags |= SECRET_YES_SHOOT;
1643 if(self.spawnflags&SECRET_YES_SHOOT)
1645 self.health = 10000;
1646 self.takedamage = DAMAGE_YES;
1647 self.event_damage = fd_secret_use;
1649 self.oldorigin = self.origin;
1651 self.wait = 5; // 5 seconds before closing
1653 self.reset = secret_reset;
1657 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1658 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1659 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
1660 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1661 height: amplitude modifier (default 32)
1662 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1663 noise: path/name of looping .wav file to play.
1664 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1668 void func_fourier_controller_think()
1673 self.nextthink = time + 0.1;
1674 if not (self.owner.active == ACTIVE_ACTIVE)
1676 self.owner.velocity = '0 0 0';
1681 n = floor((tokenize_console(self.owner.netname)) / 5);
1682 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1684 v = self.owner.destvec;
1686 for(i = 0; i < n; ++i)
1688 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1689 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;
1692 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1693 // * 10 so it will arrive in 0.1 sec
1694 self.owner.velocity = (v - self.owner.origin) * 10;
1697 void spawnfunc_func_fourier()
1699 local entity controller;
1700 if (self.noise != "")
1702 precache_sound(self.noise);
1703 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1710 self.destvec = self.origin;
1711 self.cnt = 360 / self.speed;
1713 self.blocked = generic_plat_blocked;
1714 if(self.dmg & (!self.message))
1715 self.message = " was squished";
1716 if(self.dmg && (!self.message2))
1717 self.message2 = "was squished by";
1718 if(self.dmg && (!self.dmgtime))
1719 self.dmgtime = 0.25;
1720 self.dmgtime2 = time;
1722 if(self.netname == "")
1723 self.netname = "1 0 0 0 1";
1725 if not(InitMovingBrushTrigger())
1728 self.active = ACTIVE_ACTIVE;
1730 // wait for targets to spawn
1731 controller = spawn();
1732 controller.classname = "func_fourier_controller";
1733 controller.owner = self;
1734 controller.nextthink = time + 1;
1735 controller.think = func_fourier_controller_think;
1736 self.nextthink = self.ltime + 999999999;
1737 self.think = SUB_Null;
1739 // Savage: Reduce bandwith, critical on e.g. nexdm02
1740 self.effects |= EF_LOWPRECISION;
1742 // TODO make a reset function for this one
1745 // reusing some fields havocbots declared
1746 .entity wp00, wp01, wp02, wp03;
1748 .float targetfactor, target2factor, target3factor, target4factor;
1749 .vector targetnormal, target2normal, target3normal, target4normal;
1751 vector func_vectormamamam_origin(entity o, float t)
1763 p = e.origin + t * e.velocity;
1765 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1767 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1773 p = e.origin + t * e.velocity;
1775 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1777 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1783 p = e.origin + t * e.velocity;
1785 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1787 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1793 p = e.origin + t * e.velocity;
1795 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1797 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1803 void func_vectormamamam_controller_think()
1805 self.nextthink = time + 0.1;
1807 if not (self.owner.active == ACTIVE_ACTIVE)
1809 self.owner.velocity = '0 0 0';
1813 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1814 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1817 void func_vectormamamam_findtarget()
1819 if(self.target != "")
1820 self.wp00 = find(world, targetname, self.target);
1822 if(self.target2 != "")
1823 self.wp01 = find(world, targetname, self.target2);
1825 if(self.target3 != "")
1826 self.wp02 = find(world, targetname, self.target3);
1828 if(self.target4 != "")
1829 self.wp03 = find(world, targetname, self.target4);
1831 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1832 objerror("No reference entity found, so there is nothing to move. Aborting.");
1834 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1836 local entity controller;
1837 controller = spawn();
1838 controller.classname = "func_vectormamamam_controller";
1839 controller.owner = self;
1840 controller.nextthink = time + 1;
1841 controller.think = func_vectormamamam_controller_think;
1844 void spawnfunc_func_vectormamamam()
1846 if (self.noise != "")
1848 precache_sound(self.noise);
1849 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1852 if(!self.targetfactor)
1853 self.targetfactor = 1;
1855 if(!self.target2factor)
1856 self.target2factor = 1;
1858 if(!self.target3factor)
1859 self.target3factor = 1;
1861 if(!self.target4factor)
1862 self.target4factor = 1;
1864 if(vlen(self.targetnormal))
1865 self.targetnormal = normalize(self.targetnormal);
1867 if(vlen(self.target2normal))
1868 self.target2normal = normalize(self.target2normal);
1870 if(vlen(self.target3normal))
1871 self.target3normal = normalize(self.target3normal);
1873 if(vlen(self.target4normal))
1874 self.target4normal = normalize(self.target4normal);
1876 self.blocked = generic_plat_blocked;
1877 if(self.dmg & (!self.message))
1878 self.message = " was squished";
1879 if(self.dmg && (!self.message2))
1880 self.message2 = "was squished by";
1881 if(self.dmg && (!self.dmgtime))
1882 self.dmgtime = 0.25;
1883 self.dmgtime2 = time;
1885 if(self.netname == "")
1886 self.netname = "1 0 0 0 1";
1888 if not(InitMovingBrushTrigger())
1891 // wait for targets to spawn
1892 self.nextthink = self.ltime + 999999999;
1893 self.think = SUB_Null;
1895 // Savage: Reduce bandwith, critical on e.g. nexdm02
1896 self.effects |= EF_LOWPRECISION;
1898 self.active = ACTIVE_ACTIVE;
1900 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);