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 // * 10 so it will arrive in 0.1 sec
422 self.owner.velocity = (v - self.owner.origin) * 10;
425 void bobbing_blocked()
427 // no need to duplicate code
428 generic_plat_blocked();
431 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
432 Brush model that moves back and forth on one axis (default Z).
433 speed : how long one cycle takes in seconds (default 4)
434 height : how far the cycle moves (default 32)
435 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
436 noise : path/name of looping .wav file to play.
437 dmg : Do this mutch dmg every .dmgtime intervall when blocked
440 void spawnfunc_func_bobbing()
442 local entity controller;
443 if (self.noise != "")
445 precache_sound(self.noise);
446 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
452 // center of bobbing motion
453 self.destvec = self.origin;
454 // time scale to get degrees
455 self.cnt = 360 / self.speed;
457 self.active = ACTIVE_ACTIVE;
459 // damage when blocked
460 self.blocked = bobbing_blocked;
461 if(self.dmg & (!self.message))
462 self.message = " was squished";
463 if(self.dmg && (!self.message2))
464 self.message2 = "was squished by";
465 if(self.dmg && (!self.dmgtime))
467 self.dmgtime2 = time;
470 if (self.spawnflags & 1) // X
471 self.movedir = '1 0 0' * self.height;
472 else if (self.spawnflags & 2) // Y
473 self.movedir = '0 1 0' * self.height;
475 self.movedir = '0 0 1' * self.height;
477 if not(InitMovingBrushTrigger())
480 // wait for targets to spawn
481 controller = spawn();
482 controller.classname = "func_bobbing_controller";
483 controller.owner = self;
484 controller.nextthink = time + 1;
485 controller.think = func_bobbing_controller_think;
486 self.nextthink = self.ltime + 999999999;
487 self.think = SUB_Null;
489 // Savage: Reduce bandwith, critical on e.g. nexdm02
490 self.effects |= EF_LOWPRECISION;
492 // TODO make a reset function for this one
495 // button and multiple button
498 void() button_return;
502 self.state = STATE_TOP;
503 self.nextthink = self.ltime + self.wait;
504 self.think = button_return;
505 activator = self.enemy;
507 self.frame = 1; // use alternate textures
512 self.state = STATE_BOTTOM;
517 self.state = STATE_DOWN;
518 SUB_CalcMove (self.pos1, self.speed, button_done);
519 self.frame = 0; // use normal textures
521 self.takedamage = DAMAGE_YES; // can be shot again
525 void button_blocked()
527 // do nothing, just don't come all the way back out
533 self.health = self.max_health;
534 self.takedamage = DAMAGE_NO; // will be reset upon return
536 if (self.state == STATE_UP || self.state == STATE_TOP)
539 if (self.noise != "")
540 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
542 self.state = STATE_UP;
543 SUB_CalcMove (self.pos2, self.speed, button_wait);
548 self.health = self.max_health;
549 setorigin(self, self.pos1);
550 self.frame = 0; // use normal textures
551 self.state = STATE_BOTTOM;
553 self.takedamage = DAMAGE_YES; // can be shot again
558 // if (activator.classname != "player")
560 // dprint(activator.classname);
561 // dprint(" triggered a button\n");
564 if not (self.active == ACTIVE_ACTIVE)
567 self.enemy = activator;
573 // if (activator.classname != "player")
575 // dprint(activator.classname);
576 // dprint(" touched a button\n");
580 if not(other.iscreature)
582 if(other.velocity * self.movedir < 0)
586 self.enemy = other.owner;
590 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
592 if(self.spawnflags & DOOR_NOSPLASH)
593 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
595 self.health = self.health - damage;
596 if (self.health <= 0)
598 // if (activator.classname != "player")
600 // dprint(activator.classname);
601 // dprint(" killed a button\n");
603 self.enemy = damage_attacker;
609 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
610 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.
612 "angle" determines the opening direction
613 "target" all entities with a matching targetname will be used
614 "speed" override the default 40 speed
615 "wait" override the default 1 second wait (-1 = never return)
616 "lip" override the default 4 pixel lip remaining at end of move
617 "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
624 void spawnfunc_func_button()
628 if not(InitMovingBrushTrigger())
630 self.effects |= EF_LOWPRECISION;
632 self.blocked = button_blocked;
633 self.use = button_use;
635 // if (self.health == 0) // all buttons are now shootable
639 self.max_health = self.health;
640 self.event_damage = button_damage;
641 self.takedamage = DAMAGE_YES;
644 self.touch = button_touch;
654 precache_sound(self.noise);
656 self.active = ACTIVE_ACTIVE;
658 self.pos1 = self.origin;
659 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
660 self.flags |= FL_NOTARGET;
666 float DOOR_START_OPEN = 1;
667 float DOOR_DONT_LINK = 4;
668 float DOOR_TOGGLE = 32;
672 Doors are similar to buttons, but can spawn a fat trigger field around them
673 to open without a touch, and they link together to form simultanious
676 Door.owner is the master door. If there is only one door, it points to itself.
677 If multiple doors, all will point to a single one.
679 Door.enemy chains from the master door through all doors linked in the chain.
684 =============================================================================
688 =============================================================================
693 void() door_rotating_go_down;
694 void() door_rotating_go_up;
699 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
700 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
703 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
704 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
706 //Dont chamge direction for dead or dying stuff
707 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
710 if (self.state == STATE_DOWN)
711 if (self.classname == "door")
716 door_rotating_go_up ();
719 if (self.classname == "door")
724 door_rotating_go_down ();
728 //gib dying stuff just to make sure
729 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
730 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
734 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
735 // if a door has a negative wait, it would never come back if blocked,
736 // so let it just squash the object to death real fast
737 /* if (self.wait >= 0)
739 if (self.state == STATE_DOWN)
750 if (self.noise1 != "")
751 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
752 self.state = STATE_TOP;
753 if (self.spawnflags & DOOR_TOGGLE)
754 return; // don't come down automatically
755 if (self.classname == "door")
757 self.think = door_go_down;
760 self.think = door_rotating_go_down;
762 self.nextthink = self.ltime + self.wait;
765 void door_hit_bottom()
767 if (self.noise1 != "")
768 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
769 self.state = STATE_BOTTOM;
774 if (self.noise2 != "")
775 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
778 self.takedamage = DAMAGE_YES;
779 self.health = self.max_health;
782 self.state = STATE_DOWN;
783 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
788 if (self.state == STATE_UP)
789 return; // already going up
791 if (self.state == STATE_TOP)
792 { // reset top wait time
793 self.nextthink = self.ltime + self.wait;
797 if (self.noise2 != "")
798 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
799 self.state = STATE_UP;
800 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
803 oldmessage = self.message;
806 self.message = oldmessage;
811 =============================================================================
815 =============================================================================
823 if (self.owner != self)
824 objerror ("door_fire: self.owner != self");
828 if (self.spawnflags & DOOR_TOGGLE)
830 if (self.state == STATE_UP || self.state == STATE_TOP)
835 if (self.classname == "door")
841 door_rotating_go_down ();
844 } while ( (self != starte) && (self != world) );
850 // trigger all paired doors
854 if (self.classname == "door")
859 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
860 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
862 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
863 self.pos2 = '0 0 0' - self.pos2;
865 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
866 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
867 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
869 door_rotating_go_up ();
873 } while ( (self != starte) && (self != world) );
882 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
893 void door_trigger_touch()
895 if (other.health < 1)
896 if not(other.iscreature && other.deadflag == DEAD_NO)
899 if (time < self.attack_finished_single)
901 self.attack_finished_single = time + 1;
910 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
913 if(self.spawnflags & DOOR_NOSPLASH)
914 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
916 self.health = self.health - damage;
917 if (self.health <= 0)
921 self.health = self.max_health;
922 self.takedamage = DAMAGE_NO; // wil be reset upon return
938 if(other.classname != "player")
940 if (self.owner.attack_finished_single > time)
943 self.owner.attack_finished_single = time + 2;
945 if (!(self.owner.dmg) && (self.owner.message != ""))
947 if (other.flags & FL_CLIENT)
948 centerprint (other, self.owner.message);
949 play2(other, "misc/talk.wav");
954 void door_generic_plat_blocked()
957 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
958 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
961 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
962 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
964 //Dont chamge direction for dead or dying stuff
965 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
968 if (self.state == STATE_DOWN)
969 door_rotating_go_up ();
971 door_rotating_go_down ();
974 //gib dying stuff just to make sure
975 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
976 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
980 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
981 // if a door has a negative wait, it would never come back if blocked,
982 // so let it just squash the object to death real fast
983 /* if (self.wait >= 0)
985 if (self.state == STATE_DOWN)
986 door_rotating_go_up ();
988 door_rotating_go_down ();
994 void door_rotating_hit_top()
996 if (self.noise1 != "")
997 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
998 self.state = STATE_TOP;
999 if (self.spawnflags & DOOR_TOGGLE)
1000 return; // don't come down automatically
1001 self.think = door_rotating_go_down;
1002 self.nextthink = self.ltime + self.wait;
1005 void door_rotating_hit_bottom()
1007 if (self.noise1 != "")
1008 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1009 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1011 self.pos2 = '0 0 0' - self.pos2;
1014 self.state = STATE_BOTTOM;
1017 void door_rotating_go_down()
1019 if (self.noise2 != "")
1020 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1021 if (self.max_health)
1023 self.takedamage = DAMAGE_YES;
1024 self.health = self.max_health;
1027 self.state = STATE_DOWN;
1028 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1031 void door_rotating_go_up()
1033 if (self.state == STATE_UP)
1034 return; // already going up
1036 if (self.state == STATE_TOP)
1037 { // reset top wait time
1038 self.nextthink = self.ltime + self.wait;
1041 if (self.noise2 != "")
1042 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1043 self.state = STATE_UP;
1044 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1047 oldmessage = self.message;
1050 self.message = oldmessage;
1057 =============================================================================
1061 =============================================================================
1065 entity spawn_field(vector fmins, vector fmaxs)
1067 local entity trigger;
1068 local vector t1, t2;
1071 trigger.classname = "doortriggerfield";
1072 trigger.movetype = MOVETYPE_NONE;
1073 trigger.solid = SOLID_TRIGGER;
1074 trigger.owner = self;
1075 trigger.touch = door_trigger_touch;
1079 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1084 float EntitiesTouching(entity e1, entity e2)
1086 if (e1.absmin_x > e2.absmax_x)
1088 if (e1.absmin_y > e2.absmax_y)
1090 if (e1.absmin_z > e2.absmax_z)
1092 if (e1.absmax_x < e2.absmin_x)
1094 if (e1.absmax_y < e2.absmin_y)
1096 if (e1.absmax_z < e2.absmin_z)
1111 local entity t, starte;
1112 local vector cmins, cmaxs;
1115 return; // already linked by another door
1116 if (self.spawnflags & 4)
1118 self.owner = self.enemy = self;
1126 self.trigger_field = spawn_field(self.absmin, self.absmax);
1128 return; // don't want to link this door
1131 cmins = self.absmin;
1132 cmaxs = self.absmax;
1139 self.owner = starte; // master door
1142 starte.health = self.health;
1144 starte.targetname = self.targetname;
1145 if (self.message != "")
1146 starte.message = self.message;
1148 t = find(t, classname, self.classname);
1151 self.enemy = starte; // make the chain a loop
1153 // shootable, or triggered doors just needed the owner/enemy links,
1154 // they don't spawn a field
1165 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1170 if (EntitiesTouching(self,t))
1173 objerror ("cross connected doors");
1178 if (t.absmin_x < cmins_x)
1179 cmins_x = t.absmin_x;
1180 if (t.absmin_y < cmins_y)
1181 cmins_y = t.absmin_y;
1182 if (t.absmin_z < cmins_z)
1183 cmins_z = t.absmin_z;
1184 if (t.absmax_x > cmaxs_x)
1185 cmaxs_x = t.absmax_x;
1186 if (t.absmax_y > cmaxs_y)
1187 cmaxs_y = t.absmax_y;
1188 if (t.absmax_z > cmaxs_z)
1189 cmaxs_z = t.absmax_z;
1196 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1197 if two doors touch, they are assumed to be connected and operate as a unit.
1199 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1201 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).
1203 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1204 "angle" determines the opening direction
1205 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1206 "health" if set, door must be shot open
1207 "speed" movement speed (100 default)
1208 "wait" wait before returning (3 default, -1 = never return)
1209 "lip" lip remaining at end of move (8 default)
1210 "dmg" damage to inflict when blocked (2 default)
1217 FIXME: only one sound set available at the time being
1221 void door_init_startopen()
1223 setorigin (self, self.pos2);
1224 self.pos2 = self.pos1;
1225 self.pos1 = self.origin;
1230 setorigin(self, self.pos1);
1231 self.velocity = '0 0 0';
1232 self.state = STATE_BOTTOM;
1233 self.think = SUB_Null;
1236 void spawnfunc_func_door()
1238 //if (!self.deathtype) // map makers can override this
1239 // self.deathtype = " got in the way";
1242 self.max_health = self.health;
1243 if not(InitMovingBrushTrigger())
1245 self.effects |= EF_LOWPRECISION;
1246 self.classname = "door";
1248 self.blocked = door_blocked;
1249 self.use = door_use;
1251 if(self.spawnflags & 8)
1254 if(self.dmg && (!self.message))
1255 self.message = "was squished";
1256 if(self.dmg && (!self.message2))
1257 self.message2 = "was squished by";
1259 if (self.sounds > 0)
1261 precache_sound ("plats/medplat1.wav");
1262 precache_sound ("plats/medplat2.wav");
1263 self.noise2 = "plats/medplat1.wav";
1264 self.noise1 = "plats/medplat2.wav";
1274 self.pos1 = self.origin;
1275 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1277 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1278 // but spawn in the open position
1279 if (self.spawnflags & DOOR_START_OPEN)
1280 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1282 self.state = STATE_BOTTOM;
1286 self.takedamage = DAMAGE_YES;
1287 self.event_damage = door_damage;
1293 self.touch = door_touch;
1295 // LinkDoors can't be done until all of the doors have been spawned, so
1296 // the sizes can be detected properly.
1297 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1299 self.reset = door_reset;
1302 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1303 if two doors touch, they are assumed to be connected and operate as a unit.
1305 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1307 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1308 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1309 must have set trigger_reverse to 1.
1310 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1312 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).
1314 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1315 "angle" determines the destination angle for opening. negative values reverse the direction.
1316 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1317 "health" if set, door must be shot open
1318 "speed" movement speed (100 default)
1319 "wait" wait before returning (3 default, -1 = never return)
1320 "dmg" damage to inflict when blocked (2 default)
1327 FIXME: only one sound set available at the time being
1330 void door_rotating_reset()
1332 self.angles = self.pos1;
1333 self.avelocity = '0 0 0';
1334 self.state = STATE_BOTTOM;
1335 self.think = SUB_Null;
1338 void door_rotating_init_startopen()
1340 self.angles = self.movedir;
1341 self.pos2 = '0 0 0';
1342 self.pos1 = self.movedir;
1346 void spawnfunc_func_door_rotating()
1349 //if (!self.deathtype) // map makers can override this
1350 // self.deathtype = " got in the way";
1352 // I abuse "movedir" for denoting the axis for now
1353 if (self.spawnflags & 64) // X (untested)
1354 self.movedir = '0 0 1';
1355 else if (self.spawnflags & 128) // Y (untested)
1356 self.movedir = '1 0 0';
1358 self.movedir = '0 1 0';
1360 if (self.angles_y==0) self.angles_y = 90;
1362 self.movedir = self.movedir * self.angles_y;
1363 self.angles = '0 0 0';
1365 self.max_health = self.health;
1366 if not(InitMovingBrushTrigger())
1368 //self.effects |= EF_LOWPRECISION;
1369 self.classname = "door_rotating";
1371 self.blocked = door_blocked;
1372 self.use = door_use;
1374 if(self.spawnflags & 8)
1377 if(self.dmg && (!self.message))
1378 self.message = "was squished";
1379 if(self.dmg && (!self.message2))
1380 self.message2 = "was squished by";
1382 if (self.sounds > 0)
1384 precache_sound ("plats/medplat1.wav");
1385 precache_sound ("plats/medplat2.wav");
1386 self.noise2 = "plats/medplat1.wav";
1387 self.noise1 = "plats/medplat2.wav";
1394 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1396 self.pos1 = '0 0 0';
1397 self.pos2 = self.movedir;
1399 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1400 // but spawn in the open position
1401 if (self.spawnflags & DOOR_START_OPEN)
1402 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1404 self.state = STATE_BOTTOM;
1408 self.takedamage = DAMAGE_YES;
1409 self.event_damage = door_damage;
1415 self.touch = door_touch;
1417 // LinkDoors can't be done until all of the doors have been spawned, so
1418 // the sizes can be detected properly.
1419 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1421 self.reset = door_rotating_reset;
1425 =============================================================================
1429 =============================================================================
1432 void() fd_secret_move1;
1433 void() fd_secret_move2;
1434 void() fd_secret_move3;
1435 void() fd_secret_move4;
1436 void() fd_secret_move5;
1437 void() fd_secret_move6;
1438 void() fd_secret_done;
1440 float SECRET_OPEN_ONCE = 1; // stays open
1441 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1442 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1443 float SECRET_NO_SHOOT = 8; // only opened by trigger
1444 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1447 void fd_secret_use()
1450 string message_save;
1452 self.health = 10000;
1453 self.bot_attack = TRUE;
1455 // exit if still moving around...
1456 if (self.origin != self.oldorigin)
1459 message_save = self.message;
1460 self.message = ""; // no more message
1461 SUB_UseTargets(); // fire all targets / killtargets
1462 self.message = message_save;
1464 self.velocity = '0 0 0';
1466 // Make a sound, wait a little...
1468 if (self.noise1 != "")
1469 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1470 self.nextthink = self.ltime + 0.1;
1472 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1473 makevectors(self.mangle);
1477 if (self.spawnflags & SECRET_1ST_DOWN)
1478 self.t_width = fabs(v_up * self.size);
1480 self.t_width = fabs(v_right * self.size);
1484 self.t_length = fabs(v_forward * self.size);
1486 if (self.spawnflags & SECRET_1ST_DOWN)
1487 self.dest1 = self.origin - v_up * self.t_width;
1489 self.dest1 = self.origin + v_right * (self.t_width * temp);
1491 self.dest2 = self.dest1 + v_forward * self.t_length;
1492 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1493 if (self.noise2 != "")
1494 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1497 // Wait after first movement...
1498 void fd_secret_move1()
1500 self.nextthink = self.ltime + 1.0;
1501 self.think = fd_secret_move2;
1502 if (self.noise3 != "")
1503 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1506 // Start moving sideways w/sound...
1507 void fd_secret_move2()
1509 if (self.noise2 != "")
1510 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1511 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1514 // Wait here until time to go back...
1515 void fd_secret_move3()
1517 if (self.noise3 != "")
1518 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1519 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1521 self.nextthink = self.ltime + self.wait;
1522 self.think = fd_secret_move4;
1527 void fd_secret_move4()
1529 if (self.noise2 != "")
1530 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1531 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1535 void fd_secret_move5()
1537 self.nextthink = self.ltime + 1.0;
1538 self.think = fd_secret_move6;
1539 if (self.noise3 != "")
1540 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1543 void fd_secret_move6()
1545 if (self.noise2 != "")
1546 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1547 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1550 void fd_secret_done()
1552 if (self.spawnflags&SECRET_YES_SHOOT)
1554 self.health = 10000;
1555 self.takedamage = DAMAGE_YES;
1556 //self.th_pain = fd_secret_use;
1558 if (self.noise3 != "")
1559 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1562 void secret_blocked()
1564 if (time < self.attack_finished_single)
1566 self.attack_finished_single = time + 0.5;
1567 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1579 if not(other.iscreature)
1581 if (self.attack_finished_single > time)
1584 self.attack_finished_single = time + 2;
1588 if (other.flags & FL_CLIENT)
1589 centerprint (other, self.message);
1590 play2(other, "misc/talk.wav");
1596 if (self.spawnflags&SECRET_YES_SHOOT)
1598 self.health = 10000;
1599 self.takedamage = DAMAGE_YES;
1601 setorigin(self, self.oldorigin);
1602 self.think = SUB_Null;
1605 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1606 Basic secret door. Slides back, then to the side. Angle determines direction.
1607 wait = # of seconds before coming back
1608 1st_left = 1st move is left of arrow
1609 1st_down = 1st move is down from arrow
1610 always_shoot = even if targeted, keep shootable
1611 t_width = override WIDTH to move back (or height if going down)
1612 t_length = override LENGTH to move sideways
1613 "dmg" damage to inflict when blocked (2 default)
1615 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1622 void spawnfunc_func_door_secret()
1624 /*if (!self.deathtype) // map makers can override this
1625 self.deathtype = " got in the way";*/
1631 self.mangle = self.angles;
1632 self.angles = '0 0 0';
1633 self.classname = "door";
1634 if not(InitMovingBrushTrigger())
1636 self.effects |= EF_LOWPRECISION;
1638 self.touch = secret_touch;
1639 self.blocked = secret_blocked;
1641 self.use = fd_secret_use;
1646 self.spawnflags |= SECRET_YES_SHOOT;
1648 if(self.spawnflags&SECRET_YES_SHOOT)
1650 self.health = 10000;
1651 self.takedamage = DAMAGE_YES;
1652 self.event_damage = fd_secret_use;
1654 self.oldorigin = self.origin;
1656 self.wait = 5; // 5 seconds before closing
1658 self.reset = secret_reset;
1662 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1663 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1664 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
1665 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1666 height: amplitude modifier (default 32)
1667 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1668 noise: path/name of looping .wav file to play.
1669 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1673 void func_fourier_controller_think()
1678 self.nextthink = time + 0.1;
1679 if not (self.owner.active == ACTIVE_ACTIVE)
1681 self.owner.velocity = '0 0 0';
1686 n = floor((tokenize_console(self.owner.netname)) / 5);
1687 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1689 v = self.owner.destvec;
1691 for(i = 0; i < n; ++i)
1693 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1694 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;
1697 // * 10 so it will arrive in 0.1 sec
1698 self.owner.velocity = (v - self.owner.origin) * 10;
1701 void spawnfunc_func_fourier()
1703 local entity controller;
1704 if (self.noise != "")
1706 precache_sound(self.noise);
1707 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1714 self.destvec = self.origin;
1715 self.cnt = 360 / self.speed;
1717 self.blocked = generic_plat_blocked;
1718 if(self.dmg & (!self.message))
1719 self.message = " was squished";
1720 if(self.dmg && (!self.message2))
1721 self.message2 = "was squished by";
1722 if(self.dmg && (!self.dmgtime))
1723 self.dmgtime = 0.25;
1724 self.dmgtime2 = time;
1726 if(self.netname == "")
1727 self.netname = "1 0 0 0 1";
1729 if not(InitMovingBrushTrigger())
1732 self.active = ACTIVE_ACTIVE;
1734 // wait for targets to spawn
1735 controller = spawn();
1736 controller.classname = "func_fourier_controller";
1737 controller.owner = self;
1738 controller.nextthink = time + 1;
1739 controller.think = func_fourier_controller_think;
1740 self.nextthink = self.ltime + 999999999;
1741 self.think = SUB_Null;
1743 // Savage: Reduce bandwith, critical on e.g. nexdm02
1744 self.effects |= EF_LOWPRECISION;
1746 // TODO make a reset function for this one
1749 // reusing some fields havocbots declared
1750 .entity wp00, wp01, wp02, wp03;
1752 .float targetfactor, target2factor, target3factor, target4factor;
1753 .vector targetnormal, target2normal, target3normal, target4normal;
1755 vector func_vectormamamam_origin(entity o, float t)
1767 p = e.origin + t * e.velocity;
1769 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1771 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1777 p = e.origin + t * e.velocity;
1779 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1781 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1787 p = e.origin + t * e.velocity;
1789 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1791 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1797 p = e.origin + t * e.velocity;
1799 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1801 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1807 void func_vectormamamam_controller_think()
1809 self.nextthink = time + 0.1;
1811 if not (self.owner.active == ACTIVE_ACTIVE)
1813 self.owner.velocity = '0 0 0';
1817 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1820 void func_vectormamamam_findtarget()
1822 if(self.target != "")
1823 self.wp00 = find(world, targetname, self.target);
1825 if(self.target2 != "")
1826 self.wp01 = find(world, targetname, self.target2);
1828 if(self.target3 != "")
1829 self.wp02 = find(world, targetname, self.target3);
1831 if(self.target4 != "")
1832 self.wp03 = find(world, targetname, self.target4);
1834 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1835 objerror("No reference entity found, so there is nothing to move. Aborting.");
1837 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1839 local entity controller;
1840 controller = spawn();
1841 controller.classname = "func_vectormamamam_controller";
1842 controller.owner = self;
1843 controller.nextthink = time + 1;
1844 controller.think = func_vectormamamam_controller_think;
1847 void spawnfunc_func_vectormamamam()
1849 if (self.noise != "")
1851 precache_sound(self.noise);
1852 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1855 if(!self.targetfactor)
1856 self.targetfactor = 1;
1858 if(!self.target2factor)
1859 self.target2factor = 1;
1861 if(!self.target3factor)
1862 self.target3factor = 1;
1864 if(!self.target4factor)
1865 self.target4factor = 1;
1867 if(vlen(self.targetnormal))
1868 self.targetnormal = normalize(self.targetnormal);
1870 if(vlen(self.target2normal))
1871 self.target2normal = normalize(self.target2normal);
1873 if(vlen(self.target3normal))
1874 self.target3normal = normalize(self.target3normal);
1876 if(vlen(self.target4normal))
1877 self.target4normal = normalize(self.target4normal);
1879 self.blocked = generic_plat_blocked;
1880 if(self.dmg & (!self.message))
1881 self.message = " was squished";
1882 if(self.dmg && (!self.message2))
1883 self.message2 = "was squished by";
1884 if(self.dmg && (!self.dmgtime))
1885 self.dmgtime = 0.25;
1886 self.dmgtime2 = time;
1888 if(self.netname == "")
1889 self.netname = "1 0 0 0 1";
1891 if not(InitMovingBrushTrigger())
1894 // wait for targets to spawn
1895 self.nextthink = self.ltime + 999999999;
1896 self.think = SUB_Null;
1898 // Savage: Reduce bandwith, critical on e.g. nexdm02
1899 self.effects |= EF_LOWPRECISION;
1901 self.active = ACTIVE_ACTIVE;
1903 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);