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 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
254 .float train_wait_turning;
255 .float platmovetype_default;
266 // if using bezier curves and turning is enabled, the train will turn toward the next point while waiting
267 if(!self.train_wait_turning)
268 if(self.spawnflags & 1 && self.bezier_turn && self.wait >= 0)
272 targ = find(world, targetname, self.target);
273 org = normalize(targ.origin);
274 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
275 self.train_wait_turning = TRUE;
280 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
282 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
284 self.train_wait_turning = FALSE;
289 self.think = train_next;
290 self.nextthink = self.ltime + self.wait;
299 targ = find(world, targetname, self.target);
300 self.target = targ.target;
301 if (self.spawnflags & 1)
303 cp = find(world, target, targ.targetname); // get the previous corner first
304 cp = find(world, targetname, cp.curve); // now get its second target (the control point)
305 if(cp.targetname == "")
306 cp_org = targ.origin - self.mins; // no control point found, assume a straight line to the destination
308 cp_org = cp.origin - self.mins;
311 objerror("train_next: no next target");
312 self.wait = targ.wait;
316 switch(targ.platmovetype)
318 case 0: // no override
319 self.platmovetype = self.platmovetype_default;
322 self.platmovetype = 0;
325 self.platmovetype = 1;
331 if (self.spawnflags & 1)
332 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
334 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
338 if (self.spawnflags & 1)
339 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
341 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
345 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
348 void func_train_find()
351 targ = find(world, targetname, self.target);
352 self.target = targ.target;
354 objerror("func_train_find: no next target");
355 setorigin(self, targ.origin - self.mins);
356 self.nextthink = self.ltime + 1;
357 self.think = train_next;
360 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
361 Ridable platform, targets spawnfunc_path_corner path to follow.
362 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
363 target : targetname of first spawnfunc_path_corner (starts here)
365 void spawnfunc_func_train()
367 if (self.noise != "")
368 precache_sound(self.noise);
371 objerror("func_train without a target");
374 if (self.spawnflags & 2)
375 self.bezier_turn = TRUE;
377 if not(InitMovingBrushTrigger())
379 self.effects |= EF_LOWPRECISION;
381 // wait for targets to spawn
382 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
384 self.blocked = generic_plat_blocked;
385 if(self.dmg & (!self.message))
386 self.message = " was squished";
387 if(self.dmg && (!self.message2))
388 self.message2 = "was squished by";
389 if(self.dmg && (!self.dmgtime))
391 self.dmgtime2 = time;
392 self.platmovetype_default = self.platmovetype; // used for path_corner overrides
394 // TODO make a reset function for this one
397 void func_rotating_setactive(float astate)
400 if (astate == ACTIVE_TOGGLE)
402 if(self.active == ACTIVE_ACTIVE)
403 self.active = ACTIVE_NOT;
405 self.active = ACTIVE_ACTIVE;
408 self.active = astate;
410 if(self.active == ACTIVE_NOT)
411 self.avelocity = '0 0 0';
413 self.avelocity = self.pos1;
416 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
417 Brush model that spins in place on one axis (default Z).
418 speed : speed to rotate (in degrees per second)
419 noise : path/name of looping .wav file to play.
420 dmg : Do this mutch dmg every .dmgtime intervall when blocked
424 void spawnfunc_func_rotating()
426 if (self.noise != "")
428 precache_sound(self.noise);
429 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
432 self.active = ACTIVE_ACTIVE;
433 self.setactive = func_rotating_setactive;
437 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
438 if (self.spawnflags & 4) // X (untested)
439 self.avelocity = '0 0 1' * self.speed;
440 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
441 else if (self.spawnflags & 8) // Y (untested)
442 self.avelocity = '1 0 0' * self.speed;
443 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
445 self.avelocity = '0 1 0' * self.speed;
447 self.pos1 = self.avelocity;
449 if(self.dmg & (!self.message))
450 self.message = " was squished";
451 if(self.dmg && (!self.message2))
452 self.message2 = "was squished by";
455 if(self.dmg && (!self.dmgtime))
458 self.dmgtime2 = time;
460 if not(InitMovingBrushTrigger())
462 // no EF_LOWPRECISION here, as rounding angles is bad
464 self.blocked = generic_plat_blocked;
466 // wait for targets to spawn
467 self.nextthink = self.ltime + 999999999;
468 self.think = SUB_Null;
470 // TODO make a reset function for this one
474 void func_bobbing_controller_think()
477 self.nextthink = time + 0.1;
479 if not (self.owner.active == ACTIVE_ACTIVE)
481 self.owner.velocity = '0 0 0';
485 // calculate sinewave using makevectors
486 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
487 v = self.owner.destvec + self.owner.movedir * v_forward_y;
488 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
489 // * 10 so it will arrive in 0.1 sec
490 self.owner.velocity = (v - self.owner.origin) * 10;
493 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
494 Brush model that moves back and forth on one axis (default Z).
495 speed : how long one cycle takes in seconds (default 4)
496 height : how far the cycle moves (default 32)
497 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
498 noise : path/name of looping .wav file to play.
499 dmg : Do this mutch dmg every .dmgtime intervall when blocked
502 void spawnfunc_func_bobbing()
505 if (self.noise != "")
507 precache_sound(self.noise);
508 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
514 // center of bobbing motion
515 self.destvec = self.origin;
516 // time scale to get degrees
517 self.cnt = 360 / self.speed;
519 self.active = ACTIVE_ACTIVE;
521 // damage when blocked
522 self.blocked = generic_plat_blocked;
523 if(self.dmg & (!self.message))
524 self.message = " was squished";
525 if(self.dmg && (!self.message2))
526 self.message2 = "was squished by";
527 if(self.dmg && (!self.dmgtime))
529 self.dmgtime2 = time;
532 if (self.spawnflags & 1) // X
533 self.movedir = '1 0 0' * self.height;
534 else if (self.spawnflags & 2) // Y
535 self.movedir = '0 1 0' * self.height;
537 self.movedir = '0 0 1' * self.height;
539 if not(InitMovingBrushTrigger())
542 // wait for targets to spawn
543 controller = spawn();
544 controller.classname = "func_bobbing_controller";
545 controller.owner = self;
546 controller.nextthink = time + 1;
547 controller.think = func_bobbing_controller_think;
548 self.nextthink = self.ltime + 999999999;
549 self.think = SUB_Null;
551 // Savage: Reduce bandwith, critical on e.g. nexdm02
552 self.effects |= EF_LOWPRECISION;
554 // TODO make a reset function for this one
558 void func_pendulum_controller_think()
561 self.nextthink = time + 0.1;
563 if not (self.owner.active == ACTIVE_ACTIVE)
565 self.owner.avelocity_x = 0;
569 // calculate sinewave using makevectors
570 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
571 v = self.owner.speed * v_forward_y + self.cnt;
572 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
574 // * 10 so it will arrive in 0.1 sec
575 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
579 void spawnfunc_func_pendulum()
582 if (self.noise != "")
584 precache_sound(self.noise);
585 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
588 self.active = ACTIVE_ACTIVE;
590 // keys: angle, speed, phase, noise, freq
594 // not initializing self.dmg to 2, to allow damageless pendulum
596 if(self.dmg & (!self.message))
597 self.message = " was squished";
598 if(self.dmg && (!self.message2))
599 self.message2 = "was squished by";
600 if(self.dmg && (!self.dmgtime))
602 self.dmgtime2 = time;
604 self.blocked = generic_plat_blocked;
606 self.avelocity_z = 0.0000001;
607 if not(InitMovingBrushTrigger())
612 // find pendulum length (same formula as Q3A)
613 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
616 // copy initial angle
617 self.cnt = self.angles_z;
619 // wait for targets to spawn
620 controller = spawn();
621 controller.classname = "func_pendulum_controller";
622 controller.owner = self;
623 controller.nextthink = time + 1;
624 controller.think = func_pendulum_controller_think;
625 self.nextthink = self.ltime + 999999999;
626 self.think = SUB_Null;
628 //self.effects |= EF_LOWPRECISION;
630 // TODO make a reset function for this one
633 // button and multiple button
636 void() button_return;
640 self.state = STATE_TOP;
641 self.nextthink = self.ltime + self.wait;
642 self.think = button_return;
643 activator = self.enemy;
645 self.frame = 1; // use alternate textures
650 self.state = STATE_BOTTOM;
655 self.state = STATE_DOWN;
656 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
657 self.frame = 0; // use normal textures
659 self.takedamage = DAMAGE_YES; // can be shot again
663 void button_blocked()
665 // do nothing, just don't come all the way back out
671 self.health = self.max_health;
672 self.takedamage = DAMAGE_NO; // will be reset upon return
674 if (self.state == STATE_UP || self.state == STATE_TOP)
677 if (self.noise != "")
678 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
680 self.state = STATE_UP;
681 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
686 self.health = self.max_health;
687 setorigin(self, self.pos1);
688 self.frame = 0; // use normal textures
689 self.state = STATE_BOTTOM;
691 self.takedamage = DAMAGE_YES; // can be shot again
696 // if (activator.classname != "player")
698 // dprint(activator.classname);
699 // dprint(" triggered a button\n");
702 if not (self.active == ACTIVE_ACTIVE)
705 self.enemy = activator;
711 // if (activator.classname != "player")
713 // dprint(activator.classname);
714 // dprint(" touched a button\n");
718 if not(other.iscreature)
720 if(other.velocity * self.movedir < 0)
724 self.enemy = other.owner;
728 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
730 if(self.spawnflags & DOOR_NOSPLASH)
731 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
733 self.health = self.health - damage;
734 if (self.health <= 0)
736 // if (activator.classname != "player")
738 // dprint(activator.classname);
739 // dprint(" killed a button\n");
741 self.enemy = damage_attacker;
747 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
748 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.
750 "angle" determines the opening direction
751 "target" all entities with a matching targetname will be used
752 "speed" override the default 40 speed
753 "wait" override the default 1 second wait (-1 = never return)
754 "lip" override the default 4 pixel lip remaining at end of move
755 "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
762 void spawnfunc_func_button()
766 if not(InitMovingBrushTrigger())
768 self.effects |= EF_LOWPRECISION;
770 self.blocked = button_blocked;
771 self.use = button_use;
773 // if (self.health == 0) // all buttons are now shootable
777 self.max_health = self.health;
778 self.event_damage = button_damage;
779 self.takedamage = DAMAGE_YES;
782 self.touch = button_touch;
792 precache_sound(self.noise);
794 self.active = ACTIVE_ACTIVE;
796 self.pos1 = self.origin;
797 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
798 self.flags |= FL_NOTARGET;
804 float DOOR_START_OPEN = 1;
805 float DOOR_DONT_LINK = 4;
806 float DOOR_TOGGLE = 32;
810 Doors are similar to buttons, but can spawn a fat trigger field around them
811 to open without a touch, and they link together to form simultanious
814 Door.owner is the master door. If there is only one door, it points to itself.
815 If multiple doors, all will point to a single one.
817 Door.enemy chains from the master door through all doors linked in the chain.
822 =============================================================================
826 =============================================================================
831 void() door_rotating_go_down;
832 void() door_rotating_go_up;
837 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
838 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
841 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
842 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
844 //Dont chamge direction for dead or dying stuff
845 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
848 if (self.state == STATE_DOWN)
849 if (self.classname == "door")
854 door_rotating_go_up ();
857 if (self.classname == "door")
862 door_rotating_go_down ();
866 //gib dying stuff just to make sure
867 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
868 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
872 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
873 // if a door has a negative wait, it would never come back if blocked,
874 // so let it just squash the object to death real fast
875 /* if (self.wait >= 0)
877 if (self.state == STATE_DOWN)
888 if (self.noise1 != "")
889 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
890 self.state = STATE_TOP;
891 if (self.spawnflags & DOOR_TOGGLE)
892 return; // don't come down automatically
893 if (self.classname == "door")
895 self.think = door_go_down;
898 self.think = door_rotating_go_down;
900 self.nextthink = self.ltime + self.wait;
903 void door_hit_bottom()
905 if (self.noise1 != "")
906 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
907 self.state = STATE_BOTTOM;
912 if (self.noise2 != "")
913 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
916 self.takedamage = DAMAGE_YES;
917 self.health = self.max_health;
920 self.state = STATE_DOWN;
921 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
926 if (self.state == STATE_UP)
927 return; // already going up
929 if (self.state == STATE_TOP)
930 { // reset top wait time
931 self.nextthink = self.ltime + self.wait;
935 if (self.noise2 != "")
936 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
937 self.state = STATE_UP;
938 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
941 oldmessage = self.message;
944 self.message = oldmessage;
950 =============================================================================
954 =============================================================================
957 float door_check_keys(void) {
967 if not(door.itemkeys)
970 // this door require a key
971 // only a player can have a key
972 if (other.classname != "player")
975 if (item_keys_usekey(door, other)) {
976 // some keys were used
977 if (other.key_door_messagetime <= time) {
978 play2(other, "misc/talk.wav");
979 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
980 other.key_door_messagetime = time + 2;
984 if (other.key_door_messagetime <= time) {
985 play2(other, "misc/talk.wav");
986 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
987 other.key_door_messagetime = time + 2;
992 // door is now unlocked
993 play2(other, "misc/talk.wav");
994 centerprint(other, "Door unlocked!");
1006 if (self.owner != self)
1007 objerror ("door_fire: self.owner != self");
1011 if (self.spawnflags & DOOR_TOGGLE)
1013 if (self.state == STATE_UP || self.state == STATE_TOP)
1018 if (self.classname == "door")
1024 door_rotating_go_down ();
1027 } while ( (self != starte) && (self != world) );
1033 // trigger all paired doors
1037 if (self.classname == "door")
1042 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1043 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1045 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1046 self.pos2 = '0 0 0' - self.pos2;
1048 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1049 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1050 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1052 door_rotating_go_up ();
1056 } while ( (self != starte) && (self != world) );
1065 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1077 void door_trigger_touch()
1079 if (other.health < 1)
1080 if not(other.iscreature && other.deadflag == DEAD_NO)
1083 if (time < self.attack_finished_single)
1086 // check if door is locked
1087 if (!door_check_keys())
1090 self.attack_finished_single = time + 1;
1099 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1102 if(self.spawnflags & DOOR_NOSPLASH)
1103 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1105 self.health = self.health - damage;
1107 if (self.itemkeys) {
1108 // don't allow opening doors through damage if keys are required
1112 if (self.health <= 0)
1116 self.health = self.max_health;
1117 self.takedamage = DAMAGE_NO; // wil be reset upon return
1133 if(other.classname != "player")
1135 if (self.owner.attack_finished_single > time)
1138 self.owner.attack_finished_single = time + 2;
1140 if (!(self.owner.dmg) && (self.owner.message != ""))
1142 if (other.flags & FL_CLIENT)
1143 centerprint (other, self.owner.message);
1144 play2(other, "misc/talk.wav");
1149 void door_generic_plat_blocked()
1152 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1153 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1156 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1157 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1159 //Dont chamge direction for dead or dying stuff
1160 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1163 if (self.state == STATE_DOWN)
1164 door_rotating_go_up ();
1166 door_rotating_go_down ();
1169 //gib dying stuff just to make sure
1170 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1171 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1175 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1176 // if a door has a negative wait, it would never come back if blocked,
1177 // so let it just squash the object to death real fast
1178 /* if (self.wait >= 0)
1180 if (self.state == STATE_DOWN)
1181 door_rotating_go_up ();
1183 door_rotating_go_down ();
1189 void door_rotating_hit_top()
1191 if (self.noise1 != "")
1192 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1193 self.state = STATE_TOP;
1194 if (self.spawnflags & DOOR_TOGGLE)
1195 return; // don't come down automatically
1196 self.think = door_rotating_go_down;
1197 self.nextthink = self.ltime + self.wait;
1200 void door_rotating_hit_bottom()
1202 if (self.noise1 != "")
1203 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1204 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1206 self.pos2 = '0 0 0' - self.pos2;
1209 self.state = STATE_BOTTOM;
1212 void door_rotating_go_down()
1214 if (self.noise2 != "")
1215 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1216 if (self.max_health)
1218 self.takedamage = DAMAGE_YES;
1219 self.health = self.max_health;
1222 self.state = STATE_DOWN;
1223 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1226 void door_rotating_go_up()
1228 if (self.state == STATE_UP)
1229 return; // already going up
1231 if (self.state == STATE_TOP)
1232 { // reset top wait time
1233 self.nextthink = self.ltime + self.wait;
1236 if (self.noise2 != "")
1237 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1238 self.state = STATE_UP;
1239 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1242 oldmessage = self.message;
1245 self.message = oldmessage;
1252 =============================================================================
1256 =============================================================================
1260 entity spawn_field(vector fmins, vector fmaxs)
1266 trigger.classname = "doortriggerfield";
1267 trigger.movetype = MOVETYPE_NONE;
1268 trigger.solid = SOLID_TRIGGER;
1269 trigger.owner = self;
1270 trigger.touch = door_trigger_touch;
1274 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1279 float EntitiesTouching(entity e1, entity e2)
1281 if (e1.absmin_x > e2.absmax_x)
1283 if (e1.absmin_y > e2.absmax_y)
1285 if (e1.absmin_z > e2.absmax_z)
1287 if (e1.absmax_x < e2.absmin_x)
1289 if (e1.absmax_y < e2.absmin_y)
1291 if (e1.absmax_z < e2.absmin_z)
1307 vector cmins, cmaxs;
1310 return; // already linked by another door
1311 if (self.spawnflags & 4)
1313 self.owner = self.enemy = self;
1321 self.trigger_field = spawn_field(self.absmin, self.absmax);
1323 return; // don't want to link this door
1326 cmins = self.absmin;
1327 cmaxs = self.absmax;
1334 self.owner = starte; // master door
1337 starte.health = self.health;
1339 starte.targetname = self.targetname;
1340 if (self.message != "")
1341 starte.message = self.message;
1343 t = find(t, classname, self.classname);
1346 self.enemy = starte; // make the chain a loop
1348 // shootable, or triggered doors just needed the owner/enemy links,
1349 // they don't spawn a field
1360 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1365 if (EntitiesTouching(self,t))
1368 objerror ("cross connected doors");
1373 if (t.absmin_x < cmins_x)
1374 cmins_x = t.absmin_x;
1375 if (t.absmin_y < cmins_y)
1376 cmins_y = t.absmin_y;
1377 if (t.absmin_z < cmins_z)
1378 cmins_z = t.absmin_z;
1379 if (t.absmax_x > cmaxs_x)
1380 cmaxs_x = t.absmax_x;
1381 if (t.absmax_y > cmaxs_y)
1382 cmaxs_y = t.absmax_y;
1383 if (t.absmax_z > cmaxs_z)
1384 cmaxs_z = t.absmax_z;
1391 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1392 if two doors touch, they are assumed to be connected and operate as a unit.
1394 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1396 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).
1398 GOLD_KEY causes the door to open only if the activator holds a gold key.
1400 SILVER_KEY causes the door to open only if the activator holds a silver key.
1402 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1403 "angle" determines the opening direction
1404 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1405 "health" if set, door must be shot open
1406 "speed" movement speed (100 default)
1407 "wait" wait before returning (3 default, -1 = never return)
1408 "lip" lip remaining at end of move (8 default)
1409 "dmg" damage to inflict when blocked (2 default)
1416 FIXME: only one sound set available at the time being
1420 void door_init_startopen()
1422 setorigin (self, self.pos2);
1423 self.pos2 = self.pos1;
1424 self.pos1 = self.origin;
1429 setorigin(self, self.pos1);
1430 self.velocity = '0 0 0';
1431 self.state = STATE_BOTTOM;
1432 self.think = SUB_Null;
1435 // spawnflags require key (for now only func_door)
1436 #define SPAWNFLAGS_GOLD_KEY 8
1437 #define SPAWNFLAGS_SILVER_KEY 16
1438 void spawnfunc_func_door()
1440 // Quake 1 keys compatibility
1441 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1442 self.itemkeys |= ITEM_KEY_BIT(0);
1443 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1444 self.itemkeys |= ITEM_KEY_BIT(1);
1446 //if (!self.deathtype) // map makers can override this
1447 // self.deathtype = " got in the way";
1450 self.max_health = self.health;
1451 if not(InitMovingBrushTrigger())
1453 self.effects |= EF_LOWPRECISION;
1454 self.classname = "door";
1456 self.blocked = door_blocked;
1457 self.use = door_use;
1459 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1460 // if(self.spawnflags & 8)
1461 // self.dmg = 10000;
1463 if(self.dmg && (!self.message))
1464 self.message = "was squished";
1465 if(self.dmg && (!self.message2))
1466 self.message2 = "was squished by";
1468 if (self.sounds > 0)
1470 precache_sound ("plats/medplat1.wav");
1471 precache_sound ("plats/medplat2.wav");
1472 self.noise2 = "plats/medplat1.wav";
1473 self.noise1 = "plats/medplat2.wav";
1483 self.pos1 = self.origin;
1484 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1486 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1487 // but spawn in the open position
1488 if (self.spawnflags & DOOR_START_OPEN)
1489 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1491 self.state = STATE_BOTTOM;
1495 self.takedamage = DAMAGE_YES;
1496 self.event_damage = door_damage;
1502 self.touch = door_touch;
1504 // LinkDoors can't be done until all of the doors have been spawned, so
1505 // the sizes can be detected properly.
1506 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1508 self.reset = door_reset;
1511 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1512 if two doors touch, they are assumed to be connected and operate as a unit.
1514 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1516 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1517 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1518 must have set trigger_reverse to 1.
1519 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1521 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).
1523 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1524 "angle" determines the destination angle for opening. negative values reverse the direction.
1525 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1526 "health" if set, door must be shot open
1527 "speed" movement speed (100 default)
1528 "wait" wait before returning (3 default, -1 = never return)
1529 "dmg" damage to inflict when blocked (2 default)
1536 FIXME: only one sound set available at the time being
1539 void door_rotating_reset()
1541 self.angles = self.pos1;
1542 self.avelocity = '0 0 0';
1543 self.state = STATE_BOTTOM;
1544 self.think = SUB_Null;
1547 void door_rotating_init_startopen()
1549 self.angles = self.movedir;
1550 self.pos2 = '0 0 0';
1551 self.pos1 = self.movedir;
1555 void spawnfunc_func_door_rotating()
1558 //if (!self.deathtype) // map makers can override this
1559 // self.deathtype = " got in the way";
1561 // I abuse "movedir" for denoting the axis for now
1562 if (self.spawnflags & 64) // X (untested)
1563 self.movedir = '0 0 1';
1564 else if (self.spawnflags & 128) // Y (untested)
1565 self.movedir = '1 0 0';
1567 self.movedir = '0 1 0';
1569 if (self.angles_y==0) self.angles_y = 90;
1571 self.movedir = self.movedir * self.angles_y;
1572 self.angles = '0 0 0';
1574 self.max_health = self.health;
1575 self.avelocity = self.movedir;
1576 if not(InitMovingBrushTrigger())
1578 self.velocity = '0 0 0';
1579 //self.effects |= EF_LOWPRECISION;
1580 self.classname = "door_rotating";
1582 self.blocked = door_blocked;
1583 self.use = door_use;
1585 if(self.spawnflags & 8)
1588 if(self.dmg && (!self.message))
1589 self.message = "was squished";
1590 if(self.dmg && (!self.message2))
1591 self.message2 = "was squished by";
1593 if (self.sounds > 0)
1595 precache_sound ("plats/medplat1.wav");
1596 precache_sound ("plats/medplat2.wav");
1597 self.noise2 = "plats/medplat1.wav";
1598 self.noise1 = "plats/medplat2.wav";
1605 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1607 self.pos1 = '0 0 0';
1608 self.pos2 = self.movedir;
1610 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1611 // but spawn in the open position
1612 if (self.spawnflags & DOOR_START_OPEN)
1613 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1615 self.state = STATE_BOTTOM;
1619 self.takedamage = DAMAGE_YES;
1620 self.event_damage = door_damage;
1626 self.touch = door_touch;
1628 // LinkDoors can't be done until all of the doors have been spawned, so
1629 // the sizes can be detected properly.
1630 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1632 self.reset = door_rotating_reset;
1636 =============================================================================
1640 =============================================================================
1643 void() fd_secret_move1;
1644 void() fd_secret_move2;
1645 void() fd_secret_move3;
1646 void() fd_secret_move4;
1647 void() fd_secret_move5;
1648 void() fd_secret_move6;
1649 void() fd_secret_done;
1651 float SECRET_OPEN_ONCE = 1; // stays open
1652 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1653 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1654 float SECRET_NO_SHOOT = 8; // only opened by trigger
1655 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1658 void fd_secret_use()
1661 string message_save;
1663 self.health = 10000;
1664 self.bot_attack = TRUE;
1666 // exit if still moving around...
1667 if (self.origin != self.oldorigin)
1670 message_save = self.message;
1671 self.message = ""; // no more message
1672 SUB_UseTargets(); // fire all targets / killtargets
1673 self.message = message_save;
1675 self.velocity = '0 0 0';
1677 // Make a sound, wait a little...
1679 if (self.noise1 != "")
1680 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1681 self.nextthink = self.ltime + 0.1;
1683 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1684 makevectors(self.mangle);
1688 if (self.spawnflags & SECRET_1ST_DOWN)
1689 self.t_width = fabs(v_up * self.size);
1691 self.t_width = fabs(v_right * self.size);
1695 self.t_length = fabs(v_forward * self.size);
1697 if (self.spawnflags & SECRET_1ST_DOWN)
1698 self.dest1 = self.origin - v_up * self.t_width;
1700 self.dest1 = self.origin + v_right * (self.t_width * temp);
1702 self.dest2 = self.dest1 + v_forward * self.t_length;
1703 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1704 if (self.noise2 != "")
1705 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1708 // Wait after first movement...
1709 void fd_secret_move1()
1711 self.nextthink = self.ltime + 1.0;
1712 self.think = fd_secret_move2;
1713 if (self.noise3 != "")
1714 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1717 // Start moving sideways w/sound...
1718 void fd_secret_move2()
1720 if (self.noise2 != "")
1721 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1722 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1725 // Wait here until time to go back...
1726 void fd_secret_move3()
1728 if (self.noise3 != "")
1729 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1730 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1732 self.nextthink = self.ltime + self.wait;
1733 self.think = fd_secret_move4;
1738 void fd_secret_move4()
1740 if (self.noise2 != "")
1741 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1742 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1746 void fd_secret_move5()
1748 self.nextthink = self.ltime + 1.0;
1749 self.think = fd_secret_move6;
1750 if (self.noise3 != "")
1751 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1754 void fd_secret_move6()
1756 if (self.noise2 != "")
1757 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1758 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1761 void fd_secret_done()
1763 if (self.spawnflags&SECRET_YES_SHOOT)
1765 self.health = 10000;
1766 self.takedamage = DAMAGE_YES;
1767 //self.th_pain = fd_secret_use;
1769 if (self.noise3 != "")
1770 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1773 void secret_blocked()
1775 if (time < self.attack_finished_single)
1777 self.attack_finished_single = time + 0.5;
1778 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1790 if not(other.iscreature)
1792 if (self.attack_finished_single > time)
1795 self.attack_finished_single = time + 2;
1799 if (other.flags & FL_CLIENT)
1800 centerprint (other, self.message);
1801 play2(other, "misc/talk.wav");
1807 if (self.spawnflags&SECRET_YES_SHOOT)
1809 self.health = 10000;
1810 self.takedamage = DAMAGE_YES;
1812 setorigin(self, self.oldorigin);
1813 self.think = SUB_Null;
1816 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1817 Basic secret door. Slides back, then to the side. Angle determines direction.
1818 wait = # of seconds before coming back
1819 1st_left = 1st move is left of arrow
1820 1st_down = 1st move is down from arrow
1821 always_shoot = even if targeted, keep shootable
1822 t_width = override WIDTH to move back (or height if going down)
1823 t_length = override LENGTH to move sideways
1824 "dmg" damage to inflict when blocked (2 default)
1826 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1833 void spawnfunc_func_door_secret()
1835 /*if (!self.deathtype) // map makers can override this
1836 self.deathtype = " got in the way";*/
1842 self.mangle = self.angles;
1843 self.angles = '0 0 0';
1844 self.classname = "door";
1845 if not(InitMovingBrushTrigger())
1847 self.effects |= EF_LOWPRECISION;
1849 self.touch = secret_touch;
1850 self.blocked = secret_blocked;
1852 self.use = fd_secret_use;
1857 self.spawnflags |= SECRET_YES_SHOOT;
1859 if(self.spawnflags&SECRET_YES_SHOOT)
1861 self.health = 10000;
1862 self.takedamage = DAMAGE_YES;
1863 self.event_damage = fd_secret_use;
1865 self.oldorigin = self.origin;
1867 self.wait = 5; // 5 seconds before closing
1869 self.reset = secret_reset;
1873 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1874 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1875 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
1876 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1877 height: amplitude modifier (default 32)
1878 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1879 noise: path/name of looping .wav file to play.
1880 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1884 void func_fourier_controller_think()
1889 self.nextthink = time + 0.1;
1890 if not (self.owner.active == ACTIVE_ACTIVE)
1892 self.owner.velocity = '0 0 0';
1897 n = floor((tokenize_console(self.owner.netname)) / 5);
1898 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1900 v = self.owner.destvec;
1902 for(i = 0; i < n; ++i)
1904 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1905 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;
1908 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1909 // * 10 so it will arrive in 0.1 sec
1910 self.owner.velocity = (v - self.owner.origin) * 10;
1913 void spawnfunc_func_fourier()
1916 if (self.noise != "")
1918 precache_sound(self.noise);
1919 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1926 self.destvec = self.origin;
1927 self.cnt = 360 / self.speed;
1929 self.blocked = generic_plat_blocked;
1930 if(self.dmg & (!self.message))
1931 self.message = " was squished";
1932 if(self.dmg && (!self.message2))
1933 self.message2 = "was squished by";
1934 if(self.dmg && (!self.dmgtime))
1935 self.dmgtime = 0.25;
1936 self.dmgtime2 = time;
1938 if(self.netname == "")
1939 self.netname = "1 0 0 0 1";
1941 if not(InitMovingBrushTrigger())
1944 self.active = ACTIVE_ACTIVE;
1946 // wait for targets to spawn
1947 controller = spawn();
1948 controller.classname = "func_fourier_controller";
1949 controller.owner = self;
1950 controller.nextthink = time + 1;
1951 controller.think = func_fourier_controller_think;
1952 self.nextthink = self.ltime + 999999999;
1953 self.think = SUB_Null;
1955 // Savage: Reduce bandwith, critical on e.g. nexdm02
1956 self.effects |= EF_LOWPRECISION;
1958 // TODO make a reset function for this one
1961 // reusing some fields havocbots declared
1962 .entity wp00, wp01, wp02, wp03;
1964 .float targetfactor, target2factor, target3factor, target4factor;
1965 .vector targetnormal, target2normal, target3normal, target4normal;
1967 vector func_vectormamamam_origin(entity o, float t)
1979 p = e.origin + t * e.velocity;
1981 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1983 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1989 p = e.origin + t * e.velocity;
1991 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1993 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1999 p = e.origin + t * e.velocity;
2001 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2003 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2009 p = e.origin + t * e.velocity;
2011 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2013 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2019 void func_vectormamamam_controller_think()
2021 self.nextthink = time + 0.1;
2023 if not (self.owner.active == ACTIVE_ACTIVE)
2025 self.owner.velocity = '0 0 0';
2029 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2030 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2033 void func_vectormamamam_findtarget()
2035 if(self.target != "")
2036 self.wp00 = find(world, targetname, self.target);
2038 if(self.target2 != "")
2039 self.wp01 = find(world, targetname, self.target2);
2041 if(self.target3 != "")
2042 self.wp02 = find(world, targetname, self.target3);
2044 if(self.target4 != "")
2045 self.wp03 = find(world, targetname, self.target4);
2047 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2048 objerror("No reference entity found, so there is nothing to move. Aborting.");
2050 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2053 controller = spawn();
2054 controller.classname = "func_vectormamamam_controller";
2055 controller.owner = self;
2056 controller.nextthink = time + 1;
2057 controller.think = func_vectormamamam_controller_think;
2060 void spawnfunc_func_vectormamamam()
2062 if (self.noise != "")
2064 precache_sound(self.noise);
2065 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2068 if(!self.targetfactor)
2069 self.targetfactor = 1;
2071 if(!self.target2factor)
2072 self.target2factor = 1;
2074 if(!self.target3factor)
2075 self.target3factor = 1;
2077 if(!self.target4factor)
2078 self.target4factor = 1;
2080 if(vlen(self.targetnormal))
2081 self.targetnormal = normalize(self.targetnormal);
2083 if(vlen(self.target2normal))
2084 self.target2normal = normalize(self.target2normal);
2086 if(vlen(self.target3normal))
2087 self.target3normal = normalize(self.target3normal);
2089 if(vlen(self.target4normal))
2090 self.target4normal = normalize(self.target4normal);
2092 self.blocked = generic_plat_blocked;
2093 if(self.dmg & (!self.message))
2094 self.message = " was squished";
2095 if(self.dmg && (!self.message2))
2096 self.message2 = "was squished by";
2097 if(self.dmg && (!self.dmgtime))
2098 self.dmgtime = 0.25;
2099 self.dmgtime2 = time;
2101 if(self.netname == "")
2102 self.netname = "1 0 0 0 1";
2104 if not(InitMovingBrushTrigger())
2107 // wait for targets to spawn
2108 self.nextthink = self.ltime + 999999999;
2109 self.think = SUB_Null;
2111 // Savage: Reduce bandwith, critical on e.g. nexdm02
2112 self.effects |= EF_LOWPRECISION;
2114 self.active = ACTIVE_ACTIVE;
2116 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2119 void conveyor_think()
2123 // set myself as current conveyor where possible
2124 for(e = world; (e = findentity(e, conveyor, self)); )
2129 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2130 if(!e.conveyor.state)
2133 vector emin = e.absmin;
2134 vector emax = e.absmax;
2135 if(self.solid == SOLID_BSP)
2140 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2141 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2145 for(e = world; (e = findentity(e, conveyor, self)); )
2147 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2148 continue; // done in SV_PlayerPhysics
2150 setorigin(e, e.origin + self.movedir * sys_frametime);
2151 move_out_of_solid(e);
2152 UpdateCSQCProjectile(e);
2154 // stupid conveyor code
2155 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2156 if(trace_fraction > 0)
2157 setorigin(e, trace_endpos);
2162 self.nextthink = time;
2167 self.state = !self.state;
2170 void conveyor_reset()
2172 self.state = (self.spawnflags & 1);
2175 void conveyor_init()
2179 self.movedir = self.movedir * self.speed;
2180 self.think = conveyor_think;
2181 self.nextthink = time;
2184 self.use = conveyor_use;
2185 self.reset = conveyor_reset;
2192 void spawnfunc_trigger_conveyor()
2199 void spawnfunc_func_conveyor()
2202 InitMovingBrushTrigger();
2203 self.movetype = MOVETYPE_NONE;