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 void bobbing_blocked()
428 // no need to duplicate code
429 generic_plat_blocked();
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 = bobbing_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
496 // button and multiple button
499 void() button_return;
503 self.state = STATE_TOP;
504 self.nextthink = self.ltime + self.wait;
505 self.think = button_return;
506 activator = self.enemy;
508 self.frame = 1; // use alternate textures
513 self.state = STATE_BOTTOM;
518 self.state = STATE_DOWN;
519 SUB_CalcMove (self.pos1, self.speed, button_done);
520 self.frame = 0; // use normal textures
522 self.takedamage = DAMAGE_YES; // can be shot again
526 void button_blocked()
528 // do nothing, just don't come all the way back out
534 self.health = self.max_health;
535 self.takedamage = DAMAGE_NO; // will be reset upon return
537 if (self.state == STATE_UP || self.state == STATE_TOP)
540 if (self.noise != "")
541 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
543 self.state = STATE_UP;
544 SUB_CalcMove (self.pos2, self.speed, button_wait);
549 self.health = self.max_health;
550 setorigin(self, self.pos1);
551 self.frame = 0; // use normal textures
552 self.state = STATE_BOTTOM;
554 self.takedamage = DAMAGE_YES; // can be shot again
559 // if (activator.classname != "player")
561 // dprint(activator.classname);
562 // dprint(" triggered a button\n");
565 if not (self.active == ACTIVE_ACTIVE)
568 self.enemy = activator;
574 // if (activator.classname != "player")
576 // dprint(activator.classname);
577 // dprint(" touched a button\n");
581 if not(other.iscreature)
583 if(other.velocity * self.movedir < 0)
587 self.enemy = other.owner;
591 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
593 if(self.spawnflags & DOOR_NOSPLASH)
594 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
596 self.health = self.health - damage;
597 if (self.health <= 0)
599 // if (activator.classname != "player")
601 // dprint(activator.classname);
602 // dprint(" killed a button\n");
604 self.enemy = damage_attacker;
610 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
611 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.
613 "angle" determines the opening direction
614 "target" all entities with a matching targetname will be used
615 "speed" override the default 40 speed
616 "wait" override the default 1 second wait (-1 = never return)
617 "lip" override the default 4 pixel lip remaining at end of move
618 "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
625 void spawnfunc_func_button()
629 if not(InitMovingBrushTrigger())
631 self.effects |= EF_LOWPRECISION;
633 self.blocked = button_blocked;
634 self.use = button_use;
636 // if (self.health == 0) // all buttons are now shootable
640 self.max_health = self.health;
641 self.event_damage = button_damage;
642 self.takedamage = DAMAGE_YES;
645 self.touch = button_touch;
655 precache_sound(self.noise);
657 self.active = ACTIVE_ACTIVE;
659 self.pos1 = self.origin;
660 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
661 self.flags |= FL_NOTARGET;
667 float DOOR_START_OPEN = 1;
668 float DOOR_DONT_LINK = 4;
669 float DOOR_TOGGLE = 32;
673 Doors are similar to buttons, but can spawn a fat trigger field around them
674 to open without a touch, and they link together to form simultanious
677 Door.owner is the master door. If there is only one door, it points to itself.
678 If multiple doors, all will point to a single one.
680 Door.enemy chains from the master door through all doors linked in the chain.
685 =============================================================================
689 =============================================================================
694 void() door_rotating_go_down;
695 void() door_rotating_go_up;
700 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
701 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
704 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
705 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
707 //Dont chamge direction for dead or dying stuff
708 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
711 if (self.state == STATE_DOWN)
712 if (self.classname == "door")
717 door_rotating_go_up ();
720 if (self.classname == "door")
725 door_rotating_go_down ();
729 //gib dying stuff just to make sure
730 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
731 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
735 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
736 // if a door has a negative wait, it would never come back if blocked,
737 // so let it just squash the object to death real fast
738 /* if (self.wait >= 0)
740 if (self.state == STATE_DOWN)
751 if (self.noise1 != "")
752 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
753 self.state = STATE_TOP;
754 if (self.spawnflags & DOOR_TOGGLE)
755 return; // don't come down automatically
756 if (self.classname == "door")
758 self.think = door_go_down;
761 self.think = door_rotating_go_down;
763 self.nextthink = self.ltime + self.wait;
766 void door_hit_bottom()
768 if (self.noise1 != "")
769 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
770 self.state = STATE_BOTTOM;
775 if (self.noise2 != "")
776 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
779 self.takedamage = DAMAGE_YES;
780 self.health = self.max_health;
783 self.state = STATE_DOWN;
784 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
789 if (self.state == STATE_UP)
790 return; // already going up
792 if (self.state == STATE_TOP)
793 { // reset top wait time
794 self.nextthink = self.ltime + self.wait;
798 if (self.noise2 != "")
799 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
800 self.state = STATE_UP;
801 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
804 oldmessage = self.message;
807 self.message = oldmessage;
812 =============================================================================
816 =============================================================================
824 if (self.owner != self)
825 objerror ("door_fire: self.owner != self");
829 if (self.spawnflags & DOOR_TOGGLE)
831 if (self.state == STATE_UP || self.state == STATE_TOP)
836 if (self.classname == "door")
842 door_rotating_go_down ();
845 } while ( (self != starte) && (self != world) );
851 // trigger all paired doors
855 if (self.classname == "door")
860 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
861 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
863 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
864 self.pos2 = '0 0 0' - self.pos2;
866 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
867 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
868 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
870 door_rotating_go_up ();
874 } while ( (self != starte) && (self != world) );
883 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
894 void door_trigger_touch()
896 if (other.health < 1)
897 if not(other.iscreature && other.deadflag == DEAD_NO)
900 if (time < self.attack_finished_single)
902 self.attack_finished_single = time + 1;
911 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
914 if(self.spawnflags & DOOR_NOSPLASH)
915 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
917 self.health = self.health - damage;
918 if (self.health <= 0)
922 self.health = self.max_health;
923 self.takedamage = DAMAGE_NO; // wil be reset upon return
939 if(other.classname != "player")
941 if (self.owner.attack_finished_single > time)
944 self.owner.attack_finished_single = time + 2;
946 if (!(self.owner.dmg) && (self.owner.message != ""))
948 if (other.flags & FL_CLIENT)
949 centerprint (other, self.owner.message);
950 play2(other, "misc/talk.wav");
955 void door_generic_plat_blocked()
958 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
959 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
962 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
963 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
965 //Dont chamge direction for dead or dying stuff
966 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
969 if (self.state == STATE_DOWN)
970 door_rotating_go_up ();
972 door_rotating_go_down ();
975 //gib dying stuff just to make sure
976 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
977 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
981 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
982 // if a door has a negative wait, it would never come back if blocked,
983 // so let it just squash the object to death real fast
984 /* if (self.wait >= 0)
986 if (self.state == STATE_DOWN)
987 door_rotating_go_up ();
989 door_rotating_go_down ();
995 void door_rotating_hit_top()
997 if (self.noise1 != "")
998 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
999 self.state = STATE_TOP;
1000 if (self.spawnflags & DOOR_TOGGLE)
1001 return; // don't come down automatically
1002 self.think = door_rotating_go_down;
1003 self.nextthink = self.ltime + self.wait;
1006 void door_rotating_hit_bottom()
1008 if (self.noise1 != "")
1009 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1010 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1012 self.pos2 = '0 0 0' - self.pos2;
1015 self.state = STATE_BOTTOM;
1018 void door_rotating_go_down()
1020 if (self.noise2 != "")
1021 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1022 if (self.max_health)
1024 self.takedamage = DAMAGE_YES;
1025 self.health = self.max_health;
1028 self.state = STATE_DOWN;
1029 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1032 void door_rotating_go_up()
1034 if (self.state == STATE_UP)
1035 return; // already going up
1037 if (self.state == STATE_TOP)
1038 { // reset top wait time
1039 self.nextthink = self.ltime + self.wait;
1042 if (self.noise2 != "")
1043 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1044 self.state = STATE_UP;
1045 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1048 oldmessage = self.message;
1051 self.message = oldmessage;
1058 =============================================================================
1062 =============================================================================
1066 entity spawn_field(vector fmins, vector fmaxs)
1068 local entity trigger;
1069 local vector t1, t2;
1072 trigger.classname = "doortriggerfield";
1073 trigger.movetype = MOVETYPE_NONE;
1074 trigger.solid = SOLID_TRIGGER;
1075 trigger.owner = self;
1076 trigger.touch = door_trigger_touch;
1080 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1085 float EntitiesTouching(entity e1, entity e2)
1087 if (e1.absmin_x > e2.absmax_x)
1089 if (e1.absmin_y > e2.absmax_y)
1091 if (e1.absmin_z > e2.absmax_z)
1093 if (e1.absmax_x < e2.absmin_x)
1095 if (e1.absmax_y < e2.absmin_y)
1097 if (e1.absmax_z < e2.absmin_z)
1112 local entity t, starte;
1113 local vector cmins, cmaxs;
1116 return; // already linked by another door
1117 if (self.spawnflags & 4)
1119 self.owner = self.enemy = self;
1127 self.trigger_field = spawn_field(self.absmin, self.absmax);
1129 return; // don't want to link this door
1132 cmins = self.absmin;
1133 cmaxs = self.absmax;
1140 self.owner = starte; // master door
1143 starte.health = self.health;
1145 starte.targetname = self.targetname;
1146 if (self.message != "")
1147 starte.message = self.message;
1149 t = find(t, classname, self.classname);
1152 self.enemy = starte; // make the chain a loop
1154 // shootable, or triggered doors just needed the owner/enemy links,
1155 // they don't spawn a field
1166 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1171 if (EntitiesTouching(self,t))
1174 objerror ("cross connected doors");
1179 if (t.absmin_x < cmins_x)
1180 cmins_x = t.absmin_x;
1181 if (t.absmin_y < cmins_y)
1182 cmins_y = t.absmin_y;
1183 if (t.absmin_z < cmins_z)
1184 cmins_z = t.absmin_z;
1185 if (t.absmax_x > cmaxs_x)
1186 cmaxs_x = t.absmax_x;
1187 if (t.absmax_y > cmaxs_y)
1188 cmaxs_y = t.absmax_y;
1189 if (t.absmax_z > cmaxs_z)
1190 cmaxs_z = t.absmax_z;
1197 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1198 if two doors touch, they are assumed to be connected and operate as a unit.
1200 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1202 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).
1204 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1205 "angle" determines the opening direction
1206 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1207 "health" if set, door must be shot open
1208 "speed" movement speed (100 default)
1209 "wait" wait before returning (3 default, -1 = never return)
1210 "lip" lip remaining at end of move (8 default)
1211 "dmg" damage to inflict when blocked (2 default)
1218 FIXME: only one sound set available at the time being
1222 void door_init_startopen()
1224 setorigin (self, self.pos2);
1225 self.pos2 = self.pos1;
1226 self.pos1 = self.origin;
1231 setorigin(self, self.pos1);
1232 self.velocity = '0 0 0';
1233 self.state = STATE_BOTTOM;
1234 self.think = SUB_Null;
1237 void spawnfunc_func_door()
1239 //if (!self.deathtype) // map makers can override this
1240 // self.deathtype = " got in the way";
1243 self.max_health = self.health;
1244 if not(InitMovingBrushTrigger())
1246 self.effects |= EF_LOWPRECISION;
1247 self.classname = "door";
1249 self.blocked = door_blocked;
1250 self.use = door_use;
1252 if(self.spawnflags & 8)
1255 if(self.dmg && (!self.message))
1256 self.message = "was squished";
1257 if(self.dmg && (!self.message2))
1258 self.message2 = "was squished by";
1260 if (self.sounds > 0)
1262 precache_sound ("plats/medplat1.wav");
1263 precache_sound ("plats/medplat2.wav");
1264 self.noise2 = "plats/medplat1.wav";
1265 self.noise1 = "plats/medplat2.wav";
1275 self.pos1 = self.origin;
1276 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1278 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1279 // but spawn in the open position
1280 if (self.spawnflags & DOOR_START_OPEN)
1281 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1283 self.state = STATE_BOTTOM;
1287 self.takedamage = DAMAGE_YES;
1288 self.event_damage = door_damage;
1294 self.touch = door_touch;
1296 // LinkDoors can't be done until all of the doors have been spawned, so
1297 // the sizes can be detected properly.
1298 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1300 self.reset = door_reset;
1303 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1304 if two doors touch, they are assumed to be connected and operate as a unit.
1306 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1308 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1309 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1310 must have set trigger_reverse to 1.
1311 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1313 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).
1315 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1316 "angle" determines the destination angle for opening. negative values reverse the direction.
1317 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1318 "health" if set, door must be shot open
1319 "speed" movement speed (100 default)
1320 "wait" wait before returning (3 default, -1 = never return)
1321 "dmg" damage to inflict when blocked (2 default)
1328 FIXME: only one sound set available at the time being
1331 void door_rotating_reset()
1333 self.angles = self.pos1;
1334 self.avelocity = '0 0 0';
1335 self.state = STATE_BOTTOM;
1336 self.think = SUB_Null;
1339 void door_rotating_init_startopen()
1341 self.angles = self.movedir;
1342 self.pos2 = '0 0 0';
1343 self.pos1 = self.movedir;
1347 void spawnfunc_func_door_rotating()
1350 //if (!self.deathtype) // map makers can override this
1351 // self.deathtype = " got in the way";
1353 // I abuse "movedir" for denoting the axis for now
1354 if (self.spawnflags & 64) // X (untested)
1355 self.movedir = '0 0 1';
1356 else if (self.spawnflags & 128) // Y (untested)
1357 self.movedir = '1 0 0';
1359 self.movedir = '0 1 0';
1361 if (self.angles_y==0) self.angles_y = 90;
1363 self.movedir = self.movedir * self.angles_y;
1364 self.angles = '0 0 0';
1366 self.max_health = self.health;
1367 if not(InitMovingBrushTrigger())
1369 //self.effects |= EF_LOWPRECISION;
1370 self.classname = "door_rotating";
1372 self.blocked = door_blocked;
1373 self.use = door_use;
1375 if(self.spawnflags & 8)
1378 if(self.dmg && (!self.message))
1379 self.message = "was squished";
1380 if(self.dmg && (!self.message2))
1381 self.message2 = "was squished by";
1383 if (self.sounds > 0)
1385 precache_sound ("plats/medplat1.wav");
1386 precache_sound ("plats/medplat2.wav");
1387 self.noise2 = "plats/medplat1.wav";
1388 self.noise1 = "plats/medplat2.wav";
1395 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1397 self.pos1 = '0 0 0';
1398 self.pos2 = self.movedir;
1400 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1401 // but spawn in the open position
1402 if (self.spawnflags & DOOR_START_OPEN)
1403 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1405 self.state = STATE_BOTTOM;
1409 self.takedamage = DAMAGE_YES;
1410 self.event_damage = door_damage;
1416 self.touch = door_touch;
1418 // LinkDoors can't be done until all of the doors have been spawned, so
1419 // the sizes can be detected properly.
1420 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1422 self.reset = door_rotating_reset;
1426 =============================================================================
1430 =============================================================================
1433 void() fd_secret_move1;
1434 void() fd_secret_move2;
1435 void() fd_secret_move3;
1436 void() fd_secret_move4;
1437 void() fd_secret_move5;
1438 void() fd_secret_move6;
1439 void() fd_secret_done;
1441 float SECRET_OPEN_ONCE = 1; // stays open
1442 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1443 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1444 float SECRET_NO_SHOOT = 8; // only opened by trigger
1445 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1448 void fd_secret_use()
1451 string message_save;
1453 self.health = 10000;
1454 self.bot_attack = TRUE;
1456 // exit if still moving around...
1457 if (self.origin != self.oldorigin)
1460 message_save = self.message;
1461 self.message = ""; // no more message
1462 SUB_UseTargets(); // fire all targets / killtargets
1463 self.message = message_save;
1465 self.velocity = '0 0 0';
1467 // Make a sound, wait a little...
1469 if (self.noise1 != "")
1470 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1471 self.nextthink = self.ltime + 0.1;
1473 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1474 makevectors(self.mangle);
1478 if (self.spawnflags & SECRET_1ST_DOWN)
1479 self.t_width = fabs(v_up * self.size);
1481 self.t_width = fabs(v_right * self.size);
1485 self.t_length = fabs(v_forward * self.size);
1487 if (self.spawnflags & SECRET_1ST_DOWN)
1488 self.dest1 = self.origin - v_up * self.t_width;
1490 self.dest1 = self.origin + v_right * (self.t_width * temp);
1492 self.dest2 = self.dest1 + v_forward * self.t_length;
1493 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1494 if (self.noise2 != "")
1495 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1498 // Wait after first movement...
1499 void fd_secret_move1()
1501 self.nextthink = self.ltime + 1.0;
1502 self.think = fd_secret_move2;
1503 if (self.noise3 != "")
1504 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1507 // Start moving sideways w/sound...
1508 void fd_secret_move2()
1510 if (self.noise2 != "")
1511 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1512 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1515 // Wait here until time to go back...
1516 void fd_secret_move3()
1518 if (self.noise3 != "")
1519 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1520 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1522 self.nextthink = self.ltime + self.wait;
1523 self.think = fd_secret_move4;
1528 void fd_secret_move4()
1530 if (self.noise2 != "")
1531 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1532 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1536 void fd_secret_move5()
1538 self.nextthink = self.ltime + 1.0;
1539 self.think = fd_secret_move6;
1540 if (self.noise3 != "")
1541 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1544 void fd_secret_move6()
1546 if (self.noise2 != "")
1547 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1548 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1551 void fd_secret_done()
1553 if (self.spawnflags&SECRET_YES_SHOOT)
1555 self.health = 10000;
1556 self.takedamage = DAMAGE_YES;
1557 //self.th_pain = fd_secret_use;
1559 if (self.noise3 != "")
1560 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1563 void secret_blocked()
1565 if (time < self.attack_finished_single)
1567 self.attack_finished_single = time + 0.5;
1568 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1580 if not(other.iscreature)
1582 if (self.attack_finished_single > time)
1585 self.attack_finished_single = time + 2;
1589 if (other.flags & FL_CLIENT)
1590 centerprint (other, self.message);
1591 play2(other, "misc/talk.wav");
1597 if (self.spawnflags&SECRET_YES_SHOOT)
1599 self.health = 10000;
1600 self.takedamage = DAMAGE_YES;
1602 setorigin(self, self.oldorigin);
1603 self.think = SUB_Null;
1606 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1607 Basic secret door. Slides back, then to the side. Angle determines direction.
1608 wait = # of seconds before coming back
1609 1st_left = 1st move is left of arrow
1610 1st_down = 1st move is down from arrow
1611 always_shoot = even if targeted, keep shootable
1612 t_width = override WIDTH to move back (or height if going down)
1613 t_length = override LENGTH to move sideways
1614 "dmg" damage to inflict when blocked (2 default)
1616 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1623 void spawnfunc_func_door_secret()
1625 /*if (!self.deathtype) // map makers can override this
1626 self.deathtype = " got in the way";*/
1632 self.mangle = self.angles;
1633 self.angles = '0 0 0';
1634 self.classname = "door";
1635 if not(InitMovingBrushTrigger())
1637 self.effects |= EF_LOWPRECISION;
1639 self.touch = secret_touch;
1640 self.blocked = secret_blocked;
1642 self.use = fd_secret_use;
1647 self.spawnflags |= SECRET_YES_SHOOT;
1649 if(self.spawnflags&SECRET_YES_SHOOT)
1651 self.health = 10000;
1652 self.takedamage = DAMAGE_YES;
1653 self.event_damage = fd_secret_use;
1655 self.oldorigin = self.origin;
1657 self.wait = 5; // 5 seconds before closing
1659 self.reset = secret_reset;
1663 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1664 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1665 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
1666 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1667 height: amplitude modifier (default 32)
1668 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1669 noise: path/name of looping .wav file to play.
1670 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1674 void func_fourier_controller_think()
1679 self.nextthink = time + 0.1;
1680 if not (self.owner.active == ACTIVE_ACTIVE)
1682 self.owner.velocity = '0 0 0';
1687 n = floor((tokenize_console(self.owner.netname)) / 5);
1688 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1690 v = self.owner.destvec;
1692 for(i = 0; i < n; ++i)
1694 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1695 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;
1698 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1699 // * 10 so it will arrive in 0.1 sec
1700 self.owner.velocity = (v - self.owner.origin) * 10;
1703 void spawnfunc_func_fourier()
1705 local entity controller;
1706 if (self.noise != "")
1708 precache_sound(self.noise);
1709 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1716 self.destvec = self.origin;
1717 self.cnt = 360 / self.speed;
1719 self.blocked = generic_plat_blocked;
1720 if(self.dmg & (!self.message))
1721 self.message = " was squished";
1722 if(self.dmg && (!self.message2))
1723 self.message2 = "was squished by";
1724 if(self.dmg && (!self.dmgtime))
1725 self.dmgtime = 0.25;
1726 self.dmgtime2 = time;
1728 if(self.netname == "")
1729 self.netname = "1 0 0 0 1";
1731 if not(InitMovingBrushTrigger())
1734 self.active = ACTIVE_ACTIVE;
1736 // wait for targets to spawn
1737 controller = spawn();
1738 controller.classname = "func_fourier_controller";
1739 controller.owner = self;
1740 controller.nextthink = time + 1;
1741 controller.think = func_fourier_controller_think;
1742 self.nextthink = self.ltime + 999999999;
1743 self.think = SUB_Null;
1745 // Savage: Reduce bandwith, critical on e.g. nexdm02
1746 self.effects |= EF_LOWPRECISION;
1748 // TODO make a reset function for this one
1751 // reusing some fields havocbots declared
1752 .entity wp00, wp01, wp02, wp03;
1754 .float targetfactor, target2factor, target3factor, target4factor;
1755 .vector targetnormal, target2normal, target3normal, target4normal;
1757 vector func_vectormamamam_origin(entity o, float t)
1769 p = e.origin + t * e.velocity;
1771 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1773 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1779 p = e.origin + t * e.velocity;
1781 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1783 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1789 p = e.origin + t * e.velocity;
1791 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1793 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1799 p = e.origin + t * e.velocity;
1801 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1803 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1809 void func_vectormamamam_controller_think()
1811 self.nextthink = time + 0.1;
1813 if not (self.owner.active == ACTIVE_ACTIVE)
1815 self.owner.velocity = '0 0 0';
1819 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1820 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1823 void func_vectormamamam_findtarget()
1825 if(self.target != "")
1826 self.wp00 = find(world, targetname, self.target);
1828 if(self.target2 != "")
1829 self.wp01 = find(world, targetname, self.target2);
1831 if(self.target3 != "")
1832 self.wp02 = find(world, targetname, self.target3);
1834 if(self.target4 != "")
1835 self.wp03 = find(world, targetname, self.target4);
1837 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1838 objerror("No reference entity found, so there is nothing to move. Aborting.");
1840 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1842 local entity controller;
1843 controller = spawn();
1844 controller.classname = "func_vectormamamam_controller";
1845 controller.owner = self;
1846 controller.nextthink = time + 1;
1847 controller.think = func_vectormamamam_controller_think;
1850 void spawnfunc_func_vectormamamam()
1852 if (self.noise != "")
1854 precache_sound(self.noise);
1855 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1858 if(!self.targetfactor)
1859 self.targetfactor = 1;
1861 if(!self.target2factor)
1862 self.target2factor = 1;
1864 if(!self.target3factor)
1865 self.target3factor = 1;
1867 if(!self.target4factor)
1868 self.target4factor = 1;
1870 if(vlen(self.targetnormal))
1871 self.targetnormal = normalize(self.targetnormal);
1873 if(vlen(self.target2normal))
1874 self.target2normal = normalize(self.target2normal);
1876 if(vlen(self.target3normal))
1877 self.target3normal = normalize(self.target3normal);
1879 if(vlen(self.target4normal))
1880 self.target4normal = normalize(self.target4normal);
1882 self.blocked = generic_plat_blocked;
1883 if(self.dmg & (!self.message))
1884 self.message = " was squished";
1885 if(self.dmg && (!self.message2))
1886 self.message2 = "was squished by";
1887 if(self.dmg && (!self.dmgtime))
1888 self.dmgtime = 0.25;
1889 self.dmgtime2 = time;
1891 if(self.netname == "")
1892 self.netname = "1 0 0 0 1";
1894 if not(InitMovingBrushTrigger())
1897 // wait for targets to spawn
1898 self.nextthink = self.ltime + 999999999;
1899 self.think = SUB_Null;
1901 // Savage: Reduce bandwith, critical on e.g. nexdm02
1902 self.effects |= EF_LOWPRECISION;
1904 self.active = ACTIVE_ACTIVE;
1906 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);