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, CH_TRIGGER_SINGLE, 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, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
79 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
81 SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
86 sound (self, CH_TRIGGER_SINGLE, 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)
143 // when in other states, then the plat_crush event came delayed after
144 // plat state already had changed
145 // this isn't a bug per se!
153 objerror ("plat_use: not in up state");
157 .string sound1, sound2;
163 setorigin (self, self.pos1);
169 setorigin (self, self.pos2);
171 self.use = plat_trigger_use;
175 void spawnfunc_path_corner() { };
176 void spawnfunc_func_plat()
183 if (self.sounds == 0)
186 if(self.spawnflags & 4)
189 if(self.dmg && (!self.message))
190 self.message = "was squished";
191 if(self.dmg && (!self.message2))
192 self.message2 = "was squished by";
194 if (self.sounds == 1)
196 precache_sound ("plats/plat1.wav");
197 precache_sound ("plats/plat2.wav");
198 self.noise = "plats/plat1.wav";
199 self.noise1 = "plats/plat2.wav";
202 if (self.sounds == 2)
204 precache_sound ("plats/medplat1.wav");
205 precache_sound ("plats/medplat2.wav");
206 self.noise = "plats/medplat1.wav";
207 self.noise1 = "plats/medplat2.wav";
212 precache_sound (self.sound1);
213 self.noise = self.sound1;
217 precache_sound (self.sound2);
218 self.noise1 = self.sound2;
221 self.mangle = self.angles;
222 self.angles = '0 0 0';
224 self.classname = "plat";
225 if not(InitMovingBrushTrigger())
227 self.effects |= EF_LOWPRECISION;
228 setsize (self, self.mins , self.maxs);
230 self.blocked = plat_crush;
235 self.pos1 = self.origin;
236 self.pos2 = self.origin;
237 self.pos2_z = self.origin_z - self.size_z + 8;
239 plat_spawn_inside_trigger (); // the "start moving" trigger
241 self.reset = plat_reset;
250 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
258 self.think = train_next;
259 self.nextthink = self.ltime + self.wait;
273 targ = find(world, targetname, self.target);
275 self.target = targ.target;
277 objerror("train_next: no next target");
278 self.wait = targ.wait;
283 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
285 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
288 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
291 void func_train_find()
294 targ = find(world, targetname, self.target);
295 self.target = targ.target;
297 objerror("func_train_find: no next target");
298 setorigin(self, targ.origin - self.mins);
299 self.nextthink = self.ltime + 1;
300 self.think = train_next;
303 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
304 Ridable platform, targets spawnfunc_path_corner path to follow.
305 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
306 target : targetname of first spawnfunc_path_corner (starts here)
308 void spawnfunc_func_train()
310 if (self.noise != "")
311 precache_sound(self.noise);
314 objerror("func_train without a target");
318 if not(InitMovingBrushTrigger())
320 self.effects |= EF_LOWPRECISION;
322 // wait for targets to spawn
323 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
325 self.blocked = generic_plat_blocked;
326 if(self.dmg & (!self.message))
327 self.message = " was squished";
328 if(self.dmg && (!self.message2))
329 self.message2 = "was squished by";
330 if(self.dmg && (!self.dmgtime))
332 self.dmgtime2 = time;
334 // TODO make a reset function for this one
337 void func_rotating_setactive(float astate)
340 if (astate == ACTIVE_TOGGLE)
342 if(self.active == ACTIVE_ACTIVE)
343 self.active = ACTIVE_NOT;
345 self.active = ACTIVE_ACTIVE;
348 self.active = astate;
350 if(self.active == ACTIVE_NOT)
351 self.avelocity = '0 0 0';
353 self.avelocity = self.pos1;
356 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
357 Brush model that spins in place on one axis (default Z).
358 speed : speed to rotate (in degrees per second)
359 noise : path/name of looping .wav file to play.
360 dmg : Do this mutch dmg every .dmgtime intervall when blocked
364 void spawnfunc_func_rotating()
366 if (self.noise != "")
368 precache_sound(self.noise);
369 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
372 self.active = ACTIVE_ACTIVE;
373 self.setactive = func_rotating_setactive;
377 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
378 if (self.spawnflags & 4) // X (untested)
379 self.avelocity = '0 0 1' * self.speed;
380 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
381 else if (self.spawnflags & 8) // Y (untested)
382 self.avelocity = '1 0 0' * self.speed;
383 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
385 self.avelocity = '0 1 0' * self.speed;
387 self.pos1 = self.avelocity;
389 if(self.dmg & (!self.message))
390 self.message = " was squished";
391 if(self.dmg && (!self.message2))
392 self.message2 = "was squished by";
395 if(self.dmg && (!self.dmgtime))
398 self.dmgtime2 = time;
400 if not(InitMovingBrushTrigger())
402 // no EF_LOWPRECISION here, as rounding angles is bad
404 self.blocked = generic_plat_blocked;
406 // wait for targets to spawn
407 self.nextthink = self.ltime + 999999999;
408 self.think = SUB_Null;
410 // TODO make a reset function for this one
414 void func_bobbing_controller_think()
417 self.nextthink = time + 0.1;
419 if not (self.owner.active == ACTIVE_ACTIVE)
421 self.owner.velocity = '0 0 0';
425 // calculate sinewave using makevectors
426 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
427 v = self.owner.destvec + self.owner.movedir * v_forward_y;
428 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
429 // * 10 so it will arrive in 0.1 sec
430 self.owner.velocity = (v - self.owner.origin) * 10;
433 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
434 Brush model that moves back and forth on one axis (default Z).
435 speed : how long one cycle takes in seconds (default 4)
436 height : how far the cycle moves (default 32)
437 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
438 noise : path/name of looping .wav file to play.
439 dmg : Do this mutch dmg every .dmgtime intervall when blocked
442 void spawnfunc_func_bobbing()
444 local entity controller;
445 if (self.noise != "")
447 precache_sound(self.noise);
448 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
454 // center of bobbing motion
455 self.destvec = self.origin;
456 // time scale to get degrees
457 self.cnt = 360 / self.speed;
459 self.active = ACTIVE_ACTIVE;
461 // damage when blocked
462 self.blocked = generic_plat_blocked;
463 if(self.dmg & (!self.message))
464 self.message = " was squished";
465 if(self.dmg && (!self.message2))
466 self.message2 = "was squished by";
467 if(self.dmg && (!self.dmgtime))
469 self.dmgtime2 = time;
472 if (self.spawnflags & 1) // X
473 self.movedir = '1 0 0' * self.height;
474 else if (self.spawnflags & 2) // Y
475 self.movedir = '0 1 0' * self.height;
477 self.movedir = '0 0 1' * self.height;
479 if not(InitMovingBrushTrigger())
482 // wait for targets to spawn
483 controller = spawn();
484 controller.classname = "func_bobbing_controller";
485 controller.owner = self;
486 controller.nextthink = time + 1;
487 controller.think = func_bobbing_controller_think;
488 self.nextthink = self.ltime + 999999999;
489 self.think = SUB_Null;
491 // Savage: Reduce bandwith, critical on e.g. nexdm02
492 self.effects |= EF_LOWPRECISION;
494 // TODO make a reset function for this one
498 void func_pendulum_controller_think()
501 self.nextthink = time + 0.1;
503 if not (self.owner.active == ACTIVE_ACTIVE)
505 self.owner.avelocity_x = 0;
509 // calculate sinewave using makevectors
510 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
511 v = self.owner.speed * v_forward_y + self.cnt;
512 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
514 // * 10 so it will arrive in 0.1 sec
515 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
519 void spawnfunc_func_pendulum()
521 local entity controller;
522 if (self.noise != "")
524 precache_sound(self.noise);
525 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
528 self.active = ACTIVE_ACTIVE;
530 // keys: angle, speed, phase, noise, freq
534 // not initializing self.dmg to 2, to allow damageless pendulum
536 if(self.dmg & (!self.message))
537 self.message = " was squished";
538 if(self.dmg && (!self.message2))
539 self.message2 = "was squished by";
540 if(self.dmg && (!self.dmgtime))
542 self.dmgtime2 = time;
544 self.blocked = generic_plat_blocked;
546 self.avelocity_z = 0.0000001;
547 if not(InitMovingBrushTrigger())
552 // find pendulum length (same formula as Q3A)
553 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
556 // copy initial angle
557 self.cnt = self.angles_z;
559 // wait for targets to spawn
560 controller = spawn();
561 controller.classname = "func_pendulum_controller";
562 controller.owner = self;
563 controller.nextthink = time + 1;
564 controller.think = func_pendulum_controller_think;
565 self.nextthink = self.ltime + 999999999;
566 self.think = SUB_Null;
568 //self.effects |= EF_LOWPRECISION;
570 // TODO make a reset function for this one
573 // button and multiple button
576 void() button_return;
580 self.state = STATE_TOP;
581 self.nextthink = self.ltime + self.wait;
582 self.think = button_return;
583 activator = self.enemy;
585 self.frame = 1; // use alternate textures
590 self.state = STATE_BOTTOM;
595 self.state = STATE_DOWN;
596 SUB_CalcMove (self.pos1, self.speed, button_done);
597 self.frame = 0; // use normal textures
599 self.takedamage = DAMAGE_YES; // can be shot again
603 void button_blocked()
605 // do nothing, just don't come all the way back out
611 self.health = self.max_health;
612 self.takedamage = DAMAGE_NO; // will be reset upon return
614 if (self.state == STATE_UP || self.state == STATE_TOP)
617 if (self.noise != "")
618 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
620 self.state = STATE_UP;
621 SUB_CalcMove (self.pos2, self.speed, button_wait);
626 self.health = self.max_health;
627 setorigin(self, self.pos1);
628 self.frame = 0; // use normal textures
629 self.state = STATE_BOTTOM;
631 self.takedamage = DAMAGE_YES; // can be shot again
636 // if (activator.classname != "player")
638 // dprint(activator.classname);
639 // dprint(" triggered a button\n");
642 if not (self.active == ACTIVE_ACTIVE)
645 self.enemy = activator;
651 // if (activator.classname != "player")
653 // dprint(activator.classname);
654 // dprint(" touched a button\n");
658 if not(other.iscreature)
660 if(other.velocity * self.movedir < 0)
664 self.enemy = other.owner;
668 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
670 if(self.spawnflags & DOOR_NOSPLASH)
671 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
673 self.health = self.health - damage;
674 if (self.health <= 0)
676 // if (activator.classname != "player")
678 // dprint(activator.classname);
679 // dprint(" killed a button\n");
681 self.enemy = damage_attacker;
687 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
688 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.
690 "angle" determines the opening direction
691 "target" all entities with a matching targetname will be used
692 "speed" override the default 40 speed
693 "wait" override the default 1 second wait (-1 = never return)
694 "lip" override the default 4 pixel lip remaining at end of move
695 "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
702 void spawnfunc_func_button()
706 if not(InitMovingBrushTrigger())
708 self.effects |= EF_LOWPRECISION;
710 self.blocked = button_blocked;
711 self.use = button_use;
713 // if (self.health == 0) // all buttons are now shootable
717 self.max_health = self.health;
718 self.event_damage = button_damage;
719 self.takedamage = DAMAGE_YES;
722 self.touch = button_touch;
732 precache_sound(self.noise);
734 self.active = ACTIVE_ACTIVE;
736 self.pos1 = self.origin;
737 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
738 self.flags |= FL_NOTARGET;
744 float DOOR_START_OPEN = 1;
745 float DOOR_DONT_LINK = 4;
746 float DOOR_TOGGLE = 32;
750 Doors are similar to buttons, but can spawn a fat trigger field around them
751 to open without a touch, and they link together to form simultanious
754 Door.owner is the master door. If there is only one door, it points to itself.
755 If multiple doors, all will point to a single one.
757 Door.enemy chains from the master door through all doors linked in the chain.
762 =============================================================================
766 =============================================================================
771 void() door_rotating_go_down;
772 void() door_rotating_go_up;
777 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
778 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
781 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
782 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
784 //Dont chamge direction for dead or dying stuff
785 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
788 if (self.state == STATE_DOWN)
789 if (self.classname == "door")
794 door_rotating_go_up ();
797 if (self.classname == "door")
802 door_rotating_go_down ();
806 //gib dying stuff just to make sure
807 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
808 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
812 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
813 // if a door has a negative wait, it would never come back if blocked,
814 // so let it just squash the object to death real fast
815 /* if (self.wait >= 0)
817 if (self.state == STATE_DOWN)
828 if (self.noise1 != "")
829 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
830 self.state = STATE_TOP;
831 if (self.spawnflags & DOOR_TOGGLE)
832 return; // don't come down automatically
833 if (self.classname == "door")
835 self.think = door_go_down;
838 self.think = door_rotating_go_down;
840 self.nextthink = self.ltime + self.wait;
843 void door_hit_bottom()
845 if (self.noise1 != "")
846 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
847 self.state = STATE_BOTTOM;
852 if (self.noise2 != "")
853 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
856 self.takedamage = DAMAGE_YES;
857 self.health = self.max_health;
860 self.state = STATE_DOWN;
861 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
866 if (self.state == STATE_UP)
867 return; // already going up
869 if (self.state == STATE_TOP)
870 { // reset top wait time
871 self.nextthink = self.ltime + self.wait;
875 if (self.noise2 != "")
876 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
877 self.state = STATE_UP;
878 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
881 oldmessage = self.message;
884 self.message = oldmessage;
889 =============================================================================
893 =============================================================================
901 if (self.owner != self)
902 objerror ("door_fire: self.owner != self");
906 if (self.spawnflags & DOOR_TOGGLE)
908 if (self.state == STATE_UP || self.state == STATE_TOP)
913 if (self.classname == "door")
919 door_rotating_go_down ();
922 } while ( (self != starte) && (self != world) );
928 // trigger all paired doors
932 if (self.classname == "door")
937 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
938 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
940 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
941 self.pos2 = '0 0 0' - self.pos2;
943 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
944 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
945 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
947 door_rotating_go_up ();
951 } while ( (self != starte) && (self != world) );
960 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
971 void door_trigger_touch()
973 if (other.health < 1)
974 if not(other.iscreature && other.deadflag == DEAD_NO)
977 if (time < self.attack_finished_single)
979 self.attack_finished_single = time + 1;
988 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
991 if(self.spawnflags & DOOR_NOSPLASH)
992 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
994 self.health = self.health - damage;
995 if (self.health <= 0)
999 self.health = self.max_health;
1000 self.takedamage = DAMAGE_NO; // wil be reset upon return
1016 if(other.classname != "player")
1018 if (self.owner.attack_finished_single > time)
1021 self.owner.attack_finished_single = time + 2;
1023 if (!(self.owner.dmg) && (self.owner.message != ""))
1025 if (other.flags & FL_CLIENT)
1026 centerprint (other, self.owner.message);
1027 play2(other, "misc/talk.wav");
1032 void door_generic_plat_blocked()
1035 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1036 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1039 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1040 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1042 //Dont chamge direction for dead or dying stuff
1043 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1046 if (self.state == STATE_DOWN)
1047 door_rotating_go_up ();
1049 door_rotating_go_down ();
1052 //gib dying stuff just to make sure
1053 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1054 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1058 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1059 // if a door has a negative wait, it would never come back if blocked,
1060 // so let it just squash the object to death real fast
1061 /* if (self.wait >= 0)
1063 if (self.state == STATE_DOWN)
1064 door_rotating_go_up ();
1066 door_rotating_go_down ();
1072 void door_rotating_hit_top()
1074 if (self.noise1 != "")
1075 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1076 self.state = STATE_TOP;
1077 if (self.spawnflags & DOOR_TOGGLE)
1078 return; // don't come down automatically
1079 self.think = door_rotating_go_down;
1080 self.nextthink = self.ltime + self.wait;
1083 void door_rotating_hit_bottom()
1085 if (self.noise1 != "")
1086 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1087 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1089 self.pos2 = '0 0 0' - self.pos2;
1092 self.state = STATE_BOTTOM;
1095 void door_rotating_go_down()
1097 if (self.noise2 != "")
1098 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1099 if (self.max_health)
1101 self.takedamage = DAMAGE_YES;
1102 self.health = self.max_health;
1105 self.state = STATE_DOWN;
1106 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1109 void door_rotating_go_up()
1111 if (self.state == STATE_UP)
1112 return; // already going up
1114 if (self.state == STATE_TOP)
1115 { // reset top wait time
1116 self.nextthink = self.ltime + self.wait;
1119 if (self.noise2 != "")
1120 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1121 self.state = STATE_UP;
1122 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1125 oldmessage = self.message;
1128 self.message = oldmessage;
1135 =============================================================================
1139 =============================================================================
1143 entity spawn_field(vector fmins, vector fmaxs)
1145 local entity trigger;
1146 local vector t1, t2;
1149 trigger.classname = "doortriggerfield";
1150 trigger.movetype = MOVETYPE_NONE;
1151 trigger.solid = SOLID_TRIGGER;
1152 trigger.owner = self;
1153 trigger.touch = door_trigger_touch;
1157 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1162 float EntitiesTouching(entity e1, entity e2)
1164 if (e1.absmin_x > e2.absmax_x)
1166 if (e1.absmin_y > e2.absmax_y)
1168 if (e1.absmin_z > e2.absmax_z)
1170 if (e1.absmax_x < e2.absmin_x)
1172 if (e1.absmax_y < e2.absmin_y)
1174 if (e1.absmax_z < e2.absmin_z)
1189 local entity t, starte;
1190 local vector cmins, cmaxs;
1193 return; // already linked by another door
1194 if (self.spawnflags & 4)
1196 self.owner = self.enemy = self;
1204 self.trigger_field = spawn_field(self.absmin, self.absmax);
1206 return; // don't want to link this door
1209 cmins = self.absmin;
1210 cmaxs = self.absmax;
1217 self.owner = starte; // master door
1220 starte.health = self.health;
1222 starte.targetname = self.targetname;
1223 if (self.message != "")
1224 starte.message = self.message;
1226 t = find(t, classname, self.classname);
1229 self.enemy = starte; // make the chain a loop
1231 // shootable, or triggered doors just needed the owner/enemy links,
1232 // they don't spawn a field
1243 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1248 if (EntitiesTouching(self,t))
1251 objerror ("cross connected doors");
1256 if (t.absmin_x < cmins_x)
1257 cmins_x = t.absmin_x;
1258 if (t.absmin_y < cmins_y)
1259 cmins_y = t.absmin_y;
1260 if (t.absmin_z < cmins_z)
1261 cmins_z = t.absmin_z;
1262 if (t.absmax_x > cmaxs_x)
1263 cmaxs_x = t.absmax_x;
1264 if (t.absmax_y > cmaxs_y)
1265 cmaxs_y = t.absmax_y;
1266 if (t.absmax_z > cmaxs_z)
1267 cmaxs_z = t.absmax_z;
1274 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1275 if two doors touch, they are assumed to be connected and operate as a unit.
1277 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1279 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).
1281 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1282 "angle" determines the opening direction
1283 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1284 "health" if set, door must be shot open
1285 "speed" movement speed (100 default)
1286 "wait" wait before returning (3 default, -1 = never return)
1287 "lip" lip remaining at end of move (8 default)
1288 "dmg" damage to inflict when blocked (2 default)
1295 FIXME: only one sound set available at the time being
1299 void door_init_startopen()
1301 setorigin (self, self.pos2);
1302 self.pos2 = self.pos1;
1303 self.pos1 = self.origin;
1308 setorigin(self, self.pos1);
1309 self.velocity = '0 0 0';
1310 self.state = STATE_BOTTOM;
1311 self.think = SUB_Null;
1314 void spawnfunc_func_door()
1316 //if (!self.deathtype) // map makers can override this
1317 // self.deathtype = " got in the way";
1320 self.max_health = self.health;
1321 if not(InitMovingBrushTrigger())
1323 self.effects |= EF_LOWPRECISION;
1324 self.classname = "door";
1326 self.blocked = door_blocked;
1327 self.use = door_use;
1329 if(self.spawnflags & 8)
1332 if(self.dmg && (!self.message))
1333 self.message = "was squished";
1334 if(self.dmg && (!self.message2))
1335 self.message2 = "was squished by";
1337 if (self.sounds > 0)
1339 precache_sound ("plats/medplat1.wav");
1340 precache_sound ("plats/medplat2.wav");
1341 self.noise2 = "plats/medplat1.wav";
1342 self.noise1 = "plats/medplat2.wav";
1352 self.pos1 = self.origin;
1353 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1355 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1356 // but spawn in the open position
1357 if (self.spawnflags & DOOR_START_OPEN)
1358 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1360 self.state = STATE_BOTTOM;
1364 self.takedamage = DAMAGE_YES;
1365 self.event_damage = door_damage;
1371 self.touch = door_touch;
1373 // LinkDoors can't be done until all of the doors have been spawned, so
1374 // the sizes can be detected properly.
1375 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1377 self.reset = door_reset;
1380 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1381 if two doors touch, they are assumed to be connected and operate as a unit.
1383 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1385 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1386 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1387 must have set trigger_reverse to 1.
1388 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1390 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).
1392 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1393 "angle" determines the destination angle for opening. negative values reverse the direction.
1394 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1395 "health" if set, door must be shot open
1396 "speed" movement speed (100 default)
1397 "wait" wait before returning (3 default, -1 = never return)
1398 "dmg" damage to inflict when blocked (2 default)
1405 FIXME: only one sound set available at the time being
1408 void door_rotating_reset()
1410 self.angles = self.pos1;
1411 self.avelocity = '0 0 0';
1412 self.state = STATE_BOTTOM;
1413 self.think = SUB_Null;
1416 void door_rotating_init_startopen()
1418 self.angles = self.movedir;
1419 self.pos2 = '0 0 0';
1420 self.pos1 = self.movedir;
1424 void spawnfunc_func_door_rotating()
1427 //if (!self.deathtype) // map makers can override this
1428 // self.deathtype = " got in the way";
1430 // I abuse "movedir" for denoting the axis for now
1431 if (self.spawnflags & 64) // X (untested)
1432 self.movedir = '0 0 1';
1433 else if (self.spawnflags & 128) // Y (untested)
1434 self.movedir = '1 0 0';
1436 self.movedir = '0 1 0';
1438 if (self.angles_y==0) self.angles_y = 90;
1440 self.movedir = self.movedir * self.angles_y;
1441 self.angles = '0 0 0';
1443 self.max_health = self.health;
1444 self.avelocity = self.movedir;
1445 if not(InitMovingBrushTrigger())
1447 self.velocity = '0 0 0';
1448 //self.effects |= EF_LOWPRECISION;
1449 self.classname = "door_rotating";
1451 self.blocked = door_blocked;
1452 self.use = door_use;
1454 if(self.spawnflags & 8)
1457 if(self.dmg && (!self.message))
1458 self.message = "was squished";
1459 if(self.dmg && (!self.message2))
1460 self.message2 = "was squished by";
1462 if (self.sounds > 0)
1464 precache_sound ("plats/medplat1.wav");
1465 precache_sound ("plats/medplat2.wav");
1466 self.noise2 = "plats/medplat1.wav";
1467 self.noise1 = "plats/medplat2.wav";
1474 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1476 self.pos1 = '0 0 0';
1477 self.pos2 = self.movedir;
1479 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1480 // but spawn in the open position
1481 if (self.spawnflags & DOOR_START_OPEN)
1482 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1484 self.state = STATE_BOTTOM;
1488 self.takedamage = DAMAGE_YES;
1489 self.event_damage = door_damage;
1495 self.touch = door_touch;
1497 // LinkDoors can't be done until all of the doors have been spawned, so
1498 // the sizes can be detected properly.
1499 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1501 self.reset = door_rotating_reset;
1505 =============================================================================
1509 =============================================================================
1512 void() fd_secret_move1;
1513 void() fd_secret_move2;
1514 void() fd_secret_move3;
1515 void() fd_secret_move4;
1516 void() fd_secret_move5;
1517 void() fd_secret_move6;
1518 void() fd_secret_done;
1520 float SECRET_OPEN_ONCE = 1; // stays open
1521 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1522 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1523 float SECRET_NO_SHOOT = 8; // only opened by trigger
1524 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1527 void fd_secret_use()
1530 string message_save;
1532 self.health = 10000;
1533 self.bot_attack = TRUE;
1535 // exit if still moving around...
1536 if (self.origin != self.oldorigin)
1539 message_save = self.message;
1540 self.message = ""; // no more message
1541 SUB_UseTargets(); // fire all targets / killtargets
1542 self.message = message_save;
1544 self.velocity = '0 0 0';
1546 // Make a sound, wait a little...
1548 if (self.noise1 != "")
1549 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1550 self.nextthink = self.ltime + 0.1;
1552 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1553 makevectors(self.mangle);
1557 if (self.spawnflags & SECRET_1ST_DOWN)
1558 self.t_width = fabs(v_up * self.size);
1560 self.t_width = fabs(v_right * self.size);
1564 self.t_length = fabs(v_forward * self.size);
1566 if (self.spawnflags & SECRET_1ST_DOWN)
1567 self.dest1 = self.origin - v_up * self.t_width;
1569 self.dest1 = self.origin + v_right * (self.t_width * temp);
1571 self.dest2 = self.dest1 + v_forward * self.t_length;
1572 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1573 if (self.noise2 != "")
1574 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1577 // Wait after first movement...
1578 void fd_secret_move1()
1580 self.nextthink = self.ltime + 1.0;
1581 self.think = fd_secret_move2;
1582 if (self.noise3 != "")
1583 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1586 // Start moving sideways w/sound...
1587 void fd_secret_move2()
1589 if (self.noise2 != "")
1590 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1591 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1594 // Wait here until time to go back...
1595 void fd_secret_move3()
1597 if (self.noise3 != "")
1598 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1599 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1601 self.nextthink = self.ltime + self.wait;
1602 self.think = fd_secret_move4;
1607 void fd_secret_move4()
1609 if (self.noise2 != "")
1610 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1611 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1615 void fd_secret_move5()
1617 self.nextthink = self.ltime + 1.0;
1618 self.think = fd_secret_move6;
1619 if (self.noise3 != "")
1620 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1623 void fd_secret_move6()
1625 if (self.noise2 != "")
1626 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1627 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1630 void fd_secret_done()
1632 if (self.spawnflags&SECRET_YES_SHOOT)
1634 self.health = 10000;
1635 self.takedamage = DAMAGE_YES;
1636 //self.th_pain = fd_secret_use;
1638 if (self.noise3 != "")
1639 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1642 void secret_blocked()
1644 if (time < self.attack_finished_single)
1646 self.attack_finished_single = time + 0.5;
1647 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1659 if not(other.iscreature)
1661 if (self.attack_finished_single > time)
1664 self.attack_finished_single = time + 2;
1668 if (other.flags & FL_CLIENT)
1669 centerprint (other, self.message);
1670 play2(other, "misc/talk.wav");
1676 if (self.spawnflags&SECRET_YES_SHOOT)
1678 self.health = 10000;
1679 self.takedamage = DAMAGE_YES;
1681 setorigin(self, self.oldorigin);
1682 self.think = SUB_Null;
1685 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1686 Basic secret door. Slides back, then to the side. Angle determines direction.
1687 wait = # of seconds before coming back
1688 1st_left = 1st move is left of arrow
1689 1st_down = 1st move is down from arrow
1690 always_shoot = even if targeted, keep shootable
1691 t_width = override WIDTH to move back (or height if going down)
1692 t_length = override LENGTH to move sideways
1693 "dmg" damage to inflict when blocked (2 default)
1695 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1702 void spawnfunc_func_door_secret()
1704 /*if (!self.deathtype) // map makers can override this
1705 self.deathtype = " got in the way";*/
1711 self.mangle = self.angles;
1712 self.angles = '0 0 0';
1713 self.classname = "door";
1714 if not(InitMovingBrushTrigger())
1716 self.effects |= EF_LOWPRECISION;
1718 self.touch = secret_touch;
1719 self.blocked = secret_blocked;
1721 self.use = fd_secret_use;
1726 self.spawnflags |= SECRET_YES_SHOOT;
1728 if(self.spawnflags&SECRET_YES_SHOOT)
1730 self.health = 10000;
1731 self.takedamage = DAMAGE_YES;
1732 self.event_damage = fd_secret_use;
1734 self.oldorigin = self.origin;
1736 self.wait = 5; // 5 seconds before closing
1738 self.reset = secret_reset;
1742 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1743 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1744 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
1745 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1746 height: amplitude modifier (default 32)
1747 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1748 noise: path/name of looping .wav file to play.
1749 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1753 void func_fourier_controller_think()
1758 self.nextthink = time + 0.1;
1759 if not (self.owner.active == ACTIVE_ACTIVE)
1761 self.owner.velocity = '0 0 0';
1766 n = floor((tokenize_console(self.owner.netname)) / 5);
1767 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1769 v = self.owner.destvec;
1771 for(i = 0; i < n; ++i)
1773 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1774 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;
1777 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1778 // * 10 so it will arrive in 0.1 sec
1779 self.owner.velocity = (v - self.owner.origin) * 10;
1782 void spawnfunc_func_fourier()
1784 local entity controller;
1785 if (self.noise != "")
1787 precache_sound(self.noise);
1788 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1795 self.destvec = self.origin;
1796 self.cnt = 360 / self.speed;
1798 self.blocked = generic_plat_blocked;
1799 if(self.dmg & (!self.message))
1800 self.message = " was squished";
1801 if(self.dmg && (!self.message2))
1802 self.message2 = "was squished by";
1803 if(self.dmg && (!self.dmgtime))
1804 self.dmgtime = 0.25;
1805 self.dmgtime2 = time;
1807 if(self.netname == "")
1808 self.netname = "1 0 0 0 1";
1810 if not(InitMovingBrushTrigger())
1813 self.active = ACTIVE_ACTIVE;
1815 // wait for targets to spawn
1816 controller = spawn();
1817 controller.classname = "func_fourier_controller";
1818 controller.owner = self;
1819 controller.nextthink = time + 1;
1820 controller.think = func_fourier_controller_think;
1821 self.nextthink = self.ltime + 999999999;
1822 self.think = SUB_Null;
1824 // Savage: Reduce bandwith, critical on e.g. nexdm02
1825 self.effects |= EF_LOWPRECISION;
1827 // TODO make a reset function for this one
1830 // reusing some fields havocbots declared
1831 .entity wp00, wp01, wp02, wp03;
1833 .float targetfactor, target2factor, target3factor, target4factor;
1834 .vector targetnormal, target2normal, target3normal, target4normal;
1836 vector func_vectormamamam_origin(entity o, float t)
1848 p = e.origin + t * e.velocity;
1850 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1852 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1858 p = e.origin + t * e.velocity;
1860 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1862 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1868 p = e.origin + t * e.velocity;
1870 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1872 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1878 p = e.origin + t * e.velocity;
1880 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1882 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1888 void func_vectormamamam_controller_think()
1890 self.nextthink = time + 0.1;
1892 if not (self.owner.active == ACTIVE_ACTIVE)
1894 self.owner.velocity = '0 0 0';
1898 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1899 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1902 void func_vectormamamam_findtarget()
1904 if(self.target != "")
1905 self.wp00 = find(world, targetname, self.target);
1907 if(self.target2 != "")
1908 self.wp01 = find(world, targetname, self.target2);
1910 if(self.target3 != "")
1911 self.wp02 = find(world, targetname, self.target3);
1913 if(self.target4 != "")
1914 self.wp03 = find(world, targetname, self.target4);
1916 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1917 objerror("No reference entity found, so there is nothing to move. Aborting.");
1919 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1921 local entity controller;
1922 controller = spawn();
1923 controller.classname = "func_vectormamamam_controller";
1924 controller.owner = self;
1925 controller.nextthink = time + 1;
1926 controller.think = func_vectormamamam_controller_think;
1929 void spawnfunc_func_vectormamamam()
1931 if (self.noise != "")
1933 precache_sound(self.noise);
1934 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1937 if(!self.targetfactor)
1938 self.targetfactor = 1;
1940 if(!self.target2factor)
1941 self.target2factor = 1;
1943 if(!self.target3factor)
1944 self.target3factor = 1;
1946 if(!self.target4factor)
1947 self.target4factor = 1;
1949 if(vlen(self.targetnormal))
1950 self.targetnormal = normalize(self.targetnormal);
1952 if(vlen(self.target2normal))
1953 self.target2normal = normalize(self.target2normal);
1955 if(vlen(self.target3normal))
1956 self.target3normal = normalize(self.target3normal);
1958 if(vlen(self.target4normal))
1959 self.target4normal = normalize(self.target4normal);
1961 self.blocked = generic_plat_blocked;
1962 if(self.dmg & (!self.message))
1963 self.message = " was squished";
1964 if(self.dmg && (!self.message2))
1965 self.message2 = "was squished by";
1966 if(self.dmg && (!self.dmgtime))
1967 self.dmgtime = 0.25;
1968 self.dmgtime2 = time;
1970 if(self.netname == "")
1971 self.netname = "1 0 0 0 1";
1973 if not(InitMovingBrushTrigger())
1976 // wait for targets to spawn
1977 self.nextthink = self.ltime + 999999999;
1978 self.think = SUB_Null;
1980 // Savage: Reduce bandwith, critical on e.g. nexdm02
1981 self.effects |= EF_LOWPRECISION;
1983 self.active = ACTIVE_ACTIVE;
1985 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);