2 void generic_plat_blocked()
4 if(self.dmg && other.takedamage != DAMAGE_NO) {
5 if(self.dmgtime2 < time) {
6 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
7 self.dmgtime2 = time + self.dmgtime;
10 // Gib dead/dying stuff
11 if(other.deadflag != DEAD_NO)
12 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
18 float STATE_BOTTOM = 1;
22 .entity trigger_field;
24 void() plat_center_touch;
25 void() plat_outside_touch;
26 void() plat_trigger_use;
30 float PLAT_LOW_TRIGGER = 1;
32 void plat_spawn_inside_trigger()
35 local vector tmin, tmax;
38 trigger.touch = plat_center_touch;
39 trigger.movetype = MOVETYPE_NONE;
40 trigger.solid = SOLID_TRIGGER;
43 tmin = self.absmin + '25 25 0';
44 tmax = self.absmax - '25 25 -8';
45 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
46 if (self.spawnflags & PLAT_LOW_TRIGGER)
49 if (self.size_x <= 50)
51 tmin_x = (self.mins_x + self.maxs_x) / 2;
54 if (self.size_y <= 50)
56 tmin_y = (self.mins_y + self.maxs_y) / 2;
60 setsize (trigger, tmin, tmax);
65 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
67 self.think = plat_go_down;
68 self.nextthink = self.ltime + 3;
71 void plat_hit_bottom()
73 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
79 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
81 SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
86 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
88 SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
91 void plat_center_touch()
93 if not(other.iscreature)
96 if (other.health <= 0)
102 else if (self.state == 1)
103 self.nextthink = self.ltime + 1; // delay going down
106 void plat_outside_touch()
108 if not(other.iscreature)
111 if (other.health <= 0)
119 void plat_trigger_use()
122 return; // already activated
129 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
130 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
132 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
133 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
134 // Gib dead/dying stuff
135 if(other.deadflag != DEAD_NO)
136 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
141 else if (self.state == 3)
144 objerror ("plat_crush: bad self.state\n");
152 objerror ("plat_use: not in up state");
156 .string sound1, sound2;
162 setorigin (self, self.pos1);
168 setorigin (self, self.pos2);
170 self.use = plat_trigger_use;
174 void spawnfunc_path_corner() { };
175 void spawnfunc_func_plat()
182 if (self.sounds == 0)
185 if(self.spawnflags & 4)
188 if(self.dmg && (!self.message))
189 self.message = "was squished";
190 if(self.dmg && (!self.message2))
191 self.message2 = "was squished by";
193 if (self.sounds == 1)
195 precache_sound ("plats/plat1.wav");
196 precache_sound ("plats/plat2.wav");
197 self.noise = "plats/plat1.wav";
198 self.noise1 = "plats/plat2.wav";
201 if (self.sounds == 2)
203 precache_sound ("plats/medplat1.wav");
204 precache_sound ("plats/medplat2.wav");
205 self.noise = "plats/medplat1.wav";
206 self.noise1 = "plats/medplat2.wav";
211 precache_sound (self.sound1);
212 self.noise = self.sound1;
216 precache_sound (self.sound2);
217 self.noise1 = self.sound2;
220 self.mangle = self.angles;
221 self.angles = '0 0 0';
223 self.classname = "plat";
224 if not(InitMovingBrushTrigger())
226 self.effects |= EF_LOWPRECISION;
227 setsize (self, self.mins , self.maxs);
229 self.blocked = plat_crush;
234 self.pos1 = self.origin;
235 self.pos2 = self.origin;
236 self.pos2_z = self.origin_z - self.size_z + 8;
238 plat_spawn_inside_trigger (); // the "start moving" trigger
240 self.reset = plat_reset;
248 self.think = train_next;
249 self.nextthink = self.ltime + self.wait;
252 stopsoundto(MSG_BROADCAST, self, CHAN_TRIGGER); // send this as unreliable only, as the train will resume operation shortly anyway
258 targ = find(world, targetname, self.target);
259 self.target = targ.target;
261 objerror("train_next: no next target");
262 self.wait = targ.wait;
268 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_next);
270 SUB_CalcMove(targ.origin - self.mins, self.speed, train_next);
275 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
277 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
281 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
284 void func_train_find()
287 targ = find(world, targetname, self.target);
288 self.target = targ.target;
290 objerror("func_train_find: no next target");
291 setorigin(self, targ.origin - self.mins);
292 self.nextthink = self.ltime + 1;
293 self.think = train_next;
296 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
297 Ridable platform, targets spawnfunc_path_corner path to follow.
298 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
299 target : targetname of first spawnfunc_path_corner (starts here)
301 void spawnfunc_func_train()
303 if (self.noise != "")
304 precache_sound(self.noise);
307 objerror("func_train without a target");
311 if not(InitMovingBrushTrigger())
313 self.effects |= EF_LOWPRECISION;
315 // wait for targets to spawn
316 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
318 self.blocked = generic_plat_blocked;
319 if(self.dmg & (!self.message))
320 self.message = " was squished";
321 if(self.dmg && (!self.message2))
322 self.message2 = "was squished by";
323 if(self.dmg && (!self.dmgtime))
325 self.dmgtime2 = time;
327 // TODO make a reset function for this one
330 void func_rotating_setactive(float astate)
333 if (astate == ACTIVE_TOGGLE)
335 if(self.active == ACTIVE_ACTIVE)
336 self.active = ACTIVE_NOT;
338 self.active = ACTIVE_ACTIVE;
341 self.active = astate;
343 if(self.active == ACTIVE_NOT)
344 self.avelocity = '0 0 0';
346 self.avelocity = self.pos1;
349 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
350 Brush model that spins in place on one axis (default Z).
351 speed : speed to rotate (in degrees per second)
352 noise : path/name of looping .wav file to play.
353 dmg : Do this mutch dmg every .dmgtime intervall when blocked
357 void spawnfunc_func_rotating()
359 if (self.noise != "")
361 precache_sound(self.noise);
362 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
365 self.active = ACTIVE_ACTIVE;
366 self.setactive = func_rotating_setactive;
370 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
371 if (self.spawnflags & 4) // X (untested)
372 self.avelocity = '0 0 1' * self.speed;
373 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
374 else if (self.spawnflags & 8) // Y (untested)
375 self.avelocity = '1 0 0' * self.speed;
376 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
378 self.avelocity = '0 1 0' * self.speed;
380 self.pos1 = self.avelocity;
382 if(self.dmg & (!self.message))
383 self.message = " was squished";
384 if(self.dmg && (!self.message2))
385 self.message2 = "was squished by";
388 if(self.dmg && (!self.dmgtime))
391 self.dmgtime2 = time;
393 if not(InitMovingBrushTrigger())
395 // no EF_LOWPRECISION here, as rounding angles is bad
397 self.blocked = generic_plat_blocked;
399 // wait for targets to spawn
400 self.nextthink = self.ltime + 999999999;
401 self.think = SUB_Null;
403 // TODO make a reset function for this one
407 void func_bobbing_controller_think()
410 self.nextthink = time + 0.1;
412 if not (self.owner.active == ACTIVE_ACTIVE)
414 self.owner.velocity = '0 0 0';
418 // calculate sinewave using makevectors
419 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
420 v = self.owner.destvec + self.owner.movedir * v_forward_y;
421 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
422 // * 10 so it will arrive in 0.1 sec
423 self.owner.velocity = (v - self.owner.origin) * 10;
426 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
427 Brush model that moves back and forth on one axis (default Z).
428 speed : how long one cycle takes in seconds (default 4)
429 height : how far the cycle moves (default 32)
430 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
431 noise : path/name of looping .wav file to play.
432 dmg : Do this mutch dmg every .dmgtime intervall when blocked
435 void spawnfunc_func_bobbing()
437 local entity controller;
438 if (self.noise != "")
440 precache_sound(self.noise);
441 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
447 // center of bobbing motion
448 self.destvec = self.origin;
449 // time scale to get degrees
450 self.cnt = 360 / self.speed;
452 self.active = ACTIVE_ACTIVE;
454 // damage when blocked
455 self.blocked = generic_plat_blocked;
456 if(self.dmg & (!self.message))
457 self.message = " was squished";
458 if(self.dmg && (!self.message2))
459 self.message2 = "was squished by";
460 if(self.dmg && (!self.dmgtime))
462 self.dmgtime2 = time;
465 if (self.spawnflags & 1) // X
466 self.movedir = '1 0 0' * self.height;
467 else if (self.spawnflags & 2) // Y
468 self.movedir = '0 1 0' * self.height;
470 self.movedir = '0 0 1' * self.height;
472 if not(InitMovingBrushTrigger())
475 // wait for targets to spawn
476 controller = spawn();
477 controller.classname = "func_bobbing_controller";
478 controller.owner = self;
479 controller.nextthink = time + 1;
480 controller.think = func_bobbing_controller_think;
481 self.nextthink = self.ltime + 999999999;
482 self.think = SUB_Null;
484 // Savage: Reduce bandwith, critical on e.g. nexdm02
485 self.effects |= EF_LOWPRECISION;
487 // TODO make a reset function for this one
491 void func_pendulum_controller_think()
494 self.nextthink = time + 0.1;
496 if not (self.owner.active == ACTIVE_ACTIVE)
498 self.owner.avelocity_x = 0;
502 // calculate sinewave using makevectors
503 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
504 v = self.owner.speed * v_forward_y + self.cnt;
505 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
507 // * 10 so it will arrive in 0.1 sec
508 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
512 void spawnfunc_func_pendulum()
514 local entity controller;
515 if (self.noise != "")
517 precache_sound(self.noise);
518 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
521 self.active = ACTIVE_ACTIVE;
523 // keys: angle, speed, phase, noise, freq
527 // not initializing self.dmg to 2, to allow damageless pendulum
529 if(self.dmg & (!self.message))
530 self.message = " was squished";
531 if(self.dmg && (!self.message2))
532 self.message2 = "was squished by";
533 if(self.dmg && (!self.dmgtime))
535 self.dmgtime2 = time;
537 self.blocked = generic_plat_blocked;
539 if not(InitMovingBrushTrigger())
544 // find pendulum length (same formula as Q3A)
545 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
548 // copy initial angle
549 self.cnt = self.angles_z;
551 // wait for targets to spawn
552 controller = spawn();
553 controller.classname = "func_pendulum_controller";
554 controller.owner = self;
555 controller.nextthink = time + 1;
556 controller.think = func_pendulum_controller_think;
557 self.nextthink = self.ltime + 999999999;
558 self.think = SUB_Null;
560 //self.effects |= EF_LOWPRECISION;
562 // TODO make a reset function for this one
565 // button and multiple button
568 void() button_return;
572 self.state = STATE_TOP;
573 self.nextthink = self.ltime + self.wait;
574 self.think = button_return;
575 activator = self.enemy;
577 self.frame = 1; // use alternate textures
582 self.state = STATE_BOTTOM;
587 self.state = STATE_DOWN;
588 SUB_CalcMove (self.pos1, self.speed, button_done);
589 self.frame = 0; // use normal textures
591 self.takedamage = DAMAGE_YES; // can be shot again
595 void button_blocked()
597 // do nothing, just don't come all the way back out
603 self.health = self.max_health;
604 self.takedamage = DAMAGE_NO; // will be reset upon return
606 if (self.state == STATE_UP || self.state == STATE_TOP)
609 if (self.noise != "")
610 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
612 self.state = STATE_UP;
613 SUB_CalcMove (self.pos2, self.speed, button_wait);
618 self.health = self.max_health;
619 setorigin(self, self.pos1);
620 self.frame = 0; // use normal textures
621 self.state = STATE_BOTTOM;
623 self.takedamage = DAMAGE_YES; // can be shot again
628 // if (activator.classname != "player")
630 // dprint(activator.classname);
631 // dprint(" triggered a button\n");
634 if not (self.active == ACTIVE_ACTIVE)
637 self.enemy = activator;
643 // if (activator.classname != "player")
645 // dprint(activator.classname);
646 // dprint(" touched a button\n");
650 if not(other.iscreature)
652 if(other.velocity * self.movedir < 0)
656 self.enemy = other.owner;
660 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
662 if(self.spawnflags & DOOR_NOSPLASH)
663 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
665 self.health = self.health - damage;
666 if (self.health <= 0)
668 // if (activator.classname != "player")
670 // dprint(activator.classname);
671 // dprint(" killed a button\n");
673 self.enemy = damage_attacker;
679 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
680 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.
682 "angle" determines the opening direction
683 "target" all entities with a matching targetname will be used
684 "speed" override the default 40 speed
685 "wait" override the default 1 second wait (-1 = never return)
686 "lip" override the default 4 pixel lip remaining at end of move
687 "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
694 void spawnfunc_func_button()
698 if not(InitMovingBrushTrigger())
700 self.effects |= EF_LOWPRECISION;
702 self.blocked = button_blocked;
703 self.use = button_use;
705 // if (self.health == 0) // all buttons are now shootable
709 self.max_health = self.health;
710 self.event_damage = button_damage;
711 self.takedamage = DAMAGE_YES;
714 self.touch = button_touch;
724 precache_sound(self.noise);
726 self.active = ACTIVE_ACTIVE;
728 self.pos1 = self.origin;
729 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
730 self.flags |= FL_NOTARGET;
736 float DOOR_START_OPEN = 1;
737 float DOOR_DONT_LINK = 4;
738 float DOOR_TOGGLE = 32;
742 Doors are similar to buttons, but can spawn a fat trigger field around them
743 to open without a touch, and they link together to form simultanious
746 Door.owner is the master door. If there is only one door, it points to itself.
747 If multiple doors, all will point to a single one.
749 Door.enemy chains from the master door through all doors linked in the chain.
754 =============================================================================
758 =============================================================================
763 void() door_rotating_go_down;
764 void() door_rotating_go_up;
769 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
770 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
773 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
774 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
776 //Dont chamge direction for dead or dying stuff
777 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
780 if (self.state == STATE_DOWN)
781 if (self.classname == "door")
786 door_rotating_go_up ();
789 if (self.classname == "door")
794 door_rotating_go_down ();
798 //gib dying stuff just to make sure
799 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
800 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
804 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
805 // if a door has a negative wait, it would never come back if blocked,
806 // so let it just squash the object to death real fast
807 /* if (self.wait >= 0)
809 if (self.state == STATE_DOWN)
820 if (self.noise1 != "")
821 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
822 self.state = STATE_TOP;
823 if (self.spawnflags & DOOR_TOGGLE)
824 return; // don't come down automatically
825 if (self.classname == "door")
827 self.think = door_go_down;
830 self.think = door_rotating_go_down;
832 self.nextthink = self.ltime + self.wait;
835 void door_hit_bottom()
837 if (self.noise1 != "")
838 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
839 self.state = STATE_BOTTOM;
844 if (self.noise2 != "")
845 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
848 self.takedamage = DAMAGE_YES;
849 self.health = self.max_health;
852 self.state = STATE_DOWN;
853 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
858 if (self.state == STATE_UP)
859 return; // already going up
861 if (self.state == STATE_TOP)
862 { // reset top wait time
863 self.nextthink = self.ltime + self.wait;
867 if (self.noise2 != "")
868 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
869 self.state = STATE_UP;
870 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
873 oldmessage = self.message;
876 self.message = oldmessage;
881 =============================================================================
885 =============================================================================
893 if (self.owner != self)
894 objerror ("door_fire: self.owner != self");
898 if (self.spawnflags & DOOR_TOGGLE)
900 if (self.state == STATE_UP || self.state == STATE_TOP)
905 if (self.classname == "door")
911 door_rotating_go_down ();
914 } while ( (self != starte) && (self != world) );
920 // trigger all paired doors
924 if (self.classname == "door")
929 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
930 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
932 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
933 self.pos2 = '0 0 0' - self.pos2;
935 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
936 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
937 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
939 door_rotating_go_up ();
943 } while ( (self != starte) && (self != world) );
952 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
963 void door_trigger_touch()
965 if (other.health < 1)
966 if not(other.iscreature && other.deadflag == DEAD_NO)
969 if (time < self.attack_finished_single)
971 self.attack_finished_single = time + 1;
980 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
983 if(self.spawnflags & DOOR_NOSPLASH)
984 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
986 self.health = self.health - damage;
987 if (self.health <= 0)
991 self.health = self.max_health;
992 self.takedamage = DAMAGE_NO; // wil be reset upon return
1008 if(other.classname != "player")
1010 if (self.owner.attack_finished_single > time)
1013 self.owner.attack_finished_single = time + 2;
1015 if (!(self.owner.dmg) && (self.owner.message != ""))
1017 if (other.flags & FL_CLIENT)
1018 centerprint (other, self.owner.message);
1019 play2(other, "misc/talk.wav");
1024 void door_generic_plat_blocked()
1027 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1028 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1031 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1032 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1034 //Dont chamge direction for dead or dying stuff
1035 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1038 if (self.state == STATE_DOWN)
1039 door_rotating_go_up ();
1041 door_rotating_go_down ();
1044 //gib dying stuff just to make sure
1045 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1046 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1050 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1051 // if a door has a negative wait, it would never come back if blocked,
1052 // so let it just squash the object to death real fast
1053 /* if (self.wait >= 0)
1055 if (self.state == STATE_DOWN)
1056 door_rotating_go_up ();
1058 door_rotating_go_down ();
1064 void door_rotating_hit_top()
1066 if (self.noise1 != "")
1067 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1068 self.state = STATE_TOP;
1069 if (self.spawnflags & DOOR_TOGGLE)
1070 return; // don't come down automatically
1071 self.think = door_rotating_go_down;
1072 self.nextthink = self.ltime + self.wait;
1075 void door_rotating_hit_bottom()
1077 if (self.noise1 != "")
1078 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1079 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1081 self.pos2 = '0 0 0' - self.pos2;
1084 self.state = STATE_BOTTOM;
1087 void door_rotating_go_down()
1089 if (self.noise2 != "")
1090 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1091 if (self.max_health)
1093 self.takedamage = DAMAGE_YES;
1094 self.health = self.max_health;
1097 self.state = STATE_DOWN;
1098 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1101 void door_rotating_go_up()
1103 if (self.state == STATE_UP)
1104 return; // already going up
1106 if (self.state == STATE_TOP)
1107 { // reset top wait time
1108 self.nextthink = self.ltime + self.wait;
1111 if (self.noise2 != "")
1112 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1113 self.state = STATE_UP;
1114 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1117 oldmessage = self.message;
1120 self.message = oldmessage;
1127 =============================================================================
1131 =============================================================================
1135 entity spawn_field(vector fmins, vector fmaxs)
1137 local entity trigger;
1138 local vector t1, t2;
1141 trigger.classname = "doortriggerfield";
1142 trigger.movetype = MOVETYPE_NONE;
1143 trigger.solid = SOLID_TRIGGER;
1144 trigger.owner = self;
1145 trigger.touch = door_trigger_touch;
1149 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1154 float EntitiesTouching(entity e1, entity e2)
1156 if (e1.absmin_x > e2.absmax_x)
1158 if (e1.absmin_y > e2.absmax_y)
1160 if (e1.absmin_z > e2.absmax_z)
1162 if (e1.absmax_x < e2.absmin_x)
1164 if (e1.absmax_y < e2.absmin_y)
1166 if (e1.absmax_z < e2.absmin_z)
1181 local entity t, starte;
1182 local vector cmins, cmaxs;
1185 return; // already linked by another door
1186 if (self.spawnflags & 4)
1188 self.owner = self.enemy = self;
1196 self.trigger_field = spawn_field(self.absmin, self.absmax);
1198 return; // don't want to link this door
1201 cmins = self.absmin;
1202 cmaxs = self.absmax;
1209 self.owner = starte; // master door
1212 starte.health = self.health;
1214 starte.targetname = self.targetname;
1215 if (self.message != "")
1216 starte.message = self.message;
1218 t = find(t, classname, self.classname);
1221 self.enemy = starte; // make the chain a loop
1223 // shootable, or triggered doors just needed the owner/enemy links,
1224 // they don't spawn a field
1235 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1240 if (EntitiesTouching(self,t))
1243 objerror ("cross connected doors");
1248 if (t.absmin_x < cmins_x)
1249 cmins_x = t.absmin_x;
1250 if (t.absmin_y < cmins_y)
1251 cmins_y = t.absmin_y;
1252 if (t.absmin_z < cmins_z)
1253 cmins_z = t.absmin_z;
1254 if (t.absmax_x > cmaxs_x)
1255 cmaxs_x = t.absmax_x;
1256 if (t.absmax_y > cmaxs_y)
1257 cmaxs_y = t.absmax_y;
1258 if (t.absmax_z > cmaxs_z)
1259 cmaxs_z = t.absmax_z;
1266 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1267 if two doors touch, they are assumed to be connected and operate as a unit.
1269 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1271 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).
1273 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1274 "angle" determines the opening direction
1275 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1276 "health" if set, door must be shot open
1277 "speed" movement speed (100 default)
1278 "wait" wait before returning (3 default, -1 = never return)
1279 "lip" lip remaining at end of move (8 default)
1280 "dmg" damage to inflict when blocked (2 default)
1287 FIXME: only one sound set available at the time being
1291 void door_init_startopen()
1293 setorigin (self, self.pos2);
1294 self.pos2 = self.pos1;
1295 self.pos1 = self.origin;
1300 setorigin(self, self.pos1);
1301 self.velocity = '0 0 0';
1302 self.state = STATE_BOTTOM;
1303 self.think = SUB_Null;
1306 void spawnfunc_func_door()
1308 //if (!self.deathtype) // map makers can override this
1309 // self.deathtype = " got in the way";
1312 self.max_health = self.health;
1313 if not(InitMovingBrushTrigger())
1315 self.effects |= EF_LOWPRECISION;
1316 self.classname = "door";
1318 self.blocked = door_blocked;
1319 self.use = door_use;
1321 if(self.spawnflags & 8)
1324 if(self.dmg && (!self.message))
1325 self.message = "was squished";
1326 if(self.dmg && (!self.message2))
1327 self.message2 = "was squished by";
1329 if (self.sounds > 0)
1331 precache_sound ("plats/medplat1.wav");
1332 precache_sound ("plats/medplat2.wav");
1333 self.noise2 = "plats/medplat1.wav";
1334 self.noise1 = "plats/medplat2.wav";
1344 self.pos1 = self.origin;
1345 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1347 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1348 // but spawn in the open position
1349 if (self.spawnflags & DOOR_START_OPEN)
1350 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1352 self.state = STATE_BOTTOM;
1356 self.takedamage = DAMAGE_YES;
1357 self.event_damage = door_damage;
1363 self.touch = door_touch;
1365 // LinkDoors can't be done until all of the doors have been spawned, so
1366 // the sizes can be detected properly.
1367 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1369 self.reset = door_reset;
1372 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1373 if two doors touch, they are assumed to be connected and operate as a unit.
1375 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1377 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1378 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1379 must have set trigger_reverse to 1.
1380 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1382 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).
1384 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1385 "angle" determines the destination angle for opening. negative values reverse the direction.
1386 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1387 "health" if set, door must be shot open
1388 "speed" movement speed (100 default)
1389 "wait" wait before returning (3 default, -1 = never return)
1390 "dmg" damage to inflict when blocked (2 default)
1397 FIXME: only one sound set available at the time being
1400 void door_rotating_reset()
1402 self.angles = self.pos1;
1403 self.avelocity = '0 0 0';
1404 self.state = STATE_BOTTOM;
1405 self.think = SUB_Null;
1408 void door_rotating_init_startopen()
1410 self.angles = self.movedir;
1411 self.pos2 = '0 0 0';
1412 self.pos1 = self.movedir;
1416 void spawnfunc_func_door_rotating()
1419 //if (!self.deathtype) // map makers can override this
1420 // self.deathtype = " got in the way";
1422 // I abuse "movedir" for denoting the axis for now
1423 if (self.spawnflags & 64) // X (untested)
1424 self.movedir = '0 0 1';
1425 else if (self.spawnflags & 128) // Y (untested)
1426 self.movedir = '1 0 0';
1428 self.movedir = '0 1 0';
1430 if (self.angles_y==0) self.angles_y = 90;
1432 self.movedir = self.movedir * self.angles_y;
1433 self.angles = '0 0 0';
1435 self.max_health = self.health;
1436 if not(InitMovingBrushTrigger())
1438 //self.effects |= EF_LOWPRECISION;
1439 self.classname = "door_rotating";
1441 self.blocked = door_blocked;
1442 self.use = door_use;
1444 if(self.spawnflags & 8)
1447 if(self.dmg && (!self.message))
1448 self.message = "was squished";
1449 if(self.dmg && (!self.message2))
1450 self.message2 = "was squished by";
1452 if (self.sounds > 0)
1454 precache_sound ("plats/medplat1.wav");
1455 precache_sound ("plats/medplat2.wav");
1456 self.noise2 = "plats/medplat1.wav";
1457 self.noise1 = "plats/medplat2.wav";
1464 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1466 self.pos1 = '0 0 0';
1467 self.pos2 = self.movedir;
1469 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1470 // but spawn in the open position
1471 if (self.spawnflags & DOOR_START_OPEN)
1472 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1474 self.state = STATE_BOTTOM;
1478 self.takedamage = DAMAGE_YES;
1479 self.event_damage = door_damage;
1485 self.touch = door_touch;
1487 // LinkDoors can't be done until all of the doors have been spawned, so
1488 // the sizes can be detected properly.
1489 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1491 self.reset = door_rotating_reset;
1495 =============================================================================
1499 =============================================================================
1502 void() fd_secret_move1;
1503 void() fd_secret_move2;
1504 void() fd_secret_move3;
1505 void() fd_secret_move4;
1506 void() fd_secret_move5;
1507 void() fd_secret_move6;
1508 void() fd_secret_done;
1510 float SECRET_OPEN_ONCE = 1; // stays open
1511 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1512 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1513 float SECRET_NO_SHOOT = 8; // only opened by trigger
1514 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1517 void fd_secret_use()
1520 string message_save;
1522 self.health = 10000;
1523 self.bot_attack = TRUE;
1525 // exit if still moving around...
1526 if (self.origin != self.oldorigin)
1529 message_save = self.message;
1530 self.message = ""; // no more message
1531 SUB_UseTargets(); // fire all targets / killtargets
1532 self.message = message_save;
1534 self.velocity = '0 0 0';
1536 // Make a sound, wait a little...
1538 if (self.noise1 != "")
1539 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1540 self.nextthink = self.ltime + 0.1;
1542 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1543 makevectors(self.mangle);
1547 if (self.spawnflags & SECRET_1ST_DOWN)
1548 self.t_width = fabs(v_up * self.size);
1550 self.t_width = fabs(v_right * self.size);
1554 self.t_length = fabs(v_forward * self.size);
1556 if (self.spawnflags & SECRET_1ST_DOWN)
1557 self.dest1 = self.origin - v_up * self.t_width;
1559 self.dest1 = self.origin + v_right * (self.t_width * temp);
1561 self.dest2 = self.dest1 + v_forward * self.t_length;
1562 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1563 if (self.noise2 != "")
1564 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1567 // Wait after first movement...
1568 void fd_secret_move1()
1570 self.nextthink = self.ltime + 1.0;
1571 self.think = fd_secret_move2;
1572 if (self.noise3 != "")
1573 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1576 // Start moving sideways w/sound...
1577 void fd_secret_move2()
1579 if (self.noise2 != "")
1580 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1581 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1584 // Wait here until time to go back...
1585 void fd_secret_move3()
1587 if (self.noise3 != "")
1588 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1589 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1591 self.nextthink = self.ltime + self.wait;
1592 self.think = fd_secret_move4;
1597 void fd_secret_move4()
1599 if (self.noise2 != "")
1600 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1601 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1605 void fd_secret_move5()
1607 self.nextthink = self.ltime + 1.0;
1608 self.think = fd_secret_move6;
1609 if (self.noise3 != "")
1610 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1613 void fd_secret_move6()
1615 if (self.noise2 != "")
1616 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1617 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1620 void fd_secret_done()
1622 if (self.spawnflags&SECRET_YES_SHOOT)
1624 self.health = 10000;
1625 self.takedamage = DAMAGE_YES;
1626 //self.th_pain = fd_secret_use;
1628 if (self.noise3 != "")
1629 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1632 void secret_blocked()
1634 if (time < self.attack_finished_single)
1636 self.attack_finished_single = time + 0.5;
1637 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1649 if not(other.iscreature)
1651 if (self.attack_finished_single > time)
1654 self.attack_finished_single = time + 2;
1658 if (other.flags & FL_CLIENT)
1659 centerprint (other, self.message);
1660 play2(other, "misc/talk.wav");
1666 if (self.spawnflags&SECRET_YES_SHOOT)
1668 self.health = 10000;
1669 self.takedamage = DAMAGE_YES;
1671 setorigin(self, self.oldorigin);
1672 self.think = SUB_Null;
1675 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1676 Basic secret door. Slides back, then to the side. Angle determines direction.
1677 wait = # of seconds before coming back
1678 1st_left = 1st move is left of arrow
1679 1st_down = 1st move is down from arrow
1680 always_shoot = even if targeted, keep shootable
1681 t_width = override WIDTH to move back (or height if going down)
1682 t_length = override LENGTH to move sideways
1683 "dmg" damage to inflict when blocked (2 default)
1685 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1692 void spawnfunc_func_door_secret()
1694 /*if (!self.deathtype) // map makers can override this
1695 self.deathtype = " got in the way";*/
1701 self.mangle = self.angles;
1702 self.angles = '0 0 0';
1703 self.classname = "door";
1704 if not(InitMovingBrushTrigger())
1706 self.effects |= EF_LOWPRECISION;
1708 self.touch = secret_touch;
1709 self.blocked = secret_blocked;
1711 self.use = fd_secret_use;
1716 self.spawnflags |= SECRET_YES_SHOOT;
1718 if(self.spawnflags&SECRET_YES_SHOOT)
1720 self.health = 10000;
1721 self.takedamage = DAMAGE_YES;
1722 self.event_damage = fd_secret_use;
1724 self.oldorigin = self.origin;
1726 self.wait = 5; // 5 seconds before closing
1728 self.reset = secret_reset;
1732 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1733 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1734 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
1735 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1736 height: amplitude modifier (default 32)
1737 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1738 noise: path/name of looping .wav file to play.
1739 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1743 void func_fourier_controller_think()
1748 self.nextthink = time + 0.1;
1749 if not (self.owner.active == ACTIVE_ACTIVE)
1751 self.owner.velocity = '0 0 0';
1756 n = floor((tokenize_console(self.owner.netname)) / 5);
1757 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1759 v = self.owner.destvec;
1761 for(i = 0; i < n; ++i)
1763 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1764 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;
1767 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1768 // * 10 so it will arrive in 0.1 sec
1769 self.owner.velocity = (v - self.owner.origin) * 10;
1772 void spawnfunc_func_fourier()
1774 local entity controller;
1775 if (self.noise != "")
1777 precache_sound(self.noise);
1778 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1785 self.destvec = self.origin;
1786 self.cnt = 360 / self.speed;
1788 self.blocked = generic_plat_blocked;
1789 if(self.dmg & (!self.message))
1790 self.message = " was squished";
1791 if(self.dmg && (!self.message2))
1792 self.message2 = "was squished by";
1793 if(self.dmg && (!self.dmgtime))
1794 self.dmgtime = 0.25;
1795 self.dmgtime2 = time;
1797 if(self.netname == "")
1798 self.netname = "1 0 0 0 1";
1800 if not(InitMovingBrushTrigger())
1803 self.active = ACTIVE_ACTIVE;
1805 // wait for targets to spawn
1806 controller = spawn();
1807 controller.classname = "func_fourier_controller";
1808 controller.owner = self;
1809 controller.nextthink = time + 1;
1810 controller.think = func_fourier_controller_think;
1811 self.nextthink = self.ltime + 999999999;
1812 self.think = SUB_Null;
1814 // Savage: Reduce bandwith, critical on e.g. nexdm02
1815 self.effects |= EF_LOWPRECISION;
1817 // TODO make a reset function for this one
1820 // reusing some fields havocbots declared
1821 .entity wp00, wp01, wp02, wp03;
1823 .float targetfactor, target2factor, target3factor, target4factor;
1824 .vector targetnormal, target2normal, target3normal, target4normal;
1826 vector func_vectormamamam_origin(entity o, float t)
1838 p = e.origin + t * e.velocity;
1840 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1842 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1848 p = e.origin + t * e.velocity;
1850 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1852 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1858 p = e.origin + t * e.velocity;
1860 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1862 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1868 p = e.origin + t * e.velocity;
1870 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1872 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1878 void func_vectormamamam_controller_think()
1880 self.nextthink = time + 0.1;
1882 if not (self.owner.active == ACTIVE_ACTIVE)
1884 self.owner.velocity = '0 0 0';
1888 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1889 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1892 void func_vectormamamam_findtarget()
1894 if(self.target != "")
1895 self.wp00 = find(world, targetname, self.target);
1897 if(self.target2 != "")
1898 self.wp01 = find(world, targetname, self.target2);
1900 if(self.target3 != "")
1901 self.wp02 = find(world, targetname, self.target3);
1903 if(self.target4 != "")
1904 self.wp03 = find(world, targetname, self.target4);
1906 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1907 objerror("No reference entity found, so there is nothing to move. Aborting.");
1909 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1911 local entity controller;
1912 controller = spawn();
1913 controller.classname = "func_vectormamamam_controller";
1914 controller.owner = self;
1915 controller.nextthink = time + 1;
1916 controller.think = func_vectormamamam_controller_think;
1919 void spawnfunc_func_vectormamamam()
1921 if (self.noise != "")
1923 precache_sound(self.noise);
1924 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1927 if(!self.targetfactor)
1928 self.targetfactor = 1;
1930 if(!self.target2factor)
1931 self.target2factor = 1;
1933 if(!self.target3factor)
1934 self.target3factor = 1;
1936 if(!self.target4factor)
1937 self.target4factor = 1;
1939 if(vlen(self.targetnormal))
1940 self.targetnormal = normalize(self.targetnormal);
1942 if(vlen(self.target2normal))
1943 self.target2normal = normalize(self.target2normal);
1945 if(vlen(self.target3normal))
1946 self.target3normal = normalize(self.target3normal);
1948 if(vlen(self.target4normal))
1949 self.target4normal = normalize(self.target4normal);
1951 self.blocked = generic_plat_blocked;
1952 if(self.dmg & (!self.message))
1953 self.message = " was squished";
1954 if(self.dmg && (!self.message2))
1955 self.message2 = "was squished by";
1956 if(self.dmg && (!self.dmgtime))
1957 self.dmgtime = 0.25;
1958 self.dmgtime2 = time;
1960 if(self.netname == "")
1961 self.netname = "1 0 0 0 1";
1963 if not(InitMovingBrushTrigger())
1966 // wait for targets to spawn
1967 self.nextthink = self.ltime + 999999999;
1968 self.think = SUB_Null;
1970 // Savage: Reduce bandwith, critical on e.g. nexdm02
1971 self.effects |= EF_LOWPRECISION;
1973 self.active = ACTIVE_ACTIVE;
1975 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);