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;
249 stopsoundto(MSG_BROADCAST, self, CHAN_TRIGGER); // send this as unreliable only, as the train will resume operation shortly anyway
257 self.think = train_next;
258 self.nextthink = self.ltime + self.wait;
272 targ = find(world, targetname, self.target);
274 self.target = targ.target;
276 objerror("train_next: no next target");
277 self.wait = targ.wait;
282 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
284 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
287 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
290 void func_train_find()
293 targ = find(world, targetname, self.target);
294 self.target = targ.target;
296 objerror("func_train_find: no next target");
297 setorigin(self, targ.origin - self.mins);
298 self.nextthink = self.ltime + 1;
299 self.think = train_next;
302 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
303 Ridable platform, targets spawnfunc_path_corner path to follow.
304 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
305 target : targetname of first spawnfunc_path_corner (starts here)
307 void spawnfunc_func_train()
309 if (self.noise != "")
310 precache_sound(self.noise);
313 objerror("func_train without a target");
317 if not(InitMovingBrushTrigger())
319 self.effects |= EF_LOWPRECISION;
321 // wait for targets to spawn
322 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
324 self.blocked = generic_plat_blocked;
325 if(self.dmg & (!self.message))
326 self.message = " was squished";
327 if(self.dmg && (!self.message2))
328 self.message2 = "was squished by";
329 if(self.dmg && (!self.dmgtime))
331 self.dmgtime2 = time;
333 // TODO make a reset function for this one
336 void func_rotating_setactive(float astate)
339 if (astate == ACTIVE_TOGGLE)
341 if(self.active == ACTIVE_ACTIVE)
342 self.active = ACTIVE_NOT;
344 self.active = ACTIVE_ACTIVE;
347 self.active = astate;
349 if(self.active == ACTIVE_NOT)
350 self.avelocity = '0 0 0';
352 self.avelocity = self.pos1;
355 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
356 Brush model that spins in place on one axis (default Z).
357 speed : speed to rotate (in degrees per second)
358 noise : path/name of looping .wav file to play.
359 dmg : Do this mutch dmg every .dmgtime intervall when blocked
363 void spawnfunc_func_rotating()
365 if (self.noise != "")
367 precache_sound(self.noise);
368 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
371 self.active = ACTIVE_ACTIVE;
372 self.setactive = func_rotating_setactive;
376 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
377 if (self.spawnflags & 4) // X (untested)
378 self.avelocity = '0 0 1' * self.speed;
379 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
380 else if (self.spawnflags & 8) // Y (untested)
381 self.avelocity = '1 0 0' * self.speed;
382 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
384 self.avelocity = '0 1 0' * self.speed;
386 self.pos1 = self.avelocity;
388 if(self.dmg & (!self.message))
389 self.message = " was squished";
390 if(self.dmg && (!self.message2))
391 self.message2 = "was squished by";
394 if(self.dmg && (!self.dmgtime))
397 self.dmgtime2 = time;
399 if not(InitMovingBrushTrigger())
401 // no EF_LOWPRECISION here, as rounding angles is bad
403 self.blocked = generic_plat_blocked;
405 // wait for targets to spawn
406 self.nextthink = self.ltime + 999999999;
407 self.think = SUB_Null;
409 // TODO make a reset function for this one
413 void func_bobbing_controller_think()
416 self.nextthink = time + 0.1;
418 if not (self.owner.active == ACTIVE_ACTIVE)
420 self.owner.velocity = '0 0 0';
424 // calculate sinewave using makevectors
425 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
426 v = self.owner.destvec + self.owner.movedir * v_forward_y;
427 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
428 // * 10 so it will arrive in 0.1 sec
429 self.owner.velocity = (v - self.owner.origin) * 10;
432 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
433 Brush model that moves back and forth on one axis (default Z).
434 speed : how long one cycle takes in seconds (default 4)
435 height : how far the cycle moves (default 32)
436 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
437 noise : path/name of looping .wav file to play.
438 dmg : Do this mutch dmg every .dmgtime intervall when blocked
441 void spawnfunc_func_bobbing()
443 local entity controller;
444 if (self.noise != "")
446 precache_sound(self.noise);
447 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
453 // center of bobbing motion
454 self.destvec = self.origin;
455 // time scale to get degrees
456 self.cnt = 360 / self.speed;
458 self.active = ACTIVE_ACTIVE;
460 // damage when blocked
461 self.blocked = generic_plat_blocked;
462 if(self.dmg & (!self.message))
463 self.message = " was squished";
464 if(self.dmg && (!self.message2))
465 self.message2 = "was squished by";
466 if(self.dmg && (!self.dmgtime))
468 self.dmgtime2 = time;
471 if (self.spawnflags & 1) // X
472 self.movedir = '1 0 0' * self.height;
473 else if (self.spawnflags & 2) // Y
474 self.movedir = '0 1 0' * self.height;
476 self.movedir = '0 0 1' * self.height;
478 if not(InitMovingBrushTrigger())
481 // wait for targets to spawn
482 controller = spawn();
483 controller.classname = "func_bobbing_controller";
484 controller.owner = self;
485 controller.nextthink = time + 1;
486 controller.think = func_bobbing_controller_think;
487 self.nextthink = self.ltime + 999999999;
488 self.think = SUB_Null;
490 // Savage: Reduce bandwith, critical on e.g. nexdm02
491 self.effects |= EF_LOWPRECISION;
493 // TODO make a reset function for this one
497 void func_pendulum_controller_think()
500 self.nextthink = time + 0.1;
502 if not (self.owner.active == ACTIVE_ACTIVE)
504 self.owner.avelocity_x = 0;
508 // calculate sinewave using makevectors
509 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
510 v = self.owner.speed * v_forward_y + self.cnt;
511 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
513 // * 10 so it will arrive in 0.1 sec
514 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
518 void spawnfunc_func_pendulum()
520 local entity controller;
521 if (self.noise != "")
523 precache_sound(self.noise);
524 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
527 self.active = ACTIVE_ACTIVE;
529 // keys: angle, speed, phase, noise, freq
533 // not initializing self.dmg to 2, to allow damageless pendulum
535 if(self.dmg & (!self.message))
536 self.message = " was squished";
537 if(self.dmg && (!self.message2))
538 self.message2 = "was squished by";
539 if(self.dmg && (!self.dmgtime))
541 self.dmgtime2 = time;
543 self.blocked = generic_plat_blocked;
545 self.avelocity_z = 0.0000001;
546 if not(InitMovingBrushTrigger())
551 // find pendulum length (same formula as Q3A)
552 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
555 // copy initial angle
556 self.cnt = self.angles_z;
558 // wait for targets to spawn
559 controller = spawn();
560 controller.classname = "func_pendulum_controller";
561 controller.owner = self;
562 controller.nextthink = time + 1;
563 controller.think = func_pendulum_controller_think;
564 self.nextthink = self.ltime + 999999999;
565 self.think = SUB_Null;
567 //self.effects |= EF_LOWPRECISION;
569 // TODO make a reset function for this one
572 // button and multiple button
575 void() button_return;
579 self.state = STATE_TOP;
580 self.nextthink = self.ltime + self.wait;
581 self.think = button_return;
582 activator = self.enemy;
584 self.frame = 1; // use alternate textures
589 self.state = STATE_BOTTOM;
594 self.state = STATE_DOWN;
595 SUB_CalcMove (self.pos1, self.speed, button_done);
596 self.frame = 0; // use normal textures
598 self.takedamage = DAMAGE_YES; // can be shot again
602 void button_blocked()
604 // do nothing, just don't come all the way back out
610 self.health = self.max_health;
611 self.takedamage = DAMAGE_NO; // will be reset upon return
613 if (self.state == STATE_UP || self.state == STATE_TOP)
616 if (self.noise != "")
617 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
619 self.state = STATE_UP;
620 SUB_CalcMove (self.pos2, self.speed, button_wait);
625 self.health = self.max_health;
626 setorigin(self, self.pos1);
627 self.frame = 0; // use normal textures
628 self.state = STATE_BOTTOM;
630 self.takedamage = DAMAGE_YES; // can be shot again
635 // if (activator.classname != "player")
637 // dprint(activator.classname);
638 // dprint(" triggered a button\n");
641 if not (self.active == ACTIVE_ACTIVE)
644 self.enemy = activator;
650 // if (activator.classname != "player")
652 // dprint(activator.classname);
653 // dprint(" touched a button\n");
657 if not(other.iscreature)
659 if(other.velocity * self.movedir < 0)
663 self.enemy = other.owner;
667 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
669 if(self.spawnflags & DOOR_NOSPLASH)
670 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
672 self.health = self.health - damage;
673 if (self.health <= 0)
675 // if (activator.classname != "player")
677 // dprint(activator.classname);
678 // dprint(" killed a button\n");
680 self.enemy = damage_attacker;
686 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
687 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.
689 "angle" determines the opening direction
690 "target" all entities with a matching targetname will be used
691 "speed" override the default 40 speed
692 "wait" override the default 1 second wait (-1 = never return)
693 "lip" override the default 4 pixel lip remaining at end of move
694 "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
701 void spawnfunc_func_button()
705 if not(InitMovingBrushTrigger())
707 self.effects |= EF_LOWPRECISION;
709 self.blocked = button_blocked;
710 self.use = button_use;
712 // if (self.health == 0) // all buttons are now shootable
716 self.max_health = self.health;
717 self.event_damage = button_damage;
718 self.takedamage = DAMAGE_YES;
721 self.touch = button_touch;
731 precache_sound(self.noise);
733 self.active = ACTIVE_ACTIVE;
735 self.pos1 = self.origin;
736 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
737 self.flags |= FL_NOTARGET;
743 float DOOR_START_OPEN = 1;
744 float DOOR_DONT_LINK = 4;
745 float DOOR_TOGGLE = 32;
749 Doors are similar to buttons, but can spawn a fat trigger field around them
750 to open without a touch, and they link together to form simultanious
753 Door.owner is the master door. If there is only one door, it points to itself.
754 If multiple doors, all will point to a single one.
756 Door.enemy chains from the master door through all doors linked in the chain.
761 =============================================================================
765 =============================================================================
770 void() door_rotating_go_down;
771 void() door_rotating_go_up;
776 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
777 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
780 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
781 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
783 //Dont chamge direction for dead or dying stuff
784 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
787 if (self.state == STATE_DOWN)
788 if (self.classname == "door")
793 door_rotating_go_up ();
796 if (self.classname == "door")
801 door_rotating_go_down ();
805 //gib dying stuff just to make sure
806 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
807 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
811 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
812 // if a door has a negative wait, it would never come back if blocked,
813 // so let it just squash the object to death real fast
814 /* if (self.wait >= 0)
816 if (self.state == STATE_DOWN)
827 if (self.noise1 != "")
828 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
829 self.state = STATE_TOP;
830 if (self.spawnflags & DOOR_TOGGLE)
831 return; // don't come down automatically
832 if (self.classname == "door")
834 self.think = door_go_down;
837 self.think = door_rotating_go_down;
839 self.nextthink = self.ltime + self.wait;
842 void door_hit_bottom()
844 if (self.noise1 != "")
845 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
846 self.state = STATE_BOTTOM;
851 if (self.noise2 != "")
852 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
855 self.takedamage = DAMAGE_YES;
856 self.health = self.max_health;
859 self.state = STATE_DOWN;
860 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
865 if (self.state == STATE_UP)
866 return; // already going up
868 if (self.state == STATE_TOP)
869 { // reset top wait time
870 self.nextthink = self.ltime + self.wait;
874 if (self.noise2 != "")
875 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
876 self.state = STATE_UP;
877 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
880 oldmessage = self.message;
883 self.message = oldmessage;
888 =============================================================================
892 =============================================================================
900 if (self.owner != self)
901 objerror ("door_fire: self.owner != self");
905 if (self.spawnflags & DOOR_TOGGLE)
907 if (self.state == STATE_UP || self.state == STATE_TOP)
912 if (self.classname == "door")
918 door_rotating_go_down ();
921 } while ( (self != starte) && (self != world) );
927 // trigger all paired doors
931 if (self.classname == "door")
936 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
937 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
939 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
940 self.pos2 = '0 0 0' - self.pos2;
942 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
943 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
944 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
946 door_rotating_go_up ();
950 } while ( (self != starte) && (self != world) );
959 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
970 void door_trigger_touch()
972 if (other.health < 1)
973 if not(other.iscreature && other.deadflag == DEAD_NO)
976 if (time < self.attack_finished_single)
978 self.attack_finished_single = time + 1;
987 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
990 if(self.spawnflags & DOOR_NOSPLASH)
991 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
993 self.health = self.health - damage;
994 if (self.health <= 0)
998 self.health = self.max_health;
999 self.takedamage = DAMAGE_NO; // wil be reset upon return
1015 if(other.classname != "player")
1017 if (self.owner.attack_finished_single > time)
1020 self.owner.attack_finished_single = time + 2;
1022 if (!(self.owner.dmg) && (self.owner.message != ""))
1024 if (other.flags & FL_CLIENT)
1025 centerprint (other, self.owner.message);
1026 play2(other, "misc/talk.wav");
1031 void door_generic_plat_blocked()
1034 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1035 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1038 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1039 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1041 //Dont chamge direction for dead or dying stuff
1042 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1045 if (self.state == STATE_DOWN)
1046 door_rotating_go_up ();
1048 door_rotating_go_down ();
1051 //gib dying stuff just to make sure
1052 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1053 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1057 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1058 // if a door has a negative wait, it would never come back if blocked,
1059 // so let it just squash the object to death real fast
1060 /* if (self.wait >= 0)
1062 if (self.state == STATE_DOWN)
1063 door_rotating_go_up ();
1065 door_rotating_go_down ();
1071 void door_rotating_hit_top()
1073 if (self.noise1 != "")
1074 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1075 self.state = STATE_TOP;
1076 if (self.spawnflags & DOOR_TOGGLE)
1077 return; // don't come down automatically
1078 self.think = door_rotating_go_down;
1079 self.nextthink = self.ltime + self.wait;
1082 void door_rotating_hit_bottom()
1084 if (self.noise1 != "")
1085 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1086 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1088 self.pos2 = '0 0 0' - self.pos2;
1091 self.state = STATE_BOTTOM;
1094 void door_rotating_go_down()
1096 if (self.noise2 != "")
1097 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1098 if (self.max_health)
1100 self.takedamage = DAMAGE_YES;
1101 self.health = self.max_health;
1104 self.state = STATE_DOWN;
1105 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1108 void door_rotating_go_up()
1110 if (self.state == STATE_UP)
1111 return; // already going up
1113 if (self.state == STATE_TOP)
1114 { // reset top wait time
1115 self.nextthink = self.ltime + self.wait;
1118 if (self.noise2 != "")
1119 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1120 self.state = STATE_UP;
1121 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1124 oldmessage = self.message;
1127 self.message = oldmessage;
1134 =============================================================================
1138 =============================================================================
1142 entity spawn_field(vector fmins, vector fmaxs)
1144 local entity trigger;
1145 local vector t1, t2;
1148 trigger.classname = "doortriggerfield";
1149 trigger.movetype = MOVETYPE_NONE;
1150 trigger.solid = SOLID_TRIGGER;
1151 trigger.owner = self;
1152 trigger.touch = door_trigger_touch;
1156 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1161 float EntitiesTouching(entity e1, entity e2)
1163 if (e1.absmin_x > e2.absmax_x)
1165 if (e1.absmin_y > e2.absmax_y)
1167 if (e1.absmin_z > e2.absmax_z)
1169 if (e1.absmax_x < e2.absmin_x)
1171 if (e1.absmax_y < e2.absmin_y)
1173 if (e1.absmax_z < e2.absmin_z)
1188 local entity t, starte;
1189 local vector cmins, cmaxs;
1192 return; // already linked by another door
1193 if (self.spawnflags & 4)
1195 self.owner = self.enemy = self;
1203 self.trigger_field = spawn_field(self.absmin, self.absmax);
1205 return; // don't want to link this door
1208 cmins = self.absmin;
1209 cmaxs = self.absmax;
1216 self.owner = starte; // master door
1219 starte.health = self.health;
1221 starte.targetname = self.targetname;
1222 if (self.message != "")
1223 starte.message = self.message;
1225 t = find(t, classname, self.classname);
1228 self.enemy = starte; // make the chain a loop
1230 // shootable, or triggered doors just needed the owner/enemy links,
1231 // they don't spawn a field
1242 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1247 if (EntitiesTouching(self,t))
1250 objerror ("cross connected doors");
1255 if (t.absmin_x < cmins_x)
1256 cmins_x = t.absmin_x;
1257 if (t.absmin_y < cmins_y)
1258 cmins_y = t.absmin_y;
1259 if (t.absmin_z < cmins_z)
1260 cmins_z = t.absmin_z;
1261 if (t.absmax_x > cmaxs_x)
1262 cmaxs_x = t.absmax_x;
1263 if (t.absmax_y > cmaxs_y)
1264 cmaxs_y = t.absmax_y;
1265 if (t.absmax_z > cmaxs_z)
1266 cmaxs_z = t.absmax_z;
1273 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1274 if two doors touch, they are assumed to be connected and operate as a unit.
1276 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1278 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).
1280 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1281 "angle" determines the opening direction
1282 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1283 "health" if set, door must be shot open
1284 "speed" movement speed (100 default)
1285 "wait" wait before returning (3 default, -1 = never return)
1286 "lip" lip remaining at end of move (8 default)
1287 "dmg" damage to inflict when blocked (2 default)
1294 FIXME: only one sound set available at the time being
1298 void door_init_startopen()
1300 setorigin (self, self.pos2);
1301 self.pos2 = self.pos1;
1302 self.pos1 = self.origin;
1307 setorigin(self, self.pos1);
1308 self.velocity = '0 0 0';
1309 self.state = STATE_BOTTOM;
1310 self.think = SUB_Null;
1313 void spawnfunc_func_door()
1315 //if (!self.deathtype) // map makers can override this
1316 // self.deathtype = " got in the way";
1319 self.max_health = self.health;
1320 if not(InitMovingBrushTrigger())
1322 self.effects |= EF_LOWPRECISION;
1323 self.classname = "door";
1325 self.blocked = door_blocked;
1326 self.use = door_use;
1328 if(self.spawnflags & 8)
1331 if(self.dmg && (!self.message))
1332 self.message = "was squished";
1333 if(self.dmg && (!self.message2))
1334 self.message2 = "was squished by";
1336 if (self.sounds > 0)
1338 precache_sound ("plats/medplat1.wav");
1339 precache_sound ("plats/medplat2.wav");
1340 self.noise2 = "plats/medplat1.wav";
1341 self.noise1 = "plats/medplat2.wav";
1351 self.pos1 = self.origin;
1352 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1354 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1355 // but spawn in the open position
1356 if (self.spawnflags & DOOR_START_OPEN)
1357 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1359 self.state = STATE_BOTTOM;
1363 self.takedamage = DAMAGE_YES;
1364 self.event_damage = door_damage;
1370 self.touch = door_touch;
1372 // LinkDoors can't be done until all of the doors have been spawned, so
1373 // the sizes can be detected properly.
1374 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1376 self.reset = door_reset;
1379 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1380 if two doors touch, they are assumed to be connected and operate as a unit.
1382 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1384 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1385 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1386 must have set trigger_reverse to 1.
1387 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1389 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).
1391 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1392 "angle" determines the destination angle for opening. negative values reverse the direction.
1393 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1394 "health" if set, door must be shot open
1395 "speed" movement speed (100 default)
1396 "wait" wait before returning (3 default, -1 = never return)
1397 "dmg" damage to inflict when blocked (2 default)
1404 FIXME: only one sound set available at the time being
1407 void door_rotating_reset()
1409 self.angles = self.pos1;
1410 self.avelocity = '0 0 0';
1411 self.state = STATE_BOTTOM;
1412 self.think = SUB_Null;
1415 void door_rotating_init_startopen()
1417 self.angles = self.movedir;
1418 self.pos2 = '0 0 0';
1419 self.pos1 = self.movedir;
1423 void spawnfunc_func_door_rotating()
1426 //if (!self.deathtype) // map makers can override this
1427 // self.deathtype = " got in the way";
1429 // I abuse "movedir" for denoting the axis for now
1430 if (self.spawnflags & 64) // X (untested)
1431 self.movedir = '0 0 1';
1432 else if (self.spawnflags & 128) // Y (untested)
1433 self.movedir = '1 0 0';
1435 self.movedir = '0 1 0';
1437 if (self.angles_y==0) self.angles_y = 90;
1439 self.movedir = self.movedir * self.angles_y;
1440 self.angles = '0 0 0';
1442 self.max_health = self.health;
1443 self.avelocity = self.movedir;
1444 if not(InitMovingBrushTrigger())
1446 self.velocity = '0 0 0';
1447 //self.effects |= EF_LOWPRECISION;
1448 self.classname = "door_rotating";
1450 self.blocked = door_blocked;
1451 self.use = door_use;
1453 if(self.spawnflags & 8)
1456 if(self.dmg && (!self.message))
1457 self.message = "was squished";
1458 if(self.dmg && (!self.message2))
1459 self.message2 = "was squished by";
1461 if (self.sounds > 0)
1463 precache_sound ("plats/medplat1.wav");
1464 precache_sound ("plats/medplat2.wav");
1465 self.noise2 = "plats/medplat1.wav";
1466 self.noise1 = "plats/medplat2.wav";
1473 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1475 self.pos1 = '0 0 0';
1476 self.pos2 = self.movedir;
1478 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1479 // but spawn in the open position
1480 if (self.spawnflags & DOOR_START_OPEN)
1481 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1483 self.state = STATE_BOTTOM;
1487 self.takedamage = DAMAGE_YES;
1488 self.event_damage = door_damage;
1494 self.touch = door_touch;
1496 // LinkDoors can't be done until all of the doors have been spawned, so
1497 // the sizes can be detected properly.
1498 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1500 self.reset = door_rotating_reset;
1504 =============================================================================
1508 =============================================================================
1511 void() fd_secret_move1;
1512 void() fd_secret_move2;
1513 void() fd_secret_move3;
1514 void() fd_secret_move4;
1515 void() fd_secret_move5;
1516 void() fd_secret_move6;
1517 void() fd_secret_done;
1519 float SECRET_OPEN_ONCE = 1; // stays open
1520 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1521 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1522 float SECRET_NO_SHOOT = 8; // only opened by trigger
1523 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1526 void fd_secret_use()
1529 string message_save;
1531 self.health = 10000;
1532 self.bot_attack = TRUE;
1534 // exit if still moving around...
1535 if (self.origin != self.oldorigin)
1538 message_save = self.message;
1539 self.message = ""; // no more message
1540 SUB_UseTargets(); // fire all targets / killtargets
1541 self.message = message_save;
1543 self.velocity = '0 0 0';
1545 // Make a sound, wait a little...
1547 if (self.noise1 != "")
1548 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1549 self.nextthink = self.ltime + 0.1;
1551 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1552 makevectors(self.mangle);
1556 if (self.spawnflags & SECRET_1ST_DOWN)
1557 self.t_width = fabs(v_up * self.size);
1559 self.t_width = fabs(v_right * self.size);
1563 self.t_length = fabs(v_forward * self.size);
1565 if (self.spawnflags & SECRET_1ST_DOWN)
1566 self.dest1 = self.origin - v_up * self.t_width;
1568 self.dest1 = self.origin + v_right * (self.t_width * temp);
1570 self.dest2 = self.dest1 + v_forward * self.t_length;
1571 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1572 if (self.noise2 != "")
1573 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1576 // Wait after first movement...
1577 void fd_secret_move1()
1579 self.nextthink = self.ltime + 1.0;
1580 self.think = fd_secret_move2;
1581 if (self.noise3 != "")
1582 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1585 // Start moving sideways w/sound...
1586 void fd_secret_move2()
1588 if (self.noise2 != "")
1589 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1590 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1593 // Wait here until time to go back...
1594 void fd_secret_move3()
1596 if (self.noise3 != "")
1597 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1598 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1600 self.nextthink = self.ltime + self.wait;
1601 self.think = fd_secret_move4;
1606 void fd_secret_move4()
1608 if (self.noise2 != "")
1609 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1610 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1614 void fd_secret_move5()
1616 self.nextthink = self.ltime + 1.0;
1617 self.think = fd_secret_move6;
1618 if (self.noise3 != "")
1619 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1622 void fd_secret_move6()
1624 if (self.noise2 != "")
1625 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1626 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1629 void fd_secret_done()
1631 if (self.spawnflags&SECRET_YES_SHOOT)
1633 self.health = 10000;
1634 self.takedamage = DAMAGE_YES;
1635 //self.th_pain = fd_secret_use;
1637 if (self.noise3 != "")
1638 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1641 void secret_blocked()
1643 if (time < self.attack_finished_single)
1645 self.attack_finished_single = time + 0.5;
1646 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1658 if not(other.iscreature)
1660 if (self.attack_finished_single > time)
1663 self.attack_finished_single = time + 2;
1667 if (other.flags & FL_CLIENT)
1668 centerprint (other, self.message);
1669 play2(other, "misc/talk.wav");
1675 if (self.spawnflags&SECRET_YES_SHOOT)
1677 self.health = 10000;
1678 self.takedamage = DAMAGE_YES;
1680 setorigin(self, self.oldorigin);
1681 self.think = SUB_Null;
1684 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1685 Basic secret door. Slides back, then to the side. Angle determines direction.
1686 wait = # of seconds before coming back
1687 1st_left = 1st move is left of arrow
1688 1st_down = 1st move is down from arrow
1689 always_shoot = even if targeted, keep shootable
1690 t_width = override WIDTH to move back (or height if going down)
1691 t_length = override LENGTH to move sideways
1692 "dmg" damage to inflict when blocked (2 default)
1694 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1701 void spawnfunc_func_door_secret()
1703 /*if (!self.deathtype) // map makers can override this
1704 self.deathtype = " got in the way";*/
1710 self.mangle = self.angles;
1711 self.angles = '0 0 0';
1712 self.classname = "door";
1713 if not(InitMovingBrushTrigger())
1715 self.effects |= EF_LOWPRECISION;
1717 self.touch = secret_touch;
1718 self.blocked = secret_blocked;
1720 self.use = fd_secret_use;
1725 self.spawnflags |= SECRET_YES_SHOOT;
1727 if(self.spawnflags&SECRET_YES_SHOOT)
1729 self.health = 10000;
1730 self.takedamage = DAMAGE_YES;
1731 self.event_damage = fd_secret_use;
1733 self.oldorigin = self.origin;
1735 self.wait = 5; // 5 seconds before closing
1737 self.reset = secret_reset;
1741 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1742 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1743 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
1744 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1745 height: amplitude modifier (default 32)
1746 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1747 noise: path/name of looping .wav file to play.
1748 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1752 void func_fourier_controller_think()
1757 self.nextthink = time + 0.1;
1758 if not (self.owner.active == ACTIVE_ACTIVE)
1760 self.owner.velocity = '0 0 0';
1765 n = floor((tokenize_console(self.owner.netname)) / 5);
1766 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1768 v = self.owner.destvec;
1770 for(i = 0; i < n; ++i)
1772 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1773 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;
1776 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1777 // * 10 so it will arrive in 0.1 sec
1778 self.owner.velocity = (v - self.owner.origin) * 10;
1781 void spawnfunc_func_fourier()
1783 local entity controller;
1784 if (self.noise != "")
1786 precache_sound(self.noise);
1787 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1794 self.destvec = self.origin;
1795 self.cnt = 360 / self.speed;
1797 self.blocked = generic_plat_blocked;
1798 if(self.dmg & (!self.message))
1799 self.message = " was squished";
1800 if(self.dmg && (!self.message2))
1801 self.message2 = "was squished by";
1802 if(self.dmg && (!self.dmgtime))
1803 self.dmgtime = 0.25;
1804 self.dmgtime2 = time;
1806 if(self.netname == "")
1807 self.netname = "1 0 0 0 1";
1809 if not(InitMovingBrushTrigger())
1812 self.active = ACTIVE_ACTIVE;
1814 // wait for targets to spawn
1815 controller = spawn();
1816 controller.classname = "func_fourier_controller";
1817 controller.owner = self;
1818 controller.nextthink = time + 1;
1819 controller.think = func_fourier_controller_think;
1820 self.nextthink = self.ltime + 999999999;
1821 self.think = SUB_Null;
1823 // Savage: Reduce bandwith, critical on e.g. nexdm02
1824 self.effects |= EF_LOWPRECISION;
1826 // TODO make a reset function for this one
1829 // reusing some fields havocbots declared
1830 .entity wp00, wp01, wp02, wp03;
1832 .float targetfactor, target2factor, target3factor, target4factor;
1833 .vector targetnormal, target2normal, target3normal, target4normal;
1835 vector func_vectormamamam_origin(entity o, float t)
1847 p = e.origin + t * e.velocity;
1849 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1851 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1857 p = e.origin + t * e.velocity;
1859 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1861 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1867 p = e.origin + t * e.velocity;
1869 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1871 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1877 p = e.origin + t * e.velocity;
1879 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1881 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1887 void func_vectormamamam_controller_think()
1889 self.nextthink = time + 0.1;
1891 if not (self.owner.active == ACTIVE_ACTIVE)
1893 self.owner.velocity = '0 0 0';
1897 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1898 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1901 void func_vectormamamam_findtarget()
1903 if(self.target != "")
1904 self.wp00 = find(world, targetname, self.target);
1906 if(self.target2 != "")
1907 self.wp01 = find(world, targetname, self.target2);
1909 if(self.target3 != "")
1910 self.wp02 = find(world, targetname, self.target3);
1912 if(self.target4 != "")
1913 self.wp03 = find(world, targetname, self.target4);
1915 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1916 objerror("No reference entity found, so there is nothing to move. Aborting.");
1918 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1920 local entity controller;
1921 controller = spawn();
1922 controller.classname = "func_vectormamamam_controller";
1923 controller.owner = self;
1924 controller.nextthink = time + 1;
1925 controller.think = func_vectormamamam_controller_think;
1928 void spawnfunc_func_vectormamamam()
1930 if (self.noise != "")
1932 precache_sound(self.noise);
1933 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1936 if(!self.targetfactor)
1937 self.targetfactor = 1;
1939 if(!self.target2factor)
1940 self.target2factor = 1;
1942 if(!self.target3factor)
1943 self.target3factor = 1;
1945 if(!self.target4factor)
1946 self.target4factor = 1;
1948 if(vlen(self.targetnormal))
1949 self.targetnormal = normalize(self.targetnormal);
1951 if(vlen(self.target2normal))
1952 self.target2normal = normalize(self.target2normal);
1954 if(vlen(self.target3normal))
1955 self.target3normal = normalize(self.target3normal);
1957 if(vlen(self.target4normal))
1958 self.target4normal = normalize(self.target4normal);
1960 self.blocked = generic_plat_blocked;
1961 if(self.dmg & (!self.message))
1962 self.message = " was squished";
1963 if(self.dmg && (!self.message2))
1964 self.message2 = "was squished by";
1965 if(self.dmg && (!self.dmgtime))
1966 self.dmgtime = 0.25;
1967 self.dmgtime2 = time;
1969 if(self.netname == "")
1970 self.netname = "1 0 0 0 1";
1972 if not(InitMovingBrushTrigger())
1975 // wait for targets to spawn
1976 self.nextthink = self.ltime + 999999999;
1977 self.think = SUB_Null;
1979 // Savage: Reduce bandwith, critical on e.g. nexdm02
1980 self.effects |= EF_LOWPRECISION;
1982 self.active = ACTIVE_ACTIVE;
1984 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);