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()
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;
64 setsize (trigger, tmin, tmax);
68 // otherwise, something is fishy...
70 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
75 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
77 self.think = plat_go_down;
78 self.nextthink = self.ltime + 3;
81 void plat_hit_bottom()
83 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
89 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
91 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
96 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
98 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
101 void plat_center_touch()
103 if not(other.iscreature)
106 if (other.health <= 0)
112 else if (self.state == 1)
113 self.nextthink = self.ltime + 1; // delay going down
116 void plat_outside_touch()
118 if not(other.iscreature)
121 if (other.health <= 0)
129 void plat_trigger_use()
132 return; // already activated
139 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
140 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
142 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
143 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
144 // Gib dead/dying stuff
145 if(other.deadflag != DEAD_NO)
146 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
151 else if (self.state == 3)
153 // when in other states, then the plat_crush event came delayed after
154 // plat state already had changed
155 // this isn't a bug per se!
163 objerror ("plat_use: not in up state");
167 .string sound1, sound2;
173 setorigin (self, self.pos1);
179 setorigin (self, self.pos2);
181 self.use = plat_trigger_use;
185 void set_platmovetype(entity e, string s)
187 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
192 n = tokenize_console(s);
193 e.platmovetype_start = stof(argv(0));
194 e.platmovetype_end = stof(argv(0));
196 e.platmovetype_end = stof(argv(1));
199 void spawnfunc_path_corner()
201 // setup values for overriding train movement
202 // if a second value does not exist, both start and end speeds are the single value specified
203 set_platmovetype(self, self.platmovetype);
205 void spawnfunc_func_plat()
207 if (self.sounds == 0)
210 if(self.spawnflags & 4)
213 if(self.dmg && (!self.message))
214 self.message = "was squished";
215 if(self.dmg && (!self.message2))
216 self.message2 = "was squished by";
218 if (self.sounds == 1)
220 precache_sound ("plats/plat1.wav");
221 precache_sound ("plats/plat2.wav");
222 self.noise = "plats/plat1.wav";
223 self.noise1 = "plats/plat2.wav";
226 if (self.sounds == 2)
228 precache_sound ("plats/medplat1.wav");
229 precache_sound ("plats/medplat2.wav");
230 self.noise = "plats/medplat1.wav";
231 self.noise1 = "plats/medplat2.wav";
236 precache_sound (self.sound1);
237 self.noise = self.sound1;
241 precache_sound (self.sound2);
242 self.noise1 = self.sound2;
245 self.mangle = self.angles;
246 self.angles = '0 0 0';
248 self.classname = "plat";
249 if not(InitMovingBrushTrigger())
251 self.effects |= EF_LOWPRECISION;
252 setsize (self, self.mins , self.maxs);
254 self.blocked = plat_crush;
261 self.height = self.size_z - self.lip;
263 self.pos1 = self.origin;
264 self.pos2 = self.origin;
265 self.pos2_z = self.origin_z - self.height;
267 self.reset = plat_reset;
270 plat_spawn_inside_trigger (); // the "start moving" trigger
273 .float train_wait_turning;
284 // if using bezier curves and turning is enabled, the train will turn toward the next point while waiting
285 if(!self.train_wait_turning)
286 if(self.spawnflags & 1 && self.bezier_turn && self.wait >= 0)
290 targ = find(world, targetname, self.target);
291 org = normalize(targ.origin);
292 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
293 self.train_wait_turning = TRUE;
298 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
300 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
302 self.train_wait_turning = FALSE;
307 self.think = train_next;
308 self.nextthink = self.ltime + self.wait;
317 targ = find(world, targetname, self.target);
318 self.target = targ.target;
319 if (self.spawnflags & 1)
321 cp = find(world, target, targ.targetname); // get the previous corner first
322 cp = find(world, targetname, cp.curve); // now get its second target (the control point)
323 if(cp.targetname == "")
324 cp_org = targ.origin - self.mins; // no control point found, assume a straight line to the destination
326 cp_org = cp.origin - self.mins;
329 objerror("train_next: no next target");
330 self.wait = targ.wait;
334 // override train movement type if necessary
335 if(targ.platmovetype_start || targ.platmovetype_end)
336 set_platmovetype(self, targ.platmovetype);
338 set_platmovetype(self, self.platmovetype);
342 if (self.spawnflags & 1)
343 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
345 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
349 if (self.spawnflags & 1)
350 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
352 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
356 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
359 void func_train_find()
362 targ = find(world, targetname, self.target);
363 self.target = targ.target;
365 objerror("func_train_find: no next target");
366 setorigin(self, targ.origin - self.mins);
367 self.nextthink = self.ltime + 1;
368 self.think = train_next;
371 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
372 Ridable platform, targets spawnfunc_path_corner path to follow.
373 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
374 target : targetname of first spawnfunc_path_corner (starts here)
376 void spawnfunc_func_train()
378 if (self.noise != "")
379 precache_sound(self.noise);
382 objerror("func_train without a target");
385 if (self.spawnflags & 2)
386 self.bezier_turn = TRUE;
388 if not(InitMovingBrushTrigger())
390 self.effects |= EF_LOWPRECISION;
392 // wait for targets to spawn
393 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
395 self.blocked = generic_plat_blocked;
396 if(self.dmg & (!self.message))
397 self.message = " was squished";
398 if(self.dmg && (!self.message2))
399 self.message2 = "was squished by";
400 if(self.dmg && (!self.dmgtime))
402 self.dmgtime2 = time;
404 set_platmovetype(self, self.platmovetype);
406 // TODO make a reset function for this one
409 void func_rotating_setactive(float astate)
412 if (astate == ACTIVE_TOGGLE)
414 if(self.active == ACTIVE_ACTIVE)
415 self.active = ACTIVE_NOT;
417 self.active = ACTIVE_ACTIVE;
420 self.active = astate;
422 if(self.active == ACTIVE_NOT)
423 self.avelocity = '0 0 0';
425 self.avelocity = self.pos1;
428 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
429 Brush model that spins in place on one axis (default Z).
430 speed : speed to rotate (in degrees per second)
431 noise : path/name of looping .wav file to play.
432 dmg : Do this mutch dmg every .dmgtime intervall when blocked
436 void spawnfunc_func_rotating()
438 if (self.noise != "")
440 precache_sound(self.noise);
441 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
444 self.active = ACTIVE_ACTIVE;
445 self.setactive = func_rotating_setactive;
449 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
450 if (self.spawnflags & 4) // X (untested)
451 self.avelocity = '0 0 1' * self.speed;
452 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
453 else if (self.spawnflags & 8) // Y (untested)
454 self.avelocity = '1 0 0' * self.speed;
455 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
457 self.avelocity = '0 1 0' * self.speed;
459 self.pos1 = self.avelocity;
461 if(self.dmg & (!self.message))
462 self.message = " was squished";
463 if(self.dmg && (!self.message2))
464 self.message2 = "was squished by";
467 if(self.dmg && (!self.dmgtime))
470 self.dmgtime2 = time;
472 if not(InitMovingBrushTrigger())
474 // no EF_LOWPRECISION here, as rounding angles is bad
476 self.blocked = generic_plat_blocked;
478 // wait for targets to spawn
479 self.nextthink = self.ltime + 999999999;
480 self.think = SUB_Null;
482 // TODO make a reset function for this one
486 void func_bobbing_controller_think()
489 self.nextthink = time + 0.1;
491 if not (self.owner.active == ACTIVE_ACTIVE)
493 self.owner.velocity = '0 0 0';
497 // calculate sinewave using makevectors
498 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
499 v = self.owner.destvec + self.owner.movedir * v_forward_y;
500 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
501 // * 10 so it will arrive in 0.1 sec
502 self.owner.velocity = (v - self.owner.origin) * 10;
505 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
506 Brush model that moves back and forth on one axis (default Z).
507 speed : how long one cycle takes in seconds (default 4)
508 height : how far the cycle moves (default 32)
509 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
510 noise : path/name of looping .wav file to play.
511 dmg : Do this mutch dmg every .dmgtime intervall when blocked
514 void spawnfunc_func_bobbing()
517 if (self.noise != "")
519 precache_sound(self.noise);
520 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
526 // center of bobbing motion
527 self.destvec = self.origin;
528 // time scale to get degrees
529 self.cnt = 360 / self.speed;
531 self.active = ACTIVE_ACTIVE;
533 // damage when blocked
534 self.blocked = generic_plat_blocked;
535 if(self.dmg & (!self.message))
536 self.message = " was squished";
537 if(self.dmg && (!self.message2))
538 self.message2 = "was squished by";
539 if(self.dmg && (!self.dmgtime))
541 self.dmgtime2 = time;
544 if (self.spawnflags & 1) // X
545 self.movedir = '1 0 0' * self.height;
546 else if (self.spawnflags & 2) // Y
547 self.movedir = '0 1 0' * self.height;
549 self.movedir = '0 0 1' * self.height;
551 if not(InitMovingBrushTrigger())
554 // wait for targets to spawn
555 controller = spawn();
556 controller.classname = "func_bobbing_controller";
557 controller.owner = self;
558 controller.nextthink = time + 1;
559 controller.think = func_bobbing_controller_think;
560 self.nextthink = self.ltime + 999999999;
561 self.think = SUB_Null;
563 // Savage: Reduce bandwith, critical on e.g. nexdm02
564 self.effects |= EF_LOWPRECISION;
566 // TODO make a reset function for this one
570 void func_pendulum_controller_think()
573 self.nextthink = time + 0.1;
575 if not (self.owner.active == ACTIVE_ACTIVE)
577 self.owner.avelocity_x = 0;
581 // calculate sinewave using makevectors
582 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
583 v = self.owner.speed * v_forward_y + self.cnt;
584 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
586 // * 10 so it will arrive in 0.1 sec
587 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
591 void spawnfunc_func_pendulum()
594 if (self.noise != "")
596 precache_sound(self.noise);
597 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
600 self.active = ACTIVE_ACTIVE;
602 // keys: angle, speed, phase, noise, freq
606 // not initializing self.dmg to 2, to allow damageless pendulum
608 if(self.dmg & (!self.message))
609 self.message = " was squished";
610 if(self.dmg && (!self.message2))
611 self.message2 = "was squished by";
612 if(self.dmg && (!self.dmgtime))
614 self.dmgtime2 = time;
616 self.blocked = generic_plat_blocked;
618 self.avelocity_z = 0.0000001;
619 if not(InitMovingBrushTrigger())
624 // find pendulum length (same formula as Q3A)
625 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
628 // copy initial angle
629 self.cnt = self.angles_z;
631 // wait for targets to spawn
632 controller = spawn();
633 controller.classname = "func_pendulum_controller";
634 controller.owner = self;
635 controller.nextthink = time + 1;
636 controller.think = func_pendulum_controller_think;
637 self.nextthink = self.ltime + 999999999;
638 self.think = SUB_Null;
640 //self.effects |= EF_LOWPRECISION;
642 // TODO make a reset function for this one
645 // button and multiple button
648 void() button_return;
652 self.state = STATE_TOP;
653 self.nextthink = self.ltime + self.wait;
654 self.think = button_return;
655 activator = self.enemy;
657 self.frame = 1; // use alternate textures
662 self.state = STATE_BOTTOM;
667 self.state = STATE_DOWN;
668 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
669 self.frame = 0; // use normal textures
671 self.takedamage = DAMAGE_YES; // can be shot again
675 void button_blocked()
677 // do nothing, just don't come all the way back out
683 self.health = self.max_health;
684 self.takedamage = DAMAGE_NO; // will be reset upon return
686 if (self.state == STATE_UP || self.state == STATE_TOP)
689 if (self.noise != "")
690 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
692 self.state = STATE_UP;
693 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
698 self.health = self.max_health;
699 setorigin(self, self.pos1);
700 self.frame = 0; // use normal textures
701 self.state = STATE_BOTTOM;
703 self.takedamage = DAMAGE_YES; // can be shot again
708 // if (activator.classname != "player")
710 // dprint(activator.classname);
711 // dprint(" triggered a button\n");
714 if not (self.active == ACTIVE_ACTIVE)
717 self.enemy = activator;
723 // if (activator.classname != "player")
725 // dprint(activator.classname);
726 // dprint(" touched a button\n");
730 if not(other.iscreature)
732 if(other.velocity * self.movedir < 0)
736 self.enemy = other.owner;
740 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
742 if(self.spawnflags & DOOR_NOSPLASH)
743 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
745 self.health = self.health - damage;
746 if (self.health <= 0)
748 // if (activator.classname != "player")
750 // dprint(activator.classname);
751 // dprint(" killed a button\n");
753 self.enemy = damage_attacker;
759 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
760 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.
762 "angle" determines the opening direction
763 "target" all entities with a matching targetname will be used
764 "speed" override the default 40 speed
765 "wait" override the default 1 second wait (-1 = never return)
766 "lip" override the default 4 pixel lip remaining at end of move
767 "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
774 void spawnfunc_func_button()
778 if not(InitMovingBrushTrigger())
780 self.effects |= EF_LOWPRECISION;
782 self.blocked = button_blocked;
783 self.use = button_use;
785 // if (self.health == 0) // all buttons are now shootable
789 self.max_health = self.health;
790 self.event_damage = button_damage;
791 self.takedamage = DAMAGE_YES;
794 self.touch = button_touch;
804 precache_sound(self.noise);
806 self.active = ACTIVE_ACTIVE;
808 self.pos1 = self.origin;
809 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
810 self.flags |= FL_NOTARGET;
816 float DOOR_START_OPEN = 1;
817 float DOOR_DONT_LINK = 4;
818 float DOOR_TOGGLE = 32;
822 Doors are similar to buttons, but can spawn a fat trigger field around them
823 to open without a touch, and they link together to form simultanious
826 Door.owner is the master door. If there is only one door, it points to itself.
827 If multiple doors, all will point to a single one.
829 Door.enemy chains from the master door through all doors linked in the chain.
834 =============================================================================
838 =============================================================================
843 void() door_rotating_go_down;
844 void() door_rotating_go_up;
849 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
850 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
853 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
854 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
856 //Dont chamge direction for dead or dying stuff
857 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
860 if (self.state == STATE_DOWN)
861 if (self.classname == "door")
866 door_rotating_go_up ();
869 if (self.classname == "door")
874 door_rotating_go_down ();
878 //gib dying stuff just to make sure
879 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
880 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
884 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
885 // if a door has a negative wait, it would never come back if blocked,
886 // so let it just squash the object to death real fast
887 /* if (self.wait >= 0)
889 if (self.state == STATE_DOWN)
900 if (self.noise1 != "")
901 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
902 self.state = STATE_TOP;
903 if (self.spawnflags & DOOR_TOGGLE)
904 return; // don't come down automatically
905 if (self.classname == "door")
907 self.think = door_go_down;
910 self.think = door_rotating_go_down;
912 self.nextthink = self.ltime + self.wait;
915 void door_hit_bottom()
917 if (self.noise1 != "")
918 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
919 self.state = STATE_BOTTOM;
924 if (self.noise2 != "")
925 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
928 self.takedamage = DAMAGE_YES;
929 self.health = self.max_health;
932 self.state = STATE_DOWN;
933 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
938 if (self.state == STATE_UP)
939 return; // already going up
941 if (self.state == STATE_TOP)
942 { // reset top wait time
943 self.nextthink = self.ltime + self.wait;
947 if (self.noise2 != "")
948 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
949 self.state = STATE_UP;
950 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
953 oldmessage = self.message;
956 self.message = oldmessage;
962 =============================================================================
966 =============================================================================
969 float door_check_keys(void) {
979 if not(door.itemkeys)
982 // this door require a key
983 // only a player can have a key
984 if (other.classname != "player")
987 if (item_keys_usekey(door, other)) {
988 // some keys were used
989 if (other.key_door_messagetime <= time) {
990 play2(other, "misc/talk.wav");
991 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
992 other.key_door_messagetime = time + 2;
996 if (other.key_door_messagetime <= time) {
997 play2(other, "misc/talk.wav");
998 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
999 other.key_door_messagetime = time + 2;
1003 if (door.itemkeys) {
1004 // door is now unlocked
1005 play2(other, "misc/talk.wav");
1006 centerprint(other, "Door unlocked!");
1018 if (self.owner != self)
1019 objerror ("door_fire: self.owner != self");
1023 if (self.spawnflags & DOOR_TOGGLE)
1025 if (self.state == STATE_UP || self.state == STATE_TOP)
1030 if (self.classname == "door")
1036 door_rotating_go_down ();
1039 } while ( (self != starte) && (self != world) );
1045 // trigger all paired doors
1049 if (self.classname == "door")
1054 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1055 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1057 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1058 self.pos2 = '0 0 0' - self.pos2;
1060 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1061 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1062 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1064 door_rotating_go_up ();
1068 } while ( (self != starte) && (self != world) );
1077 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1089 void door_trigger_touch()
1091 if (other.health < 1)
1092 if not(other.iscreature && other.deadflag == DEAD_NO)
1095 if (time < self.attack_finished_single)
1098 // check if door is locked
1099 if (!door_check_keys())
1102 self.attack_finished_single = time + 1;
1111 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1114 if(self.spawnflags & DOOR_NOSPLASH)
1115 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1117 self.health = self.health - damage;
1119 if (self.itemkeys) {
1120 // don't allow opening doors through damage if keys are required
1124 if (self.health <= 0)
1128 self.health = self.max_health;
1129 self.takedamage = DAMAGE_NO; // wil be reset upon return
1145 if(other.classname != "player")
1147 if (self.owner.attack_finished_single > time)
1150 self.owner.attack_finished_single = time + 2;
1152 if (!(self.owner.dmg) && (self.owner.message != ""))
1154 if (other.flags & FL_CLIENT)
1155 centerprint (other, self.owner.message);
1156 play2(other, "misc/talk.wav");
1161 void door_generic_plat_blocked()
1164 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1165 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1168 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1169 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1171 //Dont chamge direction for dead or dying stuff
1172 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1175 if (self.state == STATE_DOWN)
1176 door_rotating_go_up ();
1178 door_rotating_go_down ();
1181 //gib dying stuff just to make sure
1182 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1183 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1187 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1188 // if a door has a negative wait, it would never come back if blocked,
1189 // so let it just squash the object to death real fast
1190 /* if (self.wait >= 0)
1192 if (self.state == STATE_DOWN)
1193 door_rotating_go_up ();
1195 door_rotating_go_down ();
1201 void door_rotating_hit_top()
1203 if (self.noise1 != "")
1204 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1205 self.state = STATE_TOP;
1206 if (self.spawnflags & DOOR_TOGGLE)
1207 return; // don't come down automatically
1208 self.think = door_rotating_go_down;
1209 self.nextthink = self.ltime + self.wait;
1212 void door_rotating_hit_bottom()
1214 if (self.noise1 != "")
1215 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1216 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1218 self.pos2 = '0 0 0' - self.pos2;
1221 self.state = STATE_BOTTOM;
1224 void door_rotating_go_down()
1226 if (self.noise2 != "")
1227 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1228 if (self.max_health)
1230 self.takedamage = DAMAGE_YES;
1231 self.health = self.max_health;
1234 self.state = STATE_DOWN;
1235 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1238 void door_rotating_go_up()
1240 if (self.state == STATE_UP)
1241 return; // already going up
1243 if (self.state == STATE_TOP)
1244 { // reset top wait time
1245 self.nextthink = self.ltime + self.wait;
1248 if (self.noise2 != "")
1249 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1250 self.state = STATE_UP;
1251 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1254 oldmessage = self.message;
1257 self.message = oldmessage;
1264 =============================================================================
1268 =============================================================================
1272 entity spawn_field(vector fmins, vector fmaxs)
1278 trigger.classname = "doortriggerfield";
1279 trigger.movetype = MOVETYPE_NONE;
1280 trigger.solid = SOLID_TRIGGER;
1281 trigger.owner = self;
1282 trigger.touch = door_trigger_touch;
1286 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1291 float EntitiesTouching(entity e1, entity e2)
1293 if (e1.absmin_x > e2.absmax_x)
1295 if (e1.absmin_y > e2.absmax_y)
1297 if (e1.absmin_z > e2.absmax_z)
1299 if (e1.absmax_x < e2.absmin_x)
1301 if (e1.absmax_y < e2.absmin_y)
1303 if (e1.absmax_z < e2.absmin_z)
1319 vector cmins, cmaxs;
1322 return; // already linked by another door
1323 if (self.spawnflags & 4)
1325 self.owner = self.enemy = self;
1333 self.trigger_field = spawn_field(self.absmin, self.absmax);
1335 return; // don't want to link this door
1338 cmins = self.absmin;
1339 cmaxs = self.absmax;
1346 self.owner = starte; // master door
1349 starte.health = self.health;
1351 starte.targetname = self.targetname;
1352 if (self.message != "")
1353 starte.message = self.message;
1355 t = find(t, classname, self.classname);
1358 self.enemy = starte; // make the chain a loop
1360 // shootable, or triggered doors just needed the owner/enemy links,
1361 // they don't spawn a field
1372 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1377 if (EntitiesTouching(self,t))
1380 objerror ("cross connected doors");
1385 if (t.absmin_x < cmins_x)
1386 cmins_x = t.absmin_x;
1387 if (t.absmin_y < cmins_y)
1388 cmins_y = t.absmin_y;
1389 if (t.absmin_z < cmins_z)
1390 cmins_z = t.absmin_z;
1391 if (t.absmax_x > cmaxs_x)
1392 cmaxs_x = t.absmax_x;
1393 if (t.absmax_y > cmaxs_y)
1394 cmaxs_y = t.absmax_y;
1395 if (t.absmax_z > cmaxs_z)
1396 cmaxs_z = t.absmax_z;
1403 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1404 if two doors touch, they are assumed to be connected and operate as a unit.
1406 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1408 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).
1410 GOLD_KEY causes the door to open only if the activator holds a gold key.
1412 SILVER_KEY causes the door to open only if the activator holds a silver key.
1414 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1415 "angle" determines the opening direction
1416 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1417 "health" if set, door must be shot open
1418 "speed" movement speed (100 default)
1419 "wait" wait before returning (3 default, -1 = never return)
1420 "lip" lip remaining at end of move (8 default)
1421 "dmg" damage to inflict when blocked (2 default)
1428 FIXME: only one sound set available at the time being
1432 void door_init_startopen()
1434 setorigin (self, self.pos2);
1435 self.pos2 = self.pos1;
1436 self.pos1 = self.origin;
1441 setorigin(self, self.pos1);
1442 self.velocity = '0 0 0';
1443 self.state = STATE_BOTTOM;
1444 self.think = SUB_Null;
1447 // spawnflags require key (for now only func_door)
1448 #define SPAWNFLAGS_GOLD_KEY 8
1449 #define SPAWNFLAGS_SILVER_KEY 16
1450 void spawnfunc_func_door()
1452 // Quake 1 keys compatibility
1453 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1454 self.itemkeys |= ITEM_KEY_BIT(0);
1455 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1456 self.itemkeys |= ITEM_KEY_BIT(1);
1458 //if (!self.deathtype) // map makers can override this
1459 // self.deathtype = " got in the way";
1462 self.max_health = self.health;
1463 if not(InitMovingBrushTrigger())
1465 self.effects |= EF_LOWPRECISION;
1466 self.classname = "door";
1468 self.blocked = door_blocked;
1469 self.use = door_use;
1471 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1472 // if(self.spawnflags & 8)
1473 // self.dmg = 10000;
1475 if(self.dmg && (!self.message))
1476 self.message = "was squished";
1477 if(self.dmg && (!self.message2))
1478 self.message2 = "was squished by";
1480 if (self.sounds > 0)
1482 precache_sound ("plats/medplat1.wav");
1483 precache_sound ("plats/medplat2.wav");
1484 self.noise2 = "plats/medplat1.wav";
1485 self.noise1 = "plats/medplat2.wav";
1495 self.pos1 = self.origin;
1496 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1498 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1499 // but spawn in the open position
1500 if (self.spawnflags & DOOR_START_OPEN)
1501 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1503 self.state = STATE_BOTTOM;
1507 self.takedamage = DAMAGE_YES;
1508 self.event_damage = door_damage;
1514 self.touch = door_touch;
1516 // LinkDoors can't be done until all of the doors have been spawned, so
1517 // the sizes can be detected properly.
1518 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1520 self.reset = door_reset;
1523 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1524 if two doors touch, they are assumed to be connected and operate as a unit.
1526 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1528 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1529 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1530 must have set trigger_reverse to 1.
1531 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1533 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).
1535 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1536 "angle" determines the destination angle for opening. negative values reverse the direction.
1537 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1538 "health" if set, door must be shot open
1539 "speed" movement speed (100 default)
1540 "wait" wait before returning (3 default, -1 = never return)
1541 "dmg" damage to inflict when blocked (2 default)
1548 FIXME: only one sound set available at the time being
1551 void door_rotating_reset()
1553 self.angles = self.pos1;
1554 self.avelocity = '0 0 0';
1555 self.state = STATE_BOTTOM;
1556 self.think = SUB_Null;
1559 void door_rotating_init_startopen()
1561 self.angles = self.movedir;
1562 self.pos2 = '0 0 0';
1563 self.pos1 = self.movedir;
1567 void spawnfunc_func_door_rotating()
1570 //if (!self.deathtype) // map makers can override this
1571 // self.deathtype = " got in the way";
1573 // I abuse "movedir" for denoting the axis for now
1574 if (self.spawnflags & 64) // X (untested)
1575 self.movedir = '0 0 1';
1576 else if (self.spawnflags & 128) // Y (untested)
1577 self.movedir = '1 0 0';
1579 self.movedir = '0 1 0';
1581 if (self.angles_y==0) self.angles_y = 90;
1583 self.movedir = self.movedir * self.angles_y;
1584 self.angles = '0 0 0';
1586 self.max_health = self.health;
1587 self.avelocity = self.movedir;
1588 if not(InitMovingBrushTrigger())
1590 self.velocity = '0 0 0';
1591 //self.effects |= EF_LOWPRECISION;
1592 self.classname = "door_rotating";
1594 self.blocked = door_blocked;
1595 self.use = door_use;
1597 if(self.spawnflags & 8)
1600 if(self.dmg && (!self.message))
1601 self.message = "was squished";
1602 if(self.dmg && (!self.message2))
1603 self.message2 = "was squished by";
1605 if (self.sounds > 0)
1607 precache_sound ("plats/medplat1.wav");
1608 precache_sound ("plats/medplat2.wav");
1609 self.noise2 = "plats/medplat1.wav";
1610 self.noise1 = "plats/medplat2.wav";
1617 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1619 self.pos1 = '0 0 0';
1620 self.pos2 = self.movedir;
1622 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1623 // but spawn in the open position
1624 if (self.spawnflags & DOOR_START_OPEN)
1625 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1627 self.state = STATE_BOTTOM;
1631 self.takedamage = DAMAGE_YES;
1632 self.event_damage = door_damage;
1638 self.touch = door_touch;
1640 // LinkDoors can't be done until all of the doors have been spawned, so
1641 // the sizes can be detected properly.
1642 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1644 self.reset = door_rotating_reset;
1648 =============================================================================
1652 =============================================================================
1655 void() fd_secret_move1;
1656 void() fd_secret_move2;
1657 void() fd_secret_move3;
1658 void() fd_secret_move4;
1659 void() fd_secret_move5;
1660 void() fd_secret_move6;
1661 void() fd_secret_done;
1663 float SECRET_OPEN_ONCE = 1; // stays open
1664 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1665 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1666 float SECRET_NO_SHOOT = 8; // only opened by trigger
1667 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1670 void fd_secret_use()
1673 string message_save;
1675 self.health = 10000;
1676 self.bot_attack = TRUE;
1678 // exit if still moving around...
1679 if (self.origin != self.oldorigin)
1682 message_save = self.message;
1683 self.message = ""; // no more message
1684 SUB_UseTargets(); // fire all targets / killtargets
1685 self.message = message_save;
1687 self.velocity = '0 0 0';
1689 // Make a sound, wait a little...
1691 if (self.noise1 != "")
1692 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1693 self.nextthink = self.ltime + 0.1;
1695 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1696 makevectors(self.mangle);
1700 if (self.spawnflags & SECRET_1ST_DOWN)
1701 self.t_width = fabs(v_up * self.size);
1703 self.t_width = fabs(v_right * self.size);
1707 self.t_length = fabs(v_forward * self.size);
1709 if (self.spawnflags & SECRET_1ST_DOWN)
1710 self.dest1 = self.origin - v_up * self.t_width;
1712 self.dest1 = self.origin + v_right * (self.t_width * temp);
1714 self.dest2 = self.dest1 + v_forward * self.t_length;
1715 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1716 if (self.noise2 != "")
1717 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1720 // Wait after first movement...
1721 void fd_secret_move1()
1723 self.nextthink = self.ltime + 1.0;
1724 self.think = fd_secret_move2;
1725 if (self.noise3 != "")
1726 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1729 // Start moving sideways w/sound...
1730 void fd_secret_move2()
1732 if (self.noise2 != "")
1733 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1734 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1737 // Wait here until time to go back...
1738 void fd_secret_move3()
1740 if (self.noise3 != "")
1741 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1742 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1744 self.nextthink = self.ltime + self.wait;
1745 self.think = fd_secret_move4;
1750 void fd_secret_move4()
1752 if (self.noise2 != "")
1753 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1754 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1758 void fd_secret_move5()
1760 self.nextthink = self.ltime + 1.0;
1761 self.think = fd_secret_move6;
1762 if (self.noise3 != "")
1763 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1766 void fd_secret_move6()
1768 if (self.noise2 != "")
1769 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1770 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1773 void fd_secret_done()
1775 if (self.spawnflags&SECRET_YES_SHOOT)
1777 self.health = 10000;
1778 self.takedamage = DAMAGE_YES;
1779 //self.th_pain = fd_secret_use;
1781 if (self.noise3 != "")
1782 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1785 void secret_blocked()
1787 if (time < self.attack_finished_single)
1789 self.attack_finished_single = time + 0.5;
1790 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1802 if not(other.iscreature)
1804 if (self.attack_finished_single > time)
1807 self.attack_finished_single = time + 2;
1811 if (other.flags & FL_CLIENT)
1812 centerprint (other, self.message);
1813 play2(other, "misc/talk.wav");
1819 if (self.spawnflags&SECRET_YES_SHOOT)
1821 self.health = 10000;
1822 self.takedamage = DAMAGE_YES;
1824 setorigin(self, self.oldorigin);
1825 self.think = SUB_Null;
1828 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1829 Basic secret door. Slides back, then to the side. Angle determines direction.
1830 wait = # of seconds before coming back
1831 1st_left = 1st move is left of arrow
1832 1st_down = 1st move is down from arrow
1833 always_shoot = even if targeted, keep shootable
1834 t_width = override WIDTH to move back (or height if going down)
1835 t_length = override LENGTH to move sideways
1836 "dmg" damage to inflict when blocked (2 default)
1838 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1845 void spawnfunc_func_door_secret()
1847 /*if (!self.deathtype) // map makers can override this
1848 self.deathtype = " got in the way";*/
1854 self.mangle = self.angles;
1855 self.angles = '0 0 0';
1856 self.classname = "door";
1857 if not(InitMovingBrushTrigger())
1859 self.effects |= EF_LOWPRECISION;
1861 self.touch = secret_touch;
1862 self.blocked = secret_blocked;
1864 self.use = fd_secret_use;
1869 self.spawnflags |= SECRET_YES_SHOOT;
1871 if(self.spawnflags&SECRET_YES_SHOOT)
1873 self.health = 10000;
1874 self.takedamage = DAMAGE_YES;
1875 self.event_damage = fd_secret_use;
1877 self.oldorigin = self.origin;
1879 self.wait = 5; // 5 seconds before closing
1881 self.reset = secret_reset;
1885 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1886 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1887 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
1888 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1889 height: amplitude modifier (default 32)
1890 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1891 noise: path/name of looping .wav file to play.
1892 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1896 void func_fourier_controller_think()
1901 self.nextthink = time + 0.1;
1902 if not (self.owner.active == ACTIVE_ACTIVE)
1904 self.owner.velocity = '0 0 0';
1909 n = floor((tokenize_console(self.owner.netname)) / 5);
1910 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1912 v = self.owner.destvec;
1914 for(i = 0; i < n; ++i)
1916 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1917 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;
1920 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1921 // * 10 so it will arrive in 0.1 sec
1922 self.owner.velocity = (v - self.owner.origin) * 10;
1925 void spawnfunc_func_fourier()
1928 if (self.noise != "")
1930 precache_sound(self.noise);
1931 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1938 self.destvec = self.origin;
1939 self.cnt = 360 / self.speed;
1941 self.blocked = generic_plat_blocked;
1942 if(self.dmg & (!self.message))
1943 self.message = " was squished";
1944 if(self.dmg && (!self.message2))
1945 self.message2 = "was squished by";
1946 if(self.dmg && (!self.dmgtime))
1947 self.dmgtime = 0.25;
1948 self.dmgtime2 = time;
1950 if(self.netname == "")
1951 self.netname = "1 0 0 0 1";
1953 if not(InitMovingBrushTrigger())
1956 self.active = ACTIVE_ACTIVE;
1958 // wait for targets to spawn
1959 controller = spawn();
1960 controller.classname = "func_fourier_controller";
1961 controller.owner = self;
1962 controller.nextthink = time + 1;
1963 controller.think = func_fourier_controller_think;
1964 self.nextthink = self.ltime + 999999999;
1965 self.think = SUB_Null;
1967 // Savage: Reduce bandwith, critical on e.g. nexdm02
1968 self.effects |= EF_LOWPRECISION;
1970 // TODO make a reset function for this one
1973 // reusing some fields havocbots declared
1974 .entity wp00, wp01, wp02, wp03;
1976 .float targetfactor, target2factor, target3factor, target4factor;
1977 .vector targetnormal, target2normal, target3normal, target4normal;
1979 vector func_vectormamamam_origin(entity o, float t)
1991 p = e.origin + t * e.velocity;
1993 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1995 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2001 p = e.origin + t * e.velocity;
2003 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2005 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2011 p = e.origin + t * e.velocity;
2013 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2015 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2021 p = e.origin + t * e.velocity;
2023 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2025 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2031 void func_vectormamamam_controller_think()
2033 self.nextthink = time + 0.1;
2035 if not (self.owner.active == ACTIVE_ACTIVE)
2037 self.owner.velocity = '0 0 0';
2041 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2042 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2045 void func_vectormamamam_findtarget()
2047 if(self.target != "")
2048 self.wp00 = find(world, targetname, self.target);
2050 if(self.target2 != "")
2051 self.wp01 = find(world, targetname, self.target2);
2053 if(self.target3 != "")
2054 self.wp02 = find(world, targetname, self.target3);
2056 if(self.target4 != "")
2057 self.wp03 = find(world, targetname, self.target4);
2059 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2060 objerror("No reference entity found, so there is nothing to move. Aborting.");
2062 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2065 controller = spawn();
2066 controller.classname = "func_vectormamamam_controller";
2067 controller.owner = self;
2068 controller.nextthink = time + 1;
2069 controller.think = func_vectormamamam_controller_think;
2072 void spawnfunc_func_vectormamamam()
2074 if (self.noise != "")
2076 precache_sound(self.noise);
2077 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2080 if(!self.targetfactor)
2081 self.targetfactor = 1;
2083 if(!self.target2factor)
2084 self.target2factor = 1;
2086 if(!self.target3factor)
2087 self.target3factor = 1;
2089 if(!self.target4factor)
2090 self.target4factor = 1;
2092 if(vlen(self.targetnormal))
2093 self.targetnormal = normalize(self.targetnormal);
2095 if(vlen(self.target2normal))
2096 self.target2normal = normalize(self.target2normal);
2098 if(vlen(self.target3normal))
2099 self.target3normal = normalize(self.target3normal);
2101 if(vlen(self.target4normal))
2102 self.target4normal = normalize(self.target4normal);
2104 self.blocked = generic_plat_blocked;
2105 if(self.dmg & (!self.message))
2106 self.message = " was squished";
2107 if(self.dmg && (!self.message2))
2108 self.message2 = "was squished by";
2109 if(self.dmg && (!self.dmgtime))
2110 self.dmgtime = 0.25;
2111 self.dmgtime2 = time;
2113 if(self.netname == "")
2114 self.netname = "1 0 0 0 1";
2116 if not(InitMovingBrushTrigger())
2119 // wait for targets to spawn
2120 self.nextthink = self.ltime + 999999999;
2121 self.think = SUB_Null;
2123 // Savage: Reduce bandwith, critical on e.g. nexdm02
2124 self.effects |= EF_LOWPRECISION;
2126 self.active = ACTIVE_ACTIVE;
2128 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2131 void conveyor_think()
2135 // set myself as current conveyor where possible
2136 for(e = world; (e = findentity(e, conveyor, self)); )
2141 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2142 if(!e.conveyor.state)
2145 vector emin = e.absmin;
2146 vector emax = e.absmax;
2147 if(self.solid == SOLID_BSP)
2152 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2153 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2157 for(e = world; (e = findentity(e, conveyor, self)); )
2159 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2160 continue; // done in SV_PlayerPhysics
2162 setorigin(e, e.origin + self.movedir * sys_frametime);
2163 move_out_of_solid(e);
2164 UpdateCSQCProjectile(e);
2166 // stupid conveyor code
2167 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2168 if(trace_fraction > 0)
2169 setorigin(e, trace_endpos);
2174 self.nextthink = time;
2179 self.state = !self.state;
2182 void conveyor_reset()
2184 self.state = (self.spawnflags & 1);
2187 void conveyor_init()
2191 self.movedir = self.movedir * self.speed;
2192 self.think = conveyor_think;
2193 self.nextthink = time;
2196 self.use = conveyor_use;
2197 self.reset = conveyor_reset;
2204 void spawnfunc_trigger_conveyor()
2211 void spawnfunc_func_conveyor()
2214 InitMovingBrushTrigger();
2215 self.movetype = MOVETYPE_NONE;