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, self.speed, plat_hit_bottom);
96 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
98 SUB_CalcMove (self.pos1, 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!
161 self.use = func_null;
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 spawnfunc_path_corner() { }
186 void spawnfunc_func_plat()
188 if (self.sounds == 0)
191 if(self.spawnflags & 4)
194 if(self.dmg && (self.message == ""))
195 self.message = "was squished";
196 if(self.dmg && (self.message2 == ""))
197 self.message2 = "was squished by";
199 if (self.sounds == 1)
201 precache_sound ("plats/plat1.wav");
202 precache_sound ("plats/plat2.wav");
203 self.noise = "plats/plat1.wav";
204 self.noise1 = "plats/plat2.wav";
207 if (self.sounds == 2)
209 precache_sound ("plats/medplat1.wav");
210 precache_sound ("plats/medplat2.wav");
211 self.noise = "plats/medplat1.wav";
212 self.noise1 = "plats/medplat2.wav";
217 precache_sound (self.sound1);
218 self.noise = self.sound1;
222 precache_sound (self.sound2);
223 self.noise1 = self.sound2;
226 self.mangle = self.angles;
227 self.angles = '0 0 0';
229 self.classname = "plat";
230 if not(InitMovingBrushTrigger())
232 self.effects |= EF_LOWPRECISION;
233 setsize (self, self.mins , self.maxs);
235 self.blocked = plat_crush;
242 self.height = self.size_z - self.lip;
244 self.pos1 = self.origin;
245 self.pos2 = self.origin;
246 self.pos2_z = self.origin_z - self.height;
248 self.reset = plat_reset;
251 plat_spawn_inside_trigger (); // the "start moving" trigger
259 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
267 self.think = train_next;
268 self.nextthink = self.ltime + self.wait;
282 targ = find(world, targetname, self.target);
284 self.target = targ.target;
285 if (self.target == "")
286 objerror("train_next: no next target");
287 self.wait = targ.wait;
292 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
294 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
297 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
300 void func_train_find()
303 targ = find(world, targetname, self.target);
304 self.target = targ.target;
305 if (self.target == "")
306 objerror("func_train_find: no next target");
307 setorigin(self, targ.origin - self.mins);
308 self.nextthink = self.ltime + 1;
309 self.think = train_next;
312 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
313 Ridable platform, targets spawnfunc_path_corner path to follow.
314 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
315 target : targetname of first spawnfunc_path_corner (starts here)
317 void spawnfunc_func_train()
319 if (self.noise != "")
320 precache_sound(self.noise);
322 if (self.target == "")
323 objerror("func_train without a target");
327 if not(InitMovingBrushTrigger())
329 self.effects |= EF_LOWPRECISION;
331 // wait for targets to spawn
332 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
334 self.blocked = generic_plat_blocked;
335 if(self.dmg && (self.message == ""))
336 self.message = " was squished";
337 if(self.dmg && (self.message2 == ""))
338 self.message2 = "was squished by";
339 if(self.dmg && (!self.dmgtime))
341 self.dmgtime2 = time;
343 // TODO make a reset function for this one
346 void func_rotating_setactive(float astate)
349 if (astate == ACTIVE_TOGGLE)
351 if(self.active == ACTIVE_ACTIVE)
352 self.active = ACTIVE_NOT;
354 self.active = ACTIVE_ACTIVE;
357 self.active = astate;
359 if(self.active == ACTIVE_NOT)
360 self.avelocity = '0 0 0';
362 self.avelocity = self.pos1;
365 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
366 Brush model that spins in place on one axis (default Z).
367 speed : speed to rotate (in degrees per second)
368 noise : path/name of looping .wav file to play.
369 dmg : Do this mutch dmg every .dmgtime intervall when blocked
373 void spawnfunc_func_rotating()
375 if (self.noise != "")
377 precache_sound(self.noise);
378 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
381 self.active = ACTIVE_ACTIVE;
382 self.setactive = func_rotating_setactive;
386 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
387 if (self.spawnflags & 4) // X (untested)
388 self.avelocity = '0 0 1' * self.speed;
389 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
390 else if (self.spawnflags & 8) // Y (untested)
391 self.avelocity = '1 0 0' * self.speed;
392 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
394 self.avelocity = '0 1 0' * self.speed;
396 self.pos1 = self.avelocity;
398 if(self.dmg && (self.message == ""))
399 self.message = " was squished";
400 if(self.dmg && (self.message2 == ""))
401 self.message2 = "was squished by";
404 if(self.dmg && (!self.dmgtime))
407 self.dmgtime2 = time;
409 if not(InitMovingBrushTrigger())
411 // no EF_LOWPRECISION here, as rounding angles is bad
413 self.blocked = generic_plat_blocked;
415 // wait for targets to spawn
416 self.nextthink = self.ltime + 999999999;
417 self.think = SUB_NullThink; // for PushMove
419 // TODO make a reset function for this one
423 void func_bobbing_controller_think()
426 self.nextthink = time + 0.1;
428 if not (self.owner.active == ACTIVE_ACTIVE)
430 self.owner.velocity = '0 0 0';
434 // calculate sinewave using makevectors
435 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
436 v = self.owner.destvec + self.owner.movedir * v_forward_y;
437 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
438 // * 10 so it will arrive in 0.1 sec
439 self.owner.velocity = (v - self.owner.origin) * 10;
442 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
443 Brush model that moves back and forth on one axis (default Z).
444 speed : how long one cycle takes in seconds (default 4)
445 height : how far the cycle moves (default 32)
446 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
447 noise : path/name of looping .wav file to play.
448 dmg : Do this mutch dmg every .dmgtime intervall when blocked
451 void spawnfunc_func_bobbing()
454 if (self.noise != "")
456 precache_sound(self.noise);
457 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
463 // center of bobbing motion
464 self.destvec = self.origin;
465 // time scale to get degrees
466 self.cnt = 360 / self.speed;
468 self.active = ACTIVE_ACTIVE;
470 // damage when blocked
471 self.blocked = generic_plat_blocked;
472 if(self.dmg && (self.message == ""))
473 self.message = " was squished";
474 if(self.dmg && (self.message2 == ""))
475 self.message2 = "was squished by";
476 if(self.dmg && (!self.dmgtime))
478 self.dmgtime2 = time;
481 if (self.spawnflags & 1) // X
482 self.movedir = '1 0 0' * self.height;
483 else if (self.spawnflags & 2) // Y
484 self.movedir = '0 1 0' * self.height;
486 self.movedir = '0 0 1' * self.height;
488 if not(InitMovingBrushTrigger())
491 // wait for targets to spawn
492 controller = spawn();
493 controller.classname = "func_bobbing_controller";
494 controller.owner = self;
495 controller.nextthink = time + 1;
496 controller.think = func_bobbing_controller_think;
497 self.nextthink = self.ltime + 999999999;
498 self.think = SUB_NullThink; // for PushMove
500 // Savage: Reduce bandwith, critical on e.g. nexdm02
501 self.effects |= EF_LOWPRECISION;
503 // TODO make a reset function for this one
507 void func_pendulum_controller_think()
510 self.nextthink = time + 0.1;
512 if not (self.owner.active == ACTIVE_ACTIVE)
514 self.owner.avelocity_x = 0;
518 // calculate sinewave using makevectors
519 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
520 v = self.owner.speed * v_forward_y + self.cnt;
521 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
523 // * 10 so it will arrive in 0.1 sec
524 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
528 void spawnfunc_func_pendulum()
531 if (self.noise != "")
533 precache_sound(self.noise);
534 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
537 self.active = ACTIVE_ACTIVE;
539 // keys: angle, speed, phase, noise, freq
543 // not initializing self.dmg to 2, to allow damageless pendulum
545 if(self.dmg && (self.message == ""))
546 self.message = " was squished";
547 if(self.dmg && (self.message2 == ""))
548 self.message2 = "was squished by";
549 if(self.dmg && (!self.dmgtime))
551 self.dmgtime2 = time;
553 self.blocked = generic_plat_blocked;
555 self.avelocity_z = 0.0000001;
556 if not(InitMovingBrushTrigger())
561 // find pendulum length (same formula as Q3A)
562 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
565 // copy initial angle
566 self.cnt = self.angles_z;
568 // wait for targets to spawn
569 controller = spawn();
570 controller.classname = "func_pendulum_controller";
571 controller.owner = self;
572 controller.nextthink = time + 1;
573 controller.think = func_pendulum_controller_think;
574 self.nextthink = self.ltime + 999999999;
575 self.think = SUB_NullThink; // for PushMove
577 //self.effects |= EF_LOWPRECISION;
579 // TODO make a reset function for this one
582 // button and multiple button
585 void() button_return;
589 self.state = STATE_TOP;
590 self.nextthink = self.ltime + self.wait;
591 self.think = button_return;
592 activator = self.enemy;
594 self.frame = 1; // use alternate textures
599 self.state = STATE_BOTTOM;
604 self.state = STATE_DOWN;
605 SUB_CalcMove (self.pos1, self.speed, button_done);
606 self.frame = 0; // use normal textures
608 self.takedamage = DAMAGE_YES; // can be shot again
612 void button_blocked()
614 // do nothing, just don't come all the way back out
620 self.health = self.max_health;
621 self.takedamage = DAMAGE_NO; // will be reset upon return
623 if (self.state == STATE_UP || self.state == STATE_TOP)
626 if (self.noise != "")
627 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
629 self.state = STATE_UP;
630 SUB_CalcMove (self.pos2, self.speed, button_wait);
635 self.health = self.max_health;
636 setorigin(self, self.pos1);
637 self.frame = 0; // use normal textures
638 self.state = STATE_BOTTOM;
640 self.takedamage = DAMAGE_YES; // can be shot again
645 if not (self.active == ACTIVE_ACTIVE)
648 self.enemy = activator;
656 if not(other.iscreature)
658 if(other.velocity * self.movedir < 0)
662 self.enemy = other.owner;
666 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
668 if(self.spawnflags & DOOR_NOSPLASH)
669 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
671 self.health = self.health - damage;
672 if (self.health <= 0)
674 self.enemy = damage_attacker;
680 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
681 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.
683 "angle" determines the opening direction
684 "target" all entities with a matching targetname will be used
685 "speed" override the default 40 speed
686 "wait" override the default 1 second wait (-1 = never return)
687 "lip" override the default 4 pixel lip remaining at end of move
688 "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
695 void spawnfunc_func_button()
699 if not(InitMovingBrushTrigger())
701 self.effects |= EF_LOWPRECISION;
703 self.blocked = button_blocked;
704 self.use = button_use;
706 // if (self.health == 0) // all buttons are now shootable
710 self.max_health = self.health;
711 self.event_damage = button_damage;
712 self.takedamage = DAMAGE_YES;
715 self.touch = button_touch;
725 precache_sound(self.noise);
727 self.active = ACTIVE_ACTIVE;
729 self.pos1 = self.origin;
730 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
731 self.flags |= FL_NOTARGET;
737 float DOOR_START_OPEN = 1;
738 float DOOR_DONT_LINK = 4;
739 float DOOR_TOGGLE = 32;
743 Doors are similar to buttons, but can spawn a fat trigger field around them
744 to open without a touch, and they link together to form simultanious
747 Door.owner is the master door. If there is only one door, it points to itself.
748 If multiple doors, all will point to a single one.
750 Door.enemy chains from the master door through all doors linked in the chain.
755 =============================================================================
759 =============================================================================
764 void() door_rotating_go_down;
765 void() door_rotating_go_up;
770 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
771 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
774 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
775 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
777 //Dont chamge direction for dead or dying stuff
778 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
781 if (self.state == STATE_DOWN)
782 if (self.classname == "door")
787 door_rotating_go_up ();
790 if (self.classname == "door")
795 door_rotating_go_down ();
799 //gib dying stuff just to make sure
800 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
801 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
805 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
806 // if a door has a negative wait, it would never come back if blocked,
807 // so let it just squash the object to death real fast
808 /* if (self.wait >= 0)
810 if (self.state == STATE_DOWN)
821 if (self.noise1 != "")
822 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
823 self.state = STATE_TOP;
824 if (self.spawnflags & DOOR_TOGGLE)
825 return; // don't come down automatically
826 if (self.classname == "door")
828 self.think = door_go_down;
831 self.think = door_rotating_go_down;
833 self.nextthink = self.ltime + self.wait;
836 void door_hit_bottom()
838 if (self.noise1 != "")
839 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
840 self.state = STATE_BOTTOM;
845 if (self.noise2 != "")
846 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
849 self.takedamage = DAMAGE_YES;
850 self.health = self.max_health;
853 self.state = STATE_DOWN;
854 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
859 if (self.state == STATE_UP)
860 return; // already going up
862 if (self.state == STATE_TOP)
863 { // reset top wait time
864 self.nextthink = self.ltime + self.wait;
868 if (self.noise2 != "")
869 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
870 self.state = STATE_UP;
871 SUB_CalcMove (self.pos2, self.speed, door_hit_top);
874 oldmessage = self.message;
877 self.message = oldmessage;
883 =============================================================================
887 =============================================================================
890 float door_check_keys(void) {
900 if not(door.itemkeys)
903 // this door require a key
904 // only a player can have a key
905 if not(IS_PLAYER(other))
908 if (item_keys_usekey(door, other)) {
909 // some keys were used
910 if (other.key_door_messagetime <= time) {
911 play2(other, "misc/talk.wav");
912 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
913 other.key_door_messagetime = time + 2;
917 if (other.key_door_messagetime <= time) {
918 play2(other, "misc/talk.wav");
919 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
920 other.key_door_messagetime = time + 2;
925 // door is now unlocked
926 play2(other, "misc/talk.wav");
927 centerprint(other, "Door unlocked!");
939 if (self.owner != self)
940 objerror ("door_fire: self.owner != self");
944 if (self.spawnflags & DOOR_TOGGLE)
946 if (self.state == STATE_UP || self.state == STATE_TOP)
951 if (self.classname == "door")
957 door_rotating_go_down ();
960 } while ( (self != starte) && (self != world) );
966 // trigger all paired doors
970 if (self.classname == "door")
975 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
976 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
978 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
979 self.pos2 = '0 0 0' - self.pos2;
981 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
982 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
983 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
985 door_rotating_go_up ();
989 } while ( (self != starte) && (self != world) );
998 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1010 void door_trigger_touch()
1012 if (other.health < 1)
1013 if not(other.iscreature && other.deadflag == DEAD_NO)
1016 if (time < self.attack_finished_single)
1019 // check if door is locked
1020 if (!door_check_keys())
1023 self.attack_finished_single = time + 1;
1032 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1035 if(self.spawnflags & DOOR_NOSPLASH)
1036 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1038 self.health = self.health - damage;
1040 if (self.itemkeys) {
1041 // don't allow opening doors through damage if keys are required
1045 if (self.health <= 0)
1049 self.health = self.max_health;
1050 self.takedamage = DAMAGE_NO; // wil be reset upon return
1066 if not(IS_PLAYER(other))
1068 if (self.owner.attack_finished_single > time)
1071 self.owner.attack_finished_single = time + 2;
1073 if (!(self.owner.dmg) && (self.owner.message != ""))
1075 if (IS_CLIENT(other))
1076 centerprint (other, self.owner.message);
1077 play2(other, "misc/talk.wav");
1082 void door_generic_plat_blocked()
1085 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1086 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1089 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1090 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1092 //Dont chamge direction for dead or dying stuff
1093 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1096 if (self.state == STATE_DOWN)
1097 door_rotating_go_up ();
1099 door_rotating_go_down ();
1102 //gib dying stuff just to make sure
1103 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1104 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1108 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1109 // if a door has a negative wait, it would never come back if blocked,
1110 // so let it just squash the object to death real fast
1111 /* if (self.wait >= 0)
1113 if (self.state == STATE_DOWN)
1114 door_rotating_go_up ();
1116 door_rotating_go_down ();
1122 void door_rotating_hit_top()
1124 if (self.noise1 != "")
1125 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1126 self.state = STATE_TOP;
1127 if (self.spawnflags & DOOR_TOGGLE)
1128 return; // don't come down automatically
1129 self.think = door_rotating_go_down;
1130 self.nextthink = self.ltime + self.wait;
1133 void door_rotating_hit_bottom()
1135 if (self.noise1 != "")
1136 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1137 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1139 self.pos2 = '0 0 0' - self.pos2;
1142 self.state = STATE_BOTTOM;
1145 void door_rotating_go_down()
1147 if (self.noise2 != "")
1148 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1149 if (self.max_health)
1151 self.takedamage = DAMAGE_YES;
1152 self.health = self.max_health;
1155 self.state = STATE_DOWN;
1156 SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1159 void door_rotating_go_up()
1161 if (self.state == STATE_UP)
1162 return; // already going up
1164 if (self.state == STATE_TOP)
1165 { // reset top wait time
1166 self.nextthink = self.ltime + self.wait;
1169 if (self.noise2 != "")
1170 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1171 self.state = STATE_UP;
1172 SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1175 oldmessage = self.message;
1178 self.message = oldmessage;
1185 =============================================================================
1189 =============================================================================
1193 entity spawn_field(vector fmins, vector fmaxs)
1199 trigger.classname = "doortriggerfield";
1200 trigger.movetype = MOVETYPE_NONE;
1201 trigger.solid = SOLID_TRIGGER;
1202 trigger.owner = self;
1203 trigger.touch = door_trigger_touch;
1207 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1212 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1214 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1220 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1223 if (e1.absmin_x > e2.absmax_x + DELTA)
1225 if (e1.absmin_y > e2.absmax_y + DELTA)
1227 if (e1.absmin_z > e2.absmax_z + DELTA)
1229 if (e2.absmin_x > e1.absmax_x + DELTA)
1231 if (e2.absmin_y > e1.absmax_y + DELTA)
1233 if (e2.absmin_z > e1.absmax_z + DELTA)
1248 vector cmins, cmaxs;
1251 return; // already linked by another door
1252 if (self.spawnflags & 4)
1254 self.owner = self.enemy = self;
1262 self.trigger_field = spawn_field(self.absmin, self.absmax);
1264 return; // don't want to link this door
1267 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1269 // set owner, and make a loop of the chain
1270 dprint("LinkDoors: linking doors:");
1271 for(t = self; ; t = t.enemy)
1273 dprint(" ", etos(t));
1275 if(t.enemy == world)
1283 // collect health, targetname, message, size
1284 cmins = self.absmin;
1285 cmaxs = self.absmax;
1286 for(t = self; ; t = t.enemy)
1288 if(t.health && !self.health)
1289 self.health = t.health;
1290 if((t.targetname != "") && (self.targetname == ""))
1291 self.targetname = t.targetname;
1292 if((t.message != "") && (self.message == ""))
1293 self.message = t.message;
1294 if (t.absmin_x < cmins_x)
1295 cmins_x = t.absmin_x;
1296 if (t.absmin_y < cmins_y)
1297 cmins_y = t.absmin_y;
1298 if (t.absmin_z < cmins_z)
1299 cmins_z = t.absmin_z;
1300 if (t.absmax_x > cmaxs_x)
1301 cmaxs_x = t.absmax_x;
1302 if (t.absmax_y > cmaxs_y)
1303 cmaxs_y = t.absmax_y;
1304 if (t.absmax_z > cmaxs_z)
1305 cmaxs_z = t.absmax_z;
1310 // distribute health, targetname, message
1311 for(t = self; t; t = t.enemy)
1313 t.health = self.health;
1314 t.targetname = self.targetname;
1315 t.message = self.message;
1320 // shootable, or triggered doors just needed the owner/enemy links,
1321 // they don't spawn a field
1330 self.trigger_field = spawn_field(cmins, cmaxs);
1334 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1335 if two doors touch, they are assumed to be connected and operate as a unit.
1337 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1339 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).
1341 GOLD_KEY causes the door to open only if the activator holds a gold key.
1343 SILVER_KEY causes the door to open only if the activator holds a silver key.
1345 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1346 "angle" determines the opening direction
1347 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1348 "health" if set, door must be shot open
1349 "speed" movement speed (100 default)
1350 "wait" wait before returning (3 default, -1 = never return)
1351 "lip" lip remaining at end of move (8 default)
1352 "dmg" damage to inflict when blocked (2 default)
1359 FIXME: only one sound set available at the time being
1363 void door_init_startopen()
1365 setorigin (self, self.pos2);
1366 self.pos2 = self.pos1;
1367 self.pos1 = self.origin;
1372 setorigin(self, self.pos1);
1373 self.velocity = '0 0 0';
1374 self.state = STATE_BOTTOM;
1375 self.think = func_null;
1379 // spawnflags require key (for now only func_door)
1380 #define SPAWNFLAGS_GOLD_KEY 8
1381 #define SPAWNFLAGS_SILVER_KEY 16
1382 void spawnfunc_func_door()
1384 // Quake 1 keys compatibility
1385 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1386 self.itemkeys |= ITEM_KEY_BIT(0);
1387 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1388 self.itemkeys |= ITEM_KEY_BIT(1);
1390 //if (!self.deathtype) // map makers can override this
1391 // self.deathtype = " got in the way";
1394 self.max_health = self.health;
1395 if not(InitMovingBrushTrigger())
1397 self.effects |= EF_LOWPRECISION;
1398 self.classname = "door";
1400 self.blocked = door_blocked;
1401 self.use = door_use;
1403 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1404 // if(self.spawnflags & 8)
1405 // self.dmg = 10000;
1407 if(self.dmg && (self.message == ""))
1408 self.message = "was squished";
1409 if(self.dmg && (self.message2 == ""))
1410 self.message2 = "was squished by";
1412 if (self.sounds > 0)
1414 precache_sound ("plats/medplat1.wav");
1415 precache_sound ("plats/medplat2.wav");
1416 self.noise2 = "plats/medplat1.wav";
1417 self.noise1 = "plats/medplat2.wav";
1427 self.pos1 = self.origin;
1428 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1430 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1431 // but spawn in the open position
1432 if (self.spawnflags & DOOR_START_OPEN)
1433 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1435 self.state = STATE_BOTTOM;
1439 self.takedamage = DAMAGE_YES;
1440 self.event_damage = door_damage;
1446 self.touch = door_touch;
1448 // LinkDoors can't be done until all of the doors have been spawned, so
1449 // the sizes can be detected properly.
1450 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1452 self.reset = door_reset;
1455 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1456 if two doors touch, they are assumed to be connected and operate as a unit.
1458 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1460 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1461 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1462 must have set trigger_reverse to 1.
1463 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1465 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).
1467 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1468 "angle" determines the destination angle for opening. negative values reverse the direction.
1469 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1470 "health" if set, door must be shot open
1471 "speed" movement speed (100 default)
1472 "wait" wait before returning (3 default, -1 = never return)
1473 "dmg" damage to inflict when blocked (2 default)
1480 FIXME: only one sound set available at the time being
1483 void door_rotating_reset()
1485 self.angles = self.pos1;
1486 self.avelocity = '0 0 0';
1487 self.state = STATE_BOTTOM;
1488 self.think = func_null;
1492 void door_rotating_init_startopen()
1494 self.angles = self.movedir;
1495 self.pos2 = '0 0 0';
1496 self.pos1 = self.movedir;
1500 void spawnfunc_func_door_rotating()
1503 //if (!self.deathtype) // map makers can override this
1504 // self.deathtype = " got in the way";
1506 // I abuse "movedir" for denoting the axis for now
1507 if (self.spawnflags & 64) // X (untested)
1508 self.movedir = '0 0 1';
1509 else if (self.spawnflags & 128) // Y (untested)
1510 self.movedir = '1 0 0';
1512 self.movedir = '0 1 0';
1514 if (self.angles_y==0) self.angles_y = 90;
1516 self.movedir = self.movedir * self.angles_y;
1517 self.angles = '0 0 0';
1519 self.max_health = self.health;
1520 self.avelocity = self.movedir;
1521 if not(InitMovingBrushTrigger())
1523 self.velocity = '0 0 0';
1524 //self.effects |= EF_LOWPRECISION;
1525 self.classname = "door_rotating";
1527 self.blocked = door_blocked;
1528 self.use = door_use;
1530 if(self.spawnflags & 8)
1533 if(self.dmg && (self.message == ""))
1534 self.message = "was squished";
1535 if(self.dmg && (self.message2 == ""))
1536 self.message2 = "was squished by";
1538 if (self.sounds > 0)
1540 precache_sound ("plats/medplat1.wav");
1541 precache_sound ("plats/medplat2.wav");
1542 self.noise2 = "plats/medplat1.wav";
1543 self.noise1 = "plats/medplat2.wav";
1550 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1552 self.pos1 = '0 0 0';
1553 self.pos2 = self.movedir;
1555 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1556 // but spawn in the open position
1557 if (self.spawnflags & DOOR_START_OPEN)
1558 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1560 self.state = STATE_BOTTOM;
1564 self.takedamage = DAMAGE_YES;
1565 self.event_damage = door_damage;
1571 self.touch = door_touch;
1573 // LinkDoors can't be done until all of the doors have been spawned, so
1574 // the sizes can be detected properly.
1575 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1577 self.reset = door_rotating_reset;
1581 =============================================================================
1585 =============================================================================
1588 void() fd_secret_move1;
1589 void() fd_secret_move2;
1590 void() fd_secret_move3;
1591 void() fd_secret_move4;
1592 void() fd_secret_move5;
1593 void() fd_secret_move6;
1594 void() fd_secret_done;
1596 float SECRET_OPEN_ONCE = 1; // stays open
1597 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1598 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1599 float SECRET_NO_SHOOT = 8; // only opened by trigger
1600 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1602 void fd_secret_use()
1605 string message_save;
1607 self.health = 10000;
1608 self.bot_attack = TRUE;
1610 // exit if still moving around...
1611 if (self.origin != self.oldorigin)
1614 message_save = self.message;
1615 self.message = ""; // no more message
1616 SUB_UseTargets(); // fire all targets / killtargets
1617 self.message = message_save;
1619 self.velocity = '0 0 0';
1621 // Make a sound, wait a little...
1623 if (self.noise1 != "")
1624 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1625 self.nextthink = self.ltime + 0.1;
1627 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1628 makevectors(self.mangle);
1632 if (self.spawnflags & SECRET_1ST_DOWN)
1633 self.t_width = fabs(v_up * self.size);
1635 self.t_width = fabs(v_right * self.size);
1639 self.t_length = fabs(v_forward * self.size);
1641 if (self.spawnflags & SECRET_1ST_DOWN)
1642 self.dest1 = self.origin - v_up * self.t_width;
1644 self.dest1 = self.origin + v_right * (self.t_width * temp);
1646 self.dest2 = self.dest1 + v_forward * self.t_length;
1647 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1648 if (self.noise2 != "")
1649 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1652 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1657 // Wait after first movement...
1658 void fd_secret_move1()
1660 self.nextthink = self.ltime + 1.0;
1661 self.think = fd_secret_move2;
1662 if (self.noise3 != "")
1663 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1666 // Start moving sideways w/sound...
1667 void fd_secret_move2()
1669 if (self.noise2 != "")
1670 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1671 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1674 // Wait here until time to go back...
1675 void fd_secret_move3()
1677 if (self.noise3 != "")
1678 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1679 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1681 self.nextthink = self.ltime + self.wait;
1682 self.think = fd_secret_move4;
1687 void fd_secret_move4()
1689 if (self.noise2 != "")
1690 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1691 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1695 void fd_secret_move5()
1697 self.nextthink = self.ltime + 1.0;
1698 self.think = fd_secret_move6;
1699 if (self.noise3 != "")
1700 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1703 void fd_secret_move6()
1705 if (self.noise2 != "")
1706 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1707 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1710 void fd_secret_done()
1712 if (self.spawnflags&SECRET_YES_SHOOT)
1714 self.health = 10000;
1715 self.takedamage = DAMAGE_YES;
1716 //self.th_pain = fd_secret_use;
1718 if (self.noise3 != "")
1719 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1722 void secret_blocked()
1724 if (time < self.attack_finished_single)
1726 self.attack_finished_single = time + 0.5;
1727 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1739 if not(other.iscreature)
1741 if (self.attack_finished_single > time)
1744 self.attack_finished_single = time + 2;
1748 if (IS_CLIENT(other))
1749 centerprint (other, self.message);
1750 play2(other, "misc/talk.wav");
1756 if (self.spawnflags&SECRET_YES_SHOOT)
1758 self.health = 10000;
1759 self.takedamage = DAMAGE_YES;
1761 setorigin(self, self.oldorigin);
1762 self.think = func_null;
1766 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1767 Basic secret door. Slides back, then to the side. Angle determines direction.
1768 wait = # of seconds before coming back
1769 1st_left = 1st move is left of arrow
1770 1st_down = 1st move is down from arrow
1771 always_shoot = even if targeted, keep shootable
1772 t_width = override WIDTH to move back (or height if going down)
1773 t_length = override LENGTH to move sideways
1774 "dmg" damage to inflict when blocked (2 default)
1776 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1783 void spawnfunc_func_door_secret()
1785 /*if (!self.deathtype) // map makers can override this
1786 self.deathtype = " got in the way";*/
1792 self.mangle = self.angles;
1793 self.angles = '0 0 0';
1794 self.classname = "door";
1795 if not(InitMovingBrushTrigger())
1797 self.effects |= EF_LOWPRECISION;
1799 self.touch = secret_touch;
1800 self.blocked = secret_blocked;
1802 self.use = fd_secret_use;
1807 self.spawnflags |= SECRET_YES_SHOOT;
1809 if(self.spawnflags&SECRET_YES_SHOOT)
1811 self.health = 10000;
1812 self.takedamage = DAMAGE_YES;
1813 self.event_damage = fd_secret_damage;
1815 self.oldorigin = self.origin;
1817 self.wait = 5; // 5 seconds before closing
1819 self.reset = secret_reset;
1823 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1824 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1825 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
1826 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1827 height: amplitude modifier (default 32)
1828 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1829 noise: path/name of looping .wav file to play.
1830 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1834 void func_fourier_controller_think()
1839 self.nextthink = time + 0.1;
1840 if not (self.owner.active == ACTIVE_ACTIVE)
1842 self.owner.velocity = '0 0 0';
1847 n = floor((tokenize_console(self.owner.netname)) / 5);
1848 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1850 v = self.owner.destvec;
1852 for(i = 0; i < n; ++i)
1854 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1855 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;
1858 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1859 // * 10 so it will arrive in 0.1 sec
1860 self.owner.velocity = (v - self.owner.origin) * 10;
1863 void spawnfunc_func_fourier()
1866 if (self.noise != "")
1868 precache_sound(self.noise);
1869 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1876 self.destvec = self.origin;
1877 self.cnt = 360 / self.speed;
1879 self.blocked = generic_plat_blocked;
1880 if(self.dmg && (self.message == ""))
1881 self.message = " was squished";
1882 if(self.dmg && (self.message2 == ""))
1883 self.message2 = "was squished by";
1884 if(self.dmg && (!self.dmgtime))
1885 self.dmgtime = 0.25;
1886 self.dmgtime2 = time;
1888 if(self.netname == "")
1889 self.netname = "1 0 0 0 1";
1891 if not(InitMovingBrushTrigger())
1894 self.active = ACTIVE_ACTIVE;
1896 // wait for targets to spawn
1897 controller = spawn();
1898 controller.classname = "func_fourier_controller";
1899 controller.owner = self;
1900 controller.nextthink = time + 1;
1901 controller.think = func_fourier_controller_think;
1902 self.nextthink = self.ltime + 999999999;
1903 self.think = SUB_NullThink; // for PushMove
1905 // Savage: Reduce bandwith, critical on e.g. nexdm02
1906 self.effects |= EF_LOWPRECISION;
1908 // TODO make a reset function for this one
1911 // reusing some fields havocbots declared
1912 .entity wp00, wp01, wp02, wp03;
1914 .float targetfactor, target2factor, target3factor, target4factor;
1915 .vector targetnormal, target2normal, target3normal, target4normal;
1917 vector func_vectormamamam_origin(entity o, float t)
1929 p = e.origin + t * e.velocity;
1931 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1933 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1939 p = e.origin + t * e.velocity;
1941 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1943 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1949 p = e.origin + t * e.velocity;
1951 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1953 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1959 p = e.origin + t * e.velocity;
1961 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1963 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1969 void func_vectormamamam_controller_think()
1971 self.nextthink = time + 0.1;
1973 if not (self.owner.active == ACTIVE_ACTIVE)
1975 self.owner.velocity = '0 0 0';
1979 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1980 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1983 void func_vectormamamam_findtarget()
1985 if(self.target != "")
1986 self.wp00 = find(world, targetname, self.target);
1988 if(self.target2 != "")
1989 self.wp01 = find(world, targetname, self.target2);
1991 if(self.target3 != "")
1992 self.wp02 = find(world, targetname, self.target3);
1994 if(self.target4 != "")
1995 self.wp03 = find(world, targetname, self.target4);
1997 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1998 objerror("No reference entity found, so there is nothing to move. Aborting.");
2000 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2003 controller = spawn();
2004 controller.classname = "func_vectormamamam_controller";
2005 controller.owner = self;
2006 controller.nextthink = time + 1;
2007 controller.think = func_vectormamamam_controller_think;
2010 void spawnfunc_func_vectormamamam()
2012 if (self.noise != "")
2014 precache_sound(self.noise);
2015 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2018 if(!self.targetfactor)
2019 self.targetfactor = 1;
2021 if(!self.target2factor)
2022 self.target2factor = 1;
2024 if(!self.target3factor)
2025 self.target3factor = 1;
2027 if(!self.target4factor)
2028 self.target4factor = 1;
2030 if(vlen(self.targetnormal))
2031 self.targetnormal = normalize(self.targetnormal);
2033 if(vlen(self.target2normal))
2034 self.target2normal = normalize(self.target2normal);
2036 if(vlen(self.target3normal))
2037 self.target3normal = normalize(self.target3normal);
2039 if(vlen(self.target4normal))
2040 self.target4normal = normalize(self.target4normal);
2042 self.blocked = generic_plat_blocked;
2043 if(self.dmg && (self.message == ""))
2044 self.message = " was squished";
2045 if(self.dmg && (self.message == ""))
2046 self.message2 = "was squished by";
2047 if(self.dmg && (!self.dmgtime))
2048 self.dmgtime = 0.25;
2049 self.dmgtime2 = time;
2051 if(self.netname == "")
2052 self.netname = "1 0 0 0 1";
2054 if not(InitMovingBrushTrigger())
2057 // wait for targets to spawn
2058 self.nextthink = self.ltime + 999999999;
2059 self.think = SUB_NullThink; // for PushMove
2061 // Savage: Reduce bandwith, critical on e.g. nexdm02
2062 self.effects |= EF_LOWPRECISION;
2064 self.active = ACTIVE_ACTIVE;
2066 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2069 void conveyor_think()
2073 // set myself as current conveyor where possible
2074 for(e = world; (e = findentity(e, conveyor, self)); )
2079 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2080 if(!e.conveyor.state)
2083 vector emin = e.absmin;
2084 vector emax = e.absmax;
2085 if(self.solid == SOLID_BSP)
2090 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2091 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2095 for(e = world; (e = findentity(e, conveyor, self)); )
2097 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2098 continue; // done in SV_PlayerPhysics
2100 setorigin(e, e.origin + self.movedir * sys_frametime);
2101 move_out_of_solid(e);
2102 UpdateCSQCProjectile(e);
2104 // stupid conveyor code
2105 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2106 if(trace_fraction > 0)
2107 setorigin(e, trace_endpos);
2112 self.nextthink = time;
2117 self.state = !self.state;
2120 void conveyor_reset()
2122 self.state = (self.spawnflags & 1);
2125 void conveyor_init()
2129 self.movedir = self.movedir * self.speed;
2130 self.think = conveyor_think;
2131 self.nextthink = time;
2134 self.use = conveyor_use;
2135 self.reset = conveyor_reset;
2142 void spawnfunc_trigger_conveyor()
2149 void spawnfunc_func_conveyor()
2152 InitMovingBrushTrigger();
2153 self.movetype = MOVETYPE_NONE;