3 float STATE_BOTTOM = 1;
11 void() plat_center_touch;
12 void() plat_outside_touch;
13 void() plat_trigger_use;
17 float PLAT_LOW_TRIGGER = 1;
19 void plat_spawn_inside_trigger()
22 local vector tmin, tmax;
25 trigger.touch = plat_center_touch;
26 trigger.movetype = MOVETYPE_NONE;
27 trigger.solid = SOLID_TRIGGER;
30 tmin = self.absmin + '25 25 0';
31 tmax = self.absmax - '25 25 -8';
32 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
33 if (self.spawnflags & PLAT_LOW_TRIGGER)
36 if (self.size_x <= 50)
38 tmin_x = (self.mins_x + self.maxs_x) / 2;
41 if (self.size_y <= 50)
43 tmin_y = (self.mins_y + self.maxs_y) / 2;
47 setsize (trigger, tmin, tmax);
52 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
54 self.think = plat_go_down;
55 self.nextthink = self.ltime + 3;
58 void plat_hit_bottom()
60 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
66 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
68 SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
73 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
75 SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
78 void plat_center_touch()
80 if not(other.iscreature)
83 if (other.health <= 0)
89 else if (self.state == 1)
90 self.nextthink = self.ltime + 1; // delay going down
93 void plat_outside_touch()
95 if not(other.iscreature)
98 if (other.health <= 0)
106 void plat_trigger_use()
109 return; // already activated
116 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
117 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
119 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
120 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
121 // Gib dead/dying stuff
122 if(other.deadflag != DEAD_NO)
123 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
128 else if (self.state == 3)
131 objerror ("plat_crush: bad self.state\n");
139 objerror ("plat_use: not in up state");
143 void plat_init_movedown()
145 setorigin (self, self.pos2);
149 .string sound1, sound2;
151 void spawnfunc_path_corner() { };
152 void spawnfunc_func_plat()
159 if (self.sounds == 0)
162 if(self.spawnflags & 4)
165 if(self.dmg && (!self.message))
166 self.message = "was squished";
167 if(self.dmg && (!self.message2))
168 self.message2 = "was squished by";
170 if (self.sounds == 1)
172 precache_sound ("plats/plat1.wav");
173 precache_sound ("plats/plat2.wav");
174 self.noise = "plats/plat1.wav";
175 self.noise1 = "plats/plat2.wav";
178 if (self.sounds == 2)
180 precache_sound ("plats/medplat1.wav");
181 precache_sound ("plats/medplat2.wav");
182 self.noise = "plats/medplat1.wav";
183 self.noise1 = "plats/medplat2.wav";
188 precache_sound (self.sound1);
189 self.noise = self.sound1;
193 precache_sound (self.sound2);
194 self.noise1 = self.sound2;
197 self.mangle = self.angles;
198 self.angles = '0 0 0';
200 self.classname = "plat";
201 if not(InitMovingBrushTrigger())
203 self.effects |= EF_LOWPRECISION;
204 setsize (self, self.mins , self.maxs);
206 self.blocked = plat_crush;
211 self.pos1 = self.origin;
212 self.pos2 = self.origin;
213 self.pos2_z = self.origin_z - self.size_z + 8;
215 self.use = plat_trigger_use;
217 plat_spawn_inside_trigger (); // the "start moving" trigger
225 InitializeEntity(self, plat_init_movedown, INITPRIO_SETLOCATION);
232 self.think = train_next;
233 self.nextthink = self.ltime + self.wait;
236 sound(self, CHAN_TRIGGER, STR_MISC_NULL_WAV, VOL_BASE, ATTN_NORM);
242 targ = find(world, targetname, self.target);
243 self.target = targ.target;
245 objerror("train_next: no next target");
246 self.wait = targ.wait;
252 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_next);
254 SUB_CalcMove(targ.origin - self.mins, self.speed, train_next);
259 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
261 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
265 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
268 void func_train_find()
271 targ = find(world, targetname, self.target);
272 self.target = targ.target;
274 objerror("func_train_find: no next target");
275 setorigin(self, targ.origin - self.mins);
276 self.nextthink = self.ltime + 1;
277 self.think = train_next;
280 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
281 Ridable platform, targets spawnfunc_path_corner path to follow.
282 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
283 target : targetname of first spawnfunc_path_corner (starts here)
285 void spawnfunc_func_train()
287 if (self.noise != "")
288 precache_sound(self.noise);
291 objerror("func_train without a target");
295 if not(InitMovingBrushTrigger())
297 self.effects |= EF_LOWPRECISION;
299 // wait for targets to spawn
300 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
305 void rotating_blocked()
308 if(self.dmg && other.takedamage != DAMAGE_NO) {
309 if(self.dmgtime2 < time) {
310 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
311 self.dmgtime2 = time + self.dmgtime;
314 // Gib dead/dying stuff
315 if(other.deadflag != DEAD_NO)
316 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
322 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
323 Brush model that spins in place on one axis (default Z).
324 speed : speed to rotate (in degrees per second)
325 noise : path/name of looping .wav file to play.
326 dmg : Do this mutch dmg every .dmgtime intervall when blocked
330 void spawnfunc_func_rotating()
332 if (self.noise != "")
334 precache_sound(self.noise);
335 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
339 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
340 if (self.spawnflags & 4) // X (untested)
341 self.avelocity = '0 0 1' * self.speed;
342 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
343 else if (self.spawnflags & 8) // Y (untested)
344 self.avelocity = '1 0 0' * self.speed;
345 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
347 self.avelocity = '0 1 0' * self.speed;
349 if(self.dmg & (!self.message))
350 self.message = " was squished";
351 if(self.dmg && (!self.message2))
352 self.message2 = "was squished by";
355 if(self.dmg && (!self.dmgtime))
358 self.dmgtime2 = time;
360 if not(InitMovingBrushTrigger())
362 // no EF_LOWPRECISION here, as rounding angles is bad
364 self.blocked = rotating_blocked;
366 // wait for targets to spawn
367 self.nextthink = self.ltime + 999999999;
368 self.think = SUB_Null;
372 void func_bobbing_controller_think()
375 self.nextthink = time + 0.1;
376 // calculate sinewave using makevectors
377 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
378 v = self.owner.destvec + self.owner.movedir * v_forward_y;
379 // * 10 so it will arrive in 0.1 sec
380 self.owner.velocity = (v - self.owner.origin) * 10;
383 void bobbing_blocked()
385 // no need to duplicate code
389 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
390 Brush model that moves back and forth on one axis (default Z).
391 speed : how long one cycle takes in seconds (default 4)
392 height : how far the cycle moves (default 32)
393 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
394 noise : path/name of looping .wav file to play.
395 dmg : Do this mutch dmg every .dmgtime intervall when blocked
398 void spawnfunc_func_bobbing()
400 local entity controller;
401 if (self.noise != "")
403 precache_sound(self.noise);
404 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
410 // center of bobbing motion
411 self.destvec = self.origin;
412 // time scale to get degrees
413 self.cnt = 360 / self.speed;
415 // damage when blocked
416 self.blocked = bobbing_blocked;
417 if(self.dmg & (!self.message))
418 self.message = " was squished";
419 if(self.dmg && (!self.message2))
420 self.message2 = "was squished by";
421 if(self.dmg && (!self.dmgtime))
423 self.dmgtime2 = time;
426 if (self.spawnflags & 1) // X
427 self.movedir = '1 0 0' * self.height;
428 else if (self.spawnflags & 2) // Y
429 self.movedir = '0 1 0' * self.height;
431 self.movedir = '0 0 1' * self.height;
433 if not(InitMovingBrushTrigger())
436 // wait for targets to spawn
437 controller = spawn();
438 controller.classname = "func_bobbing_controller";
439 controller.owner = self;
440 controller.nextthink = time + 1;
441 controller.think = func_bobbing_controller_think;
442 self.nextthink = self.ltime + 999999999;
443 self.think = SUB_Null;
445 // Savage: Reduce bandwith, critical on e.g. nexdm02
446 self.effects |= EF_LOWPRECISION;
449 // button and multiple button
452 void() button_return;
456 self.state = STATE_TOP;
457 self.nextthink = self.ltime + self.wait;
458 self.think = button_return;
459 activator = self.enemy;
461 self.frame = 1; // use alternate textures
466 self.state = STATE_BOTTOM;
471 self.state = STATE_DOWN;
472 SUB_CalcMove (self.pos1, self.speed, button_done);
473 self.frame = 0; // use normal textures
475 self.takedamage = DAMAGE_YES; // can be shot again
479 void button_blocked()
481 // do nothing, just don't come all the way back out
487 self.health = self.max_health;
488 self.takedamage = DAMAGE_NO; // will be reset upon return
490 if (self.state == STATE_UP || self.state == STATE_TOP)
493 if (self.noise != "")
494 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
496 self.state = STATE_UP;
497 SUB_CalcMove (self.pos2, self.speed, button_wait);
503 // if (activator.classname != "player")
505 // dprint(activator.classname);
506 // dprint(" triggered a button\n");
508 self.enemy = activator;
514 // if (activator.classname != "player")
516 // dprint(activator.classname);
517 // dprint(" touched a button\n");
521 if not(other.iscreature)
523 if(other.velocity * self.movedir < 0)
527 self.enemy = other.owner;
531 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
533 self.health = self.health - damage;
534 if (self.health <= 0)
536 // if (activator.classname != "player")
538 // dprint(activator.classname);
539 // dprint(" killed a button\n");
541 self.enemy = damage_attacker;
547 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
548 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.
550 "angle" determines the opening direction
551 "target" all entities with a matching targetname will be used
552 "speed" override the default 40 speed
553 "wait" override the default 1 second wait (-1 = never return)
554 "lip" override the default 4 pixel lip remaining at end of move
555 "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
562 void spawnfunc_func_button()
566 if not(InitMovingBrushTrigger())
568 self.effects |= EF_LOWPRECISION;
570 self.blocked = button_blocked;
571 self.use = button_use;
573 // if (self.health == 0) // all buttons are now shootable
577 self.max_health = self.health;
578 self.event_damage = button_damage;
579 self.takedamage = DAMAGE_YES;
582 self.touch = button_touch;
592 precache_sound(self.noise);
594 self.state = STATE_BOTTOM;
596 self.pos1 = self.origin;
597 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
601 float DOOR_START_OPEN = 1;
602 float DOOR_DONT_LINK = 4;
603 float DOOR_TOGGLE = 32;
607 Doors are similar to buttons, but can spawn a fat trigger field around them
608 to open without a touch, and they link together to form simultanious
611 Door.owner is the master door. If there is only one door, it points to itself.
612 If multiple doors, all will point to a single one.
614 Door.enemy chains from the master door through all doors linked in the chain.
619 =============================================================================
623 =============================================================================
628 void() door_rotating_go_down;
629 void() door_rotating_go_up;
634 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
635 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
638 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
639 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
641 //Dont chamge direction for dead or dying stuff
642 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
645 if (self.state == STATE_DOWN)
646 if (self.classname == "door")
651 door_rotating_go_up ();
654 if (self.classname == "door")
659 door_rotating_go_down ();
663 //gib dying stuff just to make sure
664 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
665 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
669 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
670 // if a door has a negative wait, it would never come back if blocked,
671 // so let it just squash the object to death real fast
672 /* if (self.wait >= 0)
674 if (self.state == STATE_DOWN)
685 if (self.noise1 != "")
686 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
687 self.state = STATE_TOP;
688 if (self.spawnflags & DOOR_TOGGLE)
689 return; // don't come down automatically
690 if (self.classname == "door")
692 self.think = door_go_down;
695 self.think = door_rotating_go_down;
697 self.nextthink = self.ltime + self.wait;
700 void door_hit_bottom()
702 if (self.noise1 != "")
703 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
704 self.state = STATE_BOTTOM;
709 if (self.noise2 != "")
710 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
713 self.takedamage = DAMAGE_YES;
714 self.health = self.max_health;
717 self.state = STATE_DOWN;
718 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
723 if (self.state == STATE_UP)
724 return; // already going up
726 if (self.state == STATE_TOP)
727 { // reset top wait time
728 self.nextthink = self.ltime + self.wait;
732 if (self.noise2 != "")
733 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
734 self.state = STATE_UP;
735 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
738 oldmessage = self.message;
741 self.message = oldmessage;
746 =============================================================================
750 =============================================================================
758 if (self.owner != self)
759 objerror ("door_fire: self.owner != self");
763 if (self.spawnflags & DOOR_TOGGLE)
765 if (self.state == STATE_UP || self.state == STATE_TOP)
770 if (self.classname == "door")
776 door_rotating_go_down ();
779 } while ( (self != starte) && (self != world) );
785 // trigger all paired doors
789 if (self.classname == "door")
794 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
795 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
797 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
798 self.pos2 = '0 0 0' - self.pos2;
800 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
801 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
802 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
804 door_rotating_go_up ();
808 } while ( (self != starte) && (self != world) );
817 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
828 void door_trigger_touch()
830 if (other.health < 1)
831 if not(other.iscreature && other.deadflag == DEAD_NO)
834 if (time < self.attack_finished_single)
836 self.attack_finished_single = time + 1;
845 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
848 self.health = self.health - damage;
849 if (self.health <= 0)
853 self.health = self.max_health;
854 self.takedamage = DAMAGE_NO; // wil be reset upon return
870 if(other.classname != "player")
872 if (self.owner.attack_finished_single > time)
875 self.owner.attack_finished_single = time + 2;
877 if (!(self.owner.dmg) && (self.owner.message != ""))
879 if (other.flags & FL_CLIENT)
880 centerprint (other, self.owner.message);
881 play2(other, "misc/talk.wav");
886 void door_rotating_blocked()
889 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
890 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
893 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
894 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
896 //Dont chamge direction for dead or dying stuff
897 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
900 if (self.state == STATE_DOWN)
901 door_rotating_go_up ();
903 door_rotating_go_down ();
906 //gib dying stuff just to make sure
907 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
908 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
912 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
913 // if a door has a negative wait, it would never come back if blocked,
914 // so let it just squash the object to death real fast
915 /* if (self.wait >= 0)
917 if (self.state == STATE_DOWN)
918 door_rotating_go_up ();
920 door_rotating_go_down ();
926 void door_rotating_hit_top()
928 if (self.noise1 != "")
929 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
930 self.state = STATE_TOP;
931 if (self.spawnflags & DOOR_TOGGLE)
932 return; // don't come down automatically
933 self.think = door_rotating_go_down;
934 self.nextthink = self.ltime + self.wait;
937 void door_rotating_hit_bottom()
939 if (self.noise1 != "")
940 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
941 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
943 self.pos2 = '0 0 0' - self.pos2;
946 self.state = STATE_BOTTOM;
949 void door_rotating_go_down()
951 if (self.noise2 != "")
952 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
955 self.takedamage = DAMAGE_YES;
956 self.health = self.max_health;
959 self.state = STATE_DOWN;
960 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
963 void door_rotating_go_up()
965 if (self.state == STATE_UP)
966 return; // already going up
968 if (self.state == STATE_TOP)
969 { // reset top wait time
970 self.nextthink = self.ltime + self.wait;
973 if (self.noise2 != "")
974 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
975 self.state = STATE_UP;
976 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
979 oldmessage = self.message;
982 self.message = oldmessage;
989 =============================================================================
993 =============================================================================
997 entity spawn_field(vector fmins, vector fmaxs)
999 local entity trigger;
1000 local vector t1, t2;
1003 trigger.classname = "doortriggerfield";
1004 trigger.movetype = MOVETYPE_NONE;
1005 trigger.solid = SOLID_TRIGGER;
1006 trigger.owner = self;
1007 trigger.touch = door_trigger_touch;
1011 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1016 float EntitiesTouching(entity e1, entity e2)
1018 if (e1.absmin_x > e2.absmax_x)
1020 if (e1.absmin_y > e2.absmax_y)
1022 if (e1.absmin_z > e2.absmax_z)
1024 if (e1.absmax_x < e2.absmin_x)
1026 if (e1.absmax_y < e2.absmin_y)
1028 if (e1.absmax_z < e2.absmin_z)
1043 local entity t, starte;
1044 local vector cmins, cmaxs;
1047 return; // already linked by another door
1048 if (self.spawnflags & 4)
1050 self.owner = self.enemy = self;
1058 self.trigger_field = spawn_field(self.absmin, self.absmax);
1060 return; // don't want to link this door
1063 cmins = self.absmin;
1064 cmaxs = self.absmax;
1071 self.owner = starte; // master door
1074 starte.health = self.health;
1076 starte.targetname = self.targetname;
1077 if (self.message != "")
1078 starte.message = self.message;
1080 t = find(t, classname, self.classname);
1083 self.enemy = starte; // make the chain a loop
1085 // shootable, or triggered doors just needed the owner/enemy links,
1086 // they don't spawn a field
1097 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1102 if (EntitiesTouching(self,t))
1105 objerror ("cross connected doors");
1110 if (t.absmin_x < cmins_x)
1111 cmins_x = t.absmin_x;
1112 if (t.absmin_y < cmins_y)
1113 cmins_y = t.absmin_y;
1114 if (t.absmin_z < cmins_z)
1115 cmins_z = t.absmin_z;
1116 if (t.absmax_x > cmaxs_x)
1117 cmaxs_x = t.absmax_x;
1118 if (t.absmax_y > cmaxs_y)
1119 cmaxs_y = t.absmax_y;
1120 if (t.absmax_z > cmaxs_z)
1121 cmaxs_z = t.absmax_z;
1128 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1129 if two doors touch, they are assumed to be connected and operate as a unit.
1131 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1133 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).
1135 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1136 "angle" determines the opening direction
1137 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1138 "health" if set, door must be shot open
1139 "speed" movement speed (100 default)
1140 "wait" wait before returning (3 default, -1 = never return)
1141 "lip" lip remaining at end of move (8 default)
1142 "dmg" damage to inflict when blocked (2 default)
1149 FIXME: only one sound set available at the time being
1153 void door_init_startopen()
1155 setorigin (self, self.pos2);
1156 self.pos2 = self.pos1;
1157 self.pos1 = self.origin;
1160 void spawnfunc_func_door()
1162 //if (!self.deathtype) // map makers can override this
1163 // self.deathtype = " got in the way";
1166 self.max_health = self.health;
1167 if not(InitMovingBrushTrigger())
1169 self.effects |= EF_LOWPRECISION;
1170 self.classname = "door";
1172 self.blocked = door_blocked;
1173 self.use = door_use;
1175 if(self.spawnflags & 8)
1178 if(self.dmg && (!self.message))
1179 self.message = "was squished";
1180 if(self.dmg && (!self.message2))
1181 self.message2 = "was squished by";
1183 if (self.sounds > 0)
1185 precache_sound ("plats/medplat1.wav");
1186 precache_sound ("plats/medplat2.wav");
1187 self.noise2 = "plats/medplat1.wav";
1188 self.noise1 = "plats/medplat2.wav";
1198 self.pos1 = self.origin;
1199 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1201 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1202 // but spawn in the open position
1203 if (self.spawnflags & DOOR_START_OPEN)
1204 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1206 self.state = STATE_BOTTOM;
1210 self.takedamage = DAMAGE_YES;
1211 self.event_damage = door_damage;
1217 self.touch = door_touch;
1219 // LinkDoors can't be done until all of the doors have been spawned, so
1220 // the sizes can be detected properly.
1221 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1224 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1225 if two doors touch, they are assumed to be connected and operate as a unit.
1227 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1229 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1230 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1231 must have set trigger_reverse to 1.
1232 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1234 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).
1236 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1237 "angle" determines the destination angle for opening. negative values reverse the direction.
1238 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1239 "health" if set, door must be shot open
1240 "speed" movement speed (100 default)
1241 "wait" wait before returning (3 default, -1 = never return)
1242 "dmg" damage to inflict when blocked (2 default)
1249 FIXME: only one sound set available at the time being
1252 void door_rotating_init_startopen()
1254 self.angles = self.movedir;
1255 self.pos2 = '0 0 0';
1256 self.pos1 = self.movedir;
1260 void spawnfunc_func_door_rotating()
1263 //if (!self.deathtype) // map makers can override this
1264 // self.deathtype = " got in the way";
1266 // I abuse "movedir" for denoting the axis for now
1267 if (self.spawnflags & 64) // X (untested)
1268 self.movedir = '0 0 1';
1269 else if (self.spawnflags & 128) // Y (untested)
1270 self.movedir = '1 0 0';
1272 self.movedir = '0 1 0';
1274 if (self.angles_y==0) self.angles_y = 90;
1276 self.movedir = self.movedir * self.angles_y;
1277 self.angles = '0 0 0';
1279 self.max_health = self.health;
1280 if not(InitMovingBrushTrigger())
1282 //self.effects |= EF_LOWPRECISION;
1283 self.classname = "door_rotating";
1285 self.blocked = door_blocked;
1286 self.use = door_use;
1288 if(self.spawnflags & 8)
1291 if(self.dmg && (!self.message))
1292 self.message = "was squished";
1293 if(self.dmg && (!self.message2))
1294 self.message2 = "was squished by";
1296 if (self.sounds > 0)
1298 precache_sound ("plats/medplat1.wav");
1299 precache_sound ("plats/medplat2.wav");
1300 self.noise2 = "plats/medplat1.wav";
1301 self.noise1 = "plats/medplat2.wav";
1308 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1310 self.pos1 = '0 0 0';
1311 self.pos2 = self.movedir;
1313 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1314 // but spawn in the open position
1315 if (self.spawnflags & DOOR_START_OPEN)
1316 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1318 self.state = STATE_BOTTOM;
1322 self.takedamage = DAMAGE_YES;
1323 self.event_damage = door_damage;
1329 self.touch = door_touch;
1331 // LinkDoors can't be done until all of the doors have been spawned, so
1332 // the sizes can be detected properly.
1333 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1337 =============================================================================
1341 =============================================================================
1344 void() fd_secret_move1;
1345 void() fd_secret_move2;
1346 void() fd_secret_move3;
1347 void() fd_secret_move4;
1348 void() fd_secret_move5;
1349 void() fd_secret_move6;
1350 void() fd_secret_done;
1352 float SECRET_OPEN_ONCE = 1; // stays open
1353 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1354 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1355 float SECRET_NO_SHOOT = 8; // only opened by trigger
1356 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1359 void fd_secret_use()
1362 string message_save;
1364 self.health = 10000;
1365 self.bot_attack = TRUE;
1367 // exit if still moving around...
1368 if (self.origin != self.oldorigin)
1371 message_save = self.message;
1372 self.message = ""; // no more message
1373 SUB_UseTargets(); // fire all targets / killtargets
1374 self.message = message_save;
1376 self.velocity = '0 0 0';
1378 // Make a sound, wait a little...
1380 if (self.noise1 != "")
1381 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1382 self.nextthink = self.ltime + 0.1;
1384 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1385 makevectors(self.mangle);
1389 if (self.spawnflags & SECRET_1ST_DOWN)
1390 self.t_width = fabs(v_up * self.size);
1392 self.t_width = fabs(v_right * self.size);
1396 self.t_length = fabs(v_forward * self.size);
1398 if (self.spawnflags & SECRET_1ST_DOWN)
1399 self.dest1 = self.origin - v_up * self.t_width;
1401 self.dest1 = self.origin + v_right * (self.t_width * temp);
1403 self.dest2 = self.dest1 + v_forward * self.t_length;
1404 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1405 if (self.noise2 != "")
1406 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1409 // Wait after first movement...
1410 void fd_secret_move1()
1412 self.nextthink = self.ltime + 1.0;
1413 self.think = fd_secret_move2;
1414 if (self.noise3 != "")
1415 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1418 // Start moving sideways w/sound...
1419 void fd_secret_move2()
1421 if (self.noise2 != "")
1422 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1423 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1426 // Wait here until time to go back...
1427 void fd_secret_move3()
1429 if (self.noise3 != "")
1430 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1431 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1433 self.nextthink = self.ltime + self.wait;
1434 self.think = fd_secret_move4;
1439 void fd_secret_move4()
1441 if (self.noise2 != "")
1442 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1443 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1447 void fd_secret_move5()
1449 self.nextthink = self.ltime + 1.0;
1450 self.think = fd_secret_move6;
1451 if (self.noise3 != "")
1452 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1455 void fd_secret_move6()
1457 if (self.noise2 != "")
1458 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1459 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1462 void fd_secret_done()
1464 if (self.spawnflags&SECRET_YES_SHOOT)
1466 self.health = 10000;
1467 self.takedamage = DAMAGE_YES;
1468 //self.th_pain = fd_secret_use;
1470 if (self.noise3 != "")
1471 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1474 void secret_blocked()
1476 if (time < self.attack_finished_single)
1478 self.attack_finished_single = time + 0.5;
1479 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1491 if not(other.iscreature)
1493 if (self.attack_finished_single > time)
1496 self.attack_finished_single = time + 2;
1500 if (other.flags & FL_CLIENT)
1501 centerprint (other, self.message);
1502 play2(other, "misc/talk.wav");
1507 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1508 Basic secret door. Slides back, then to the side. Angle determines direction.
1509 wait = # of seconds before coming back
1510 1st_left = 1st move is left of arrow
1511 1st_down = 1st move is down from arrow
1512 always_shoot = even if targeted, keep shootable
1513 t_width = override WIDTH to move back (or height if going down)
1514 t_length = override LENGTH to move sideways
1515 "dmg" damage to inflict when blocked (2 default)
1517 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1524 void spawnfunc_func_door_secret()
1526 /*if (!self.deathtype) // map makers can override this
1527 self.deathtype = " got in the way";*/
1533 self.mangle = self.angles;
1534 self.angles = '0 0 0';
1535 self.classname = "door";
1536 if not(InitMovingBrushTrigger())
1538 self.effects |= EF_LOWPRECISION;
1540 self.touch = secret_touch;
1541 self.blocked = secret_blocked;
1543 self.use = fd_secret_use;
1548 self.spawnflags |= SECRET_YES_SHOOT;
1550 if(self.spawnflags&SECRET_YES_SHOOT)
1552 self.health = 10000;
1553 self.takedamage = DAMAGE_YES;
1554 self.event_damage = fd_secret_use;
1556 self.oldorigin = self.origin;
1558 self.wait = 5; // 5 seconds before closing
1561 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1562 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1563 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
1564 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1565 height: amplitude modifier (default 1)
1566 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1567 noise: path/name of looping .wav file to play.
1568 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1572 void func_fourier_controller_think()
1577 self.nextthink = time + 0.1;
1579 n = floor((tokenize_sane(self.owner.netname)) / 5);
1580 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1582 v = self.owner.destvec;
1584 for(i = 0; i < n; ++i)
1586 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1587 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;
1590 // * 10 so it will arrive in 0.1 sec
1591 self.owner.velocity = (v - self.owner.origin) * 10;
1594 void spawnfunc_func_fourier()
1596 local entity controller;
1597 if (self.noise != "")
1599 precache_sound(self.noise);
1600 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
1607 self.destvec = self.origin;
1608 self.cnt = 360 / self.speed;
1610 self.blocked = rotating_blocked;
1611 if(self.dmg & (!self.message))
1612 self.message = " was squished";
1613 if(self.dmg && (!self.message2))
1614 self.message2 = "was squished by";
1615 if(self.dmg && (!self.dmgtime))
1616 self.dmgtime = 0.25;
1617 self.dmgtime2 = time;
1619 if(self.netname == "")
1620 self.netname = "1 0 0 0 1";
1622 if not(InitMovingBrushTrigger())
1625 // wait for targets to spawn
1626 controller = spawn();
1627 controller.classname = "func_fourier_controller";
1628 controller.owner = self;
1629 controller.nextthink = time + 1;
1630 controller.think = func_fourier_controller_think;
1631 self.nextthink = self.ltime + 999999999;
1632 self.think = SUB_Null;
1634 // Savage: Reduce bandwith, critical on e.g. nexdm02
1635 self.effects |= EF_LOWPRECISION;
1638 // reusing some fields havocbots declared
1639 .entity wp00, wp01, wp02, wp03;
1641 .float targetfactor, target2factor, target3factor, target4factor;
1643 void func_vectormamamam_controller_think()
1648 self.nextthink = time + 0.1;
1650 v = self.owner.destvec;
1652 e = self.owner.wp00;
1654 v = v + e.origin + 0.1 * e.velocity;
1656 e = self.owner.wp01;
1658 v = v + e.origin + 0.1 * e.velocity;
1660 e = self.owner.wp02;
1662 v = v + e.origin + 0.1 * e.velocity;
1664 e = self.owner.wp03;
1666 v = v + e.origin + 0.1 * e.velocity;
1668 self.owner.velocity = (v - self.owner.origin) * 10;
1671 void func_vectormamamam_findtarget()
1677 if(self.target != "")
1679 self.wp00 = find(world, targetname, self.target);
1681 s0 = s0 + self.wp00.origin * self.targetfactor;
1684 if(self.target2 != "")
1686 self.wp01 = find(world, targetname, self.target2);
1688 s0 = s0 + self.wp01.origin * self.target2factor;
1691 if(self.target3 != "")
1693 self.wp02 = find(world, targetname, self.target3);
1695 s0 = s0 + self.wp02.origin * self.target3factor;
1698 if(self.target4 != "")
1700 self.wp03 = find(world, targetname, self.target4);
1702 s0 = s0 + self.wp03.origin * self.target4factor;
1705 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1706 objerror("No reference entity found, so there is nothing to move. Aborting.");
1708 self.destvec = self.origin - s0;
1710 local entity controller;
1711 controller = spawn();
1712 controller.classname = "func_vectormamamam_controller";
1713 controller.owner = self;
1714 controller.nextthink = time + 1;
1715 controller.think = func_vectormamamam_controller_think;
1718 void spawnfunc_func_vectormamamam()
1720 if (self.noise != "")
1722 precache_sound(self.noise);
1723 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
1726 if(!self.targetfactor)
1727 self.targetfactor = 1;
1729 if(!self.target2factor)
1730 self.target2factor = 1;
1732 if(!self.target3factor)
1733 self.target3factor = 1;
1735 if(!self.target4factor)
1736 self.target4factor = 1;
1738 self.blocked = rotating_blocked;
1739 if(self.dmg & (!self.message))
1740 self.message = " was squished";
1741 if(self.dmg && (!self.message2))
1742 self.message2 = "was squished by";
1743 if(self.dmg && (!self.dmgtime))
1744 self.dmgtime = 0.25;
1745 self.dmgtime2 = time;
1747 if(self.netname == "")
1748 self.netname = "1 0 0 0 1";
1750 if not(InitMovingBrushTrigger())
1753 // wait for targets to spawn
1754 self.nextthink = self.ltime + 999999999;
1755 self.think = SUB_Null;
1757 // Savage: Reduce bandwith, critical on e.g. nexdm02
1758 self.effects |= EF_LOWPRECISION;
1760 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);