4 void generic_plat_blocked()
6 if(self.dmg && other.takedamage != DAMAGE_NO) {
7 if(self.dmgtime2 < time) {
8 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
9 self.dmgtime2 = time + self.dmgtime;
12 // Gib dead/dying stuff
13 if(other.deadflag != DEAD_NO)
14 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
19 void() plat_center_touch;
20 void() plat_outside_touch;
21 void() plat_trigger_use;
25 const float PLAT_LOW_TRIGGER = 1;
27 void plat_spawn_inside_trigger()
33 trigger.touch = plat_center_touch;
34 trigger.movetype = MOVETYPE_NONE;
35 trigger.solid = SOLID_TRIGGER;
38 tmin = self.absmin + '25 25 0';
39 tmax = self.absmax - '25 25 -8';
40 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
41 if (self.spawnflags & PLAT_LOW_TRIGGER)
44 if (self.size_x <= 50)
46 tmin_x = (self.mins_x + self.maxs_x) / 2;
49 if (self.size_y <= 50)
51 tmin_y = (self.mins_y + self.maxs_y) / 2;
59 setsize (trigger, tmin, tmax);
63 // otherwise, something is fishy...
65 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
70 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
72 self.think = plat_go_down;
73 self.nextthink = self.ltime + 3;
76 void plat_hit_bottom()
78 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
84 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
86 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
91 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
93 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
96 void plat_center_touch()
98 if (!other.iscreature)
101 if (other.health <= 0)
107 else if (self.state == 1)
108 self.nextthink = self.ltime + 1; // delay going down
111 void plat_outside_touch()
113 if (!other.iscreature)
116 if (other.health <= 0)
124 void plat_trigger_use()
127 return; // already activated
134 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
135 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
137 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
138 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
139 // Gib dead/dying stuff
140 if(other.deadflag != DEAD_NO)
141 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
146 else if (self.state == 3)
148 // when in other states, then the plat_crush event came delayed after
149 // plat state already had changed
150 // this isn't a bug per se!
156 self.use = func_null;
158 objerror ("plat_use: not in up state");
162 .string sound1, sound2;
168 setorigin (self, self.pos1);
174 setorigin (self, self.pos2);
176 self.use = plat_trigger_use;
180 .float platmovetype_start_default, platmovetype_end_default;
181 float set_platmovetype(entity e, string s)
183 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
186 n = tokenize_console(s);
188 e.platmovetype_start = stof(argv(0));
190 e.platmovetype_start = 0;
193 e.platmovetype_end = stof(argv(1));
195 e.platmovetype_end = e.platmovetype_start;
198 if(argv(2) == "force")
199 return TRUE; // no checking, return immediately
201 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
203 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
210 void spawnfunc_path_corner()
212 // setup values for overriding train movement
213 // if a second value does not exist, both start and end speeds are the single value specified
214 if(!set_platmovetype(self, self.platmovetype))
217 void spawnfunc_func_plat()
219 if (self.sounds == 0)
222 if(self.spawnflags & 4)
225 if(self.dmg && (self.message == ""))
226 self.message = "was squished";
227 if(self.dmg && (self.message2 == ""))
228 self.message2 = "was squished by";
230 if (self.sounds == 1)
232 precache_sound ("plats/plat1.wav");
233 precache_sound ("plats/plat2.wav");
234 self.noise = "plats/plat1.wav";
235 self.noise1 = "plats/plat2.wav";
238 if (self.sounds == 2)
240 precache_sound ("plats/medplat1.wav");
241 precache_sound ("plats/medplat2.wav");
242 self.noise = "plats/medplat1.wav";
243 self.noise1 = "plats/medplat2.wav";
248 precache_sound (self.sound1);
249 self.noise = self.sound1;
253 precache_sound (self.sound2);
254 self.noise1 = self.sound2;
257 self.mangle = self.angles;
258 self.angles = '0 0 0';
260 self.classname = "plat";
261 if (!InitMovingBrushTrigger())
263 self.effects |= EF_LOWPRECISION;
264 setsize (self, self.mins , self.maxs);
266 self.blocked = plat_crush;
273 self.height = self.size_z - self.lip;
275 self.pos1 = self.origin;
276 self.pos2 = self.origin;
277 self.pos2_z = self.origin_z - self.height;
279 self.reset = plat_reset;
282 plat_spawn_inside_trigger (); // the "start moving" trigger
285 .float train_wait_turning;
296 // if turning is enabled, the train will turn toward the next point while waiting
297 if(self.platmovetype_turn && !self.train_wait_turning)
301 targ = find(world, targetname, self.target);
302 if((self.spawnflags & 1) && targ.curvetarget)
303 cp = find(world, targetname, targ.curvetarget);
307 if(cp) // bezier curves movement
308 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
309 else // linear movement
310 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
311 ang = vectoangles(ang);
312 ang_x = -ang_x; // flip up / down orientation
314 if(self.wait > 0) // slow turning
315 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
316 else // instant turning
317 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
318 self.train_wait_turning = TRUE;
323 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
325 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
327 self.train_wait_turning = FALSE;
332 self.think = train_next;
333 self.nextthink = self.ltime + self.wait;
339 entity targ, cp = world;
340 vector cp_org = '0 0 0';
342 targ = find(world, targetname, self.target);
343 self.target = targ.target;
344 if (self.spawnflags & 1)
348 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
349 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
352 if (self.target == "")
353 objerror("train_next: no next target");
354 self.wait = targ.wait;
358 if(targ.platmovetype)
360 // this path_corner contains a movetype overrider, apply it
361 self.platmovetype_start = targ.platmovetype_start;
362 self.platmovetype_end = targ.platmovetype_end;
366 // this path_corner doesn't contain a movetype overrider, use the train's defaults
367 self.platmovetype_start = self.platmovetype_start_default;
368 self.platmovetype_end = self.platmovetype_end_default;
374 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
376 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
381 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
383 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
387 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
390 void func_train_find()
393 targ = find(world, targetname, self.target);
394 self.target = targ.target;
395 if (self.target == "")
396 objerror("func_train_find: no next target");
397 setorigin(self, targ.origin - self.view_ofs);
398 self.nextthink = self.ltime + 1;
399 self.think = train_next;
402 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
403 Ridable platform, targets spawnfunc_path_corner path to follow.
404 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
405 target : targetname of first spawnfunc_path_corner (starts here)
407 void spawnfunc_func_train()
409 if (self.noise != "")
410 precache_sound(self.noise);
412 if (self.target == "")
413 objerror("func_train without a target");
417 if (!InitMovingBrushTrigger())
419 self.effects |= EF_LOWPRECISION;
421 if (self.spawnflags & 2)
423 self.platmovetype_turn = TRUE;
424 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
427 self.view_ofs = self.mins;
429 // wait for targets to spawn
430 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
432 self.blocked = generic_plat_blocked;
433 if(self.dmg && (self.message == ""))
434 self.message = " was squished";
435 if(self.dmg && (self.message2 == ""))
436 self.message2 = "was squished by";
437 if(self.dmg && (!self.dmgtime))
439 self.dmgtime2 = time;
441 if(!set_platmovetype(self, self.platmovetype))
443 self.platmovetype_start_default = self.platmovetype_start;
444 self.platmovetype_end_default = self.platmovetype_end;
446 // TODO make a reset function for this one
449 void func_rotating_setactive(float astate)
452 if (astate == ACTIVE_TOGGLE)
454 if(self.active == ACTIVE_ACTIVE)
455 self.active = ACTIVE_NOT;
457 self.active = ACTIVE_ACTIVE;
460 self.active = astate;
462 if(self.active == ACTIVE_NOT)
463 self.avelocity = '0 0 0';
465 self.avelocity = self.pos1;
468 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
469 Brush model that spins in place on one axis (default Z).
470 speed : speed to rotate (in degrees per second)
471 noise : path/name of looping .wav file to play.
472 dmg : Do this mutch dmg every .dmgtime intervall when blocked
476 void spawnfunc_func_rotating()
478 if (self.noise != "")
480 precache_sound(self.noise);
481 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
484 self.active = ACTIVE_ACTIVE;
485 self.setactive = func_rotating_setactive;
489 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
490 if (self.spawnflags & 4) // X (untested)
491 self.avelocity = '0 0 1' * self.speed;
492 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
493 else if (self.spawnflags & 8) // Y (untested)
494 self.avelocity = '1 0 0' * self.speed;
495 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
497 self.avelocity = '0 1 0' * self.speed;
499 self.pos1 = self.avelocity;
501 if(self.dmg && (self.message == ""))
502 self.message = " was squished";
503 if(self.dmg && (self.message2 == ""))
504 self.message2 = "was squished by";
507 if(self.dmg && (!self.dmgtime))
510 self.dmgtime2 = time;
512 if (!InitMovingBrushTrigger())
514 // no EF_LOWPRECISION here, as rounding angles is bad
516 self.blocked = generic_plat_blocked;
518 // wait for targets to spawn
519 self.nextthink = self.ltime + 999999999;
520 self.think = SUB_NullThink; // for PushMove
522 // TODO make a reset function for this one
526 void func_bobbing_controller_think()
529 self.nextthink = time + 0.1;
531 if(self.owner.active != ACTIVE_ACTIVE)
533 self.owner.velocity = '0 0 0';
537 // calculate sinewave using makevectors
538 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
539 v = self.owner.destvec + self.owner.movedir * v_forward_y;
540 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
541 // * 10 so it will arrive in 0.1 sec
542 self.owner.velocity = (v - self.owner.origin) * 10;
545 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
546 Brush model that moves back and forth on one axis (default Z).
547 speed : how long one cycle takes in seconds (default 4)
548 height : how far the cycle moves (default 32)
549 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
550 noise : path/name of looping .wav file to play.
551 dmg : Do this mutch dmg every .dmgtime intervall when blocked
554 void spawnfunc_func_bobbing()
557 if (self.noise != "")
559 precache_sound(self.noise);
560 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
566 // center of bobbing motion
567 self.destvec = self.origin;
568 // time scale to get degrees
569 self.cnt = 360 / self.speed;
571 self.active = ACTIVE_ACTIVE;
573 // damage when blocked
574 self.blocked = generic_plat_blocked;
575 if(self.dmg && (self.message == ""))
576 self.message = " was squished";
577 if(self.dmg && (self.message2 == ""))
578 self.message2 = "was squished by";
579 if(self.dmg && (!self.dmgtime))
581 self.dmgtime2 = time;
584 if (self.spawnflags & 1) // X
585 self.movedir = '1 0 0' * self.height;
586 else if (self.spawnflags & 2) // Y
587 self.movedir = '0 1 0' * self.height;
589 self.movedir = '0 0 1' * self.height;
591 if (!InitMovingBrushTrigger())
594 // wait for targets to spawn
595 controller = spawn();
596 controller.classname = "func_bobbing_controller";
597 controller.owner = self;
598 controller.nextthink = time + 1;
599 controller.think = func_bobbing_controller_think;
600 self.nextthink = self.ltime + 999999999;
601 self.think = SUB_NullThink; // for PushMove
603 // Savage: Reduce bandwith, critical on e.g. nexdm02
604 self.effects |= EF_LOWPRECISION;
606 // TODO make a reset function for this one
610 void func_pendulum_controller_think()
613 self.nextthink = time + 0.1;
615 if (!(self.owner.active == ACTIVE_ACTIVE))
617 self.owner.avelocity_x = 0;
621 // calculate sinewave using makevectors
622 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
623 v = self.owner.speed * v_forward_y + self.cnt;
624 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
626 // * 10 so it will arrive in 0.1 sec
627 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
631 void spawnfunc_func_pendulum()
634 if (self.noise != "")
636 precache_sound(self.noise);
637 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
640 self.active = ACTIVE_ACTIVE;
642 // keys: angle, speed, phase, noise, freq
646 // not initializing self.dmg to 2, to allow damageless pendulum
648 if(self.dmg && (self.message == ""))
649 self.message = " was squished";
650 if(self.dmg && (self.message2 == ""))
651 self.message2 = "was squished by";
652 if(self.dmg && (!self.dmgtime))
654 self.dmgtime2 = time;
656 self.blocked = generic_plat_blocked;
658 self.avelocity_z = 0.0000001;
659 if (!InitMovingBrushTrigger())
664 // find pendulum length (same formula as Q3A)
665 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
668 // copy initial angle
669 self.cnt = self.angles_z;
671 // wait for targets to spawn
672 controller = spawn();
673 controller.classname = "func_pendulum_controller";
674 controller.owner = self;
675 controller.nextthink = time + 1;
676 controller.think = func_pendulum_controller_think;
677 self.nextthink = self.ltime + 999999999;
678 self.think = SUB_NullThink; // for PushMove
680 //self.effects |= EF_LOWPRECISION;
682 // TODO make a reset function for this one
685 // button and multiple button
688 void() button_return;
692 self.state = STATE_TOP;
693 self.nextthink = self.ltime + self.wait;
694 self.think = button_return;
695 activator = self.enemy;
697 self.frame = 1; // use alternate textures
702 self.state = STATE_BOTTOM;
707 self.state = STATE_DOWN;
708 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
709 self.frame = 0; // use normal textures
711 self.takedamage = DAMAGE_YES; // can be shot again
715 void button_blocked()
717 // do nothing, just don't come all the way back out
723 self.health = self.max_health;
724 self.takedamage = DAMAGE_NO; // will be reset upon return
726 if (self.state == STATE_UP || self.state == STATE_TOP)
729 if (self.noise != "")
730 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
732 self.state = STATE_UP;
733 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
738 self.health = self.max_health;
739 setorigin(self, self.pos1);
740 self.frame = 0; // use normal textures
741 self.state = STATE_BOTTOM;
743 self.takedamage = DAMAGE_YES; // can be shot again
748 if(self.active != ACTIVE_ACTIVE)
751 self.enemy = activator;
759 if (!other.iscreature)
761 if(other.velocity * self.movedir < 0)
765 self.enemy = other.owner;
769 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
771 if(self.spawnflags & DOOR_NOSPLASH)
772 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
774 self.health = self.health - damage;
775 if (self.health <= 0)
777 self.enemy = damage_attacker;
783 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
784 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.
786 "angle" determines the opening direction
787 "target" all entities with a matching targetname will be used
788 "speed" override the default 40 speed
789 "wait" override the default 1 second wait (-1 = never return)
790 "lip" override the default 4 pixel lip remaining at end of move
791 "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 InstaGib laser
798 void spawnfunc_func_button()
802 if (!InitMovingBrushTrigger())
804 self.effects |= EF_LOWPRECISION;
806 self.blocked = button_blocked;
807 self.use = button_use;
809 // if (self.health == 0) // all buttons are now shootable
813 self.max_health = self.health;
814 self.event_damage = button_damage;
815 self.takedamage = DAMAGE_YES;
818 self.touch = button_touch;
828 precache_sound(self.noise);
830 self.active = ACTIVE_ACTIVE;
832 self.pos1 = self.origin;
833 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
834 self.flags |= FL_NOTARGET;
839 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
840 if two doors touch, they are assumed to be connected and operate as a unit.
842 TOGGLE causes the door to wait in both the start and end states for a trigger event.
844 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
845 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
846 must have set trigger_reverse to 1.
847 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
849 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).
851 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
852 "angle" determines the destination angle for opening. negative values reverse the direction.
853 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
854 "health" if set, door must be shot open
855 "speed" movement speed (100 default)
856 "wait" wait before returning (3 default, -1 = never return)
857 "dmg" damage to inflict when blocked (2 default)
864 FIXME: only one sound set available at the time being
867 void door_rotating_reset()
869 self.angles = self.pos1;
870 self.avelocity = '0 0 0';
871 self.state = STATE_BOTTOM;
872 self.think = func_null;
876 void door_rotating_init_startopen()
878 self.angles = self.movedir;
880 self.pos1 = self.movedir;
884 void spawnfunc_func_door_rotating()
887 //if (!self.deathtype) // map makers can override this
888 // self.deathtype = " got in the way";
890 // I abuse "movedir" for denoting the axis for now
891 if (self.spawnflags & 64) // X (untested)
892 self.movedir = '0 0 1';
893 else if (self.spawnflags & 128) // Y (untested)
894 self.movedir = '1 0 0';
896 self.movedir = '0 1 0';
898 if (self.angles_y==0) self.angles_y = 90;
900 self.movedir = self.movedir * self.angles_y;
901 self.angles = '0 0 0';
903 self.max_health = self.health;
904 self.avelocity = self.movedir;
905 if (!InitMovingBrushTrigger())
907 self.velocity = '0 0 0';
908 //self.effects |= EF_LOWPRECISION;
909 self.classname = "door_rotating";
911 self.blocked = door_blocked;
914 if(self.spawnflags & 8)
917 if(self.dmg && (self.message == ""))
918 self.message = "was squished";
919 if(self.dmg && (self.message2 == ""))
920 self.message2 = "was squished by";
924 precache_sound ("plats/medplat1.wav");
925 precache_sound ("plats/medplat2.wav");
926 self.noise2 = "plats/medplat1.wav";
927 self.noise1 = "plats/medplat2.wav";
934 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
937 self.pos2 = self.movedir;
939 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
940 // but spawn in the open position
941 if (self.spawnflags & DOOR_START_OPEN)
942 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
944 self.state = STATE_BOTTOM;
948 self.takedamage = DAMAGE_YES;
949 self.event_damage = door_damage;
955 self.touch = door_touch;
957 // LinkDoors can't be done until all of the doors have been spawned, so
958 // the sizes can be detected properly.
959 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
961 self.reset = door_rotating_reset;
965 =============================================================================
969 =============================================================================
972 void() fd_secret_move1;
973 void() fd_secret_move2;
974 void() fd_secret_move3;
975 void() fd_secret_move4;
976 void() fd_secret_move5;
977 void() fd_secret_move6;
978 void() fd_secret_done;
980 const float SECRET_OPEN_ONCE = 1; // stays open
981 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
982 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
983 const float SECRET_NO_SHOOT = 8; // only opened by trigger
984 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
992 self.bot_attack = TRUE;
994 // exit if still moving around...
995 if (self.origin != self.oldorigin)
998 message_save = self.message;
999 self.message = ""; // no more message
1000 SUB_UseTargets(); // fire all targets / killtargets
1001 self.message = message_save;
1003 self.velocity = '0 0 0';
1005 // Make a sound, wait a little...
1007 if (self.noise1 != "")
1008 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1009 self.nextthink = self.ltime + 0.1;
1011 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1012 makevectors(self.mangle);
1016 if (self.spawnflags & SECRET_1ST_DOWN)
1017 self.t_width = fabs(v_up * self.size);
1019 self.t_width = fabs(v_right * self.size);
1023 self.t_length = fabs(v_forward * self.size);
1025 if (self.spawnflags & SECRET_1ST_DOWN)
1026 self.dest1 = self.origin - v_up * self.t_width;
1028 self.dest1 = self.origin + v_right * (self.t_width * temp);
1030 self.dest2 = self.dest1 + v_forward * self.t_length;
1031 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1032 if (self.noise2 != "")
1033 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1036 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1041 // Wait after first movement...
1042 void fd_secret_move1()
1044 self.nextthink = self.ltime + 1.0;
1045 self.think = fd_secret_move2;
1046 if (self.noise3 != "")
1047 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1050 // Start moving sideways w/sound...
1051 void fd_secret_move2()
1053 if (self.noise2 != "")
1054 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1055 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1058 // Wait here until time to go back...
1059 void fd_secret_move3()
1061 if (self.noise3 != "")
1062 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1063 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1065 self.nextthink = self.ltime + self.wait;
1066 self.think = fd_secret_move4;
1071 void fd_secret_move4()
1073 if (self.noise2 != "")
1074 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1075 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1079 void fd_secret_move5()
1081 self.nextthink = self.ltime + 1.0;
1082 self.think = fd_secret_move6;
1083 if (self.noise3 != "")
1084 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1087 void fd_secret_move6()
1089 if (self.noise2 != "")
1090 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1091 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1094 void fd_secret_done()
1096 if (self.spawnflags&SECRET_YES_SHOOT)
1098 self.health = 10000;
1099 self.takedamage = DAMAGE_YES;
1100 //self.th_pain = fd_secret_use;
1102 if (self.noise3 != "")
1103 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1106 void secret_blocked()
1108 if (time < self.attack_finished_single)
1110 self.attack_finished_single = time + 0.5;
1111 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1123 if (!other.iscreature)
1125 if (self.attack_finished_single > time)
1128 self.attack_finished_single = time + 2;
1132 if (IS_CLIENT(other))
1133 centerprint(other, self.message);
1134 play2(other, "misc/talk.wav");
1140 if (self.spawnflags&SECRET_YES_SHOOT)
1142 self.health = 10000;
1143 self.takedamage = DAMAGE_YES;
1145 setorigin(self, self.oldorigin);
1146 self.think = func_null;
1150 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1151 Basic secret door. Slides back, then to the side. Angle determines direction.
1152 wait = # of seconds before coming back
1153 1st_left = 1st move is left of arrow
1154 1st_down = 1st move is down from arrow
1155 always_shoot = even if targeted, keep shootable
1156 t_width = override WIDTH to move back (or height if going down)
1157 t_length = override LENGTH to move sideways
1158 "dmg" damage to inflict when blocked (2 default)
1160 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1167 void spawnfunc_func_door_secret()
1169 /*if (!self.deathtype) // map makers can override this
1170 self.deathtype = " got in the way";*/
1176 self.mangle = self.angles;
1177 self.angles = '0 0 0';
1178 self.classname = "door";
1179 if (!InitMovingBrushTrigger())
1181 self.effects |= EF_LOWPRECISION;
1183 self.touch = secret_touch;
1184 self.blocked = secret_blocked;
1186 self.use = fd_secret_use;
1191 self.spawnflags |= SECRET_YES_SHOOT;
1193 if(self.spawnflags&SECRET_YES_SHOOT)
1195 self.health = 10000;
1196 self.takedamage = DAMAGE_YES;
1197 self.event_damage = fd_secret_damage;
1199 self.oldorigin = self.origin;
1201 self.wait = 5; // 5 seconds before closing
1203 self.reset = secret_reset;
1207 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1208 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1209 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
1210 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1211 height: amplitude modifier (default 32)
1212 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1213 noise: path/name of looping .wav file to play.
1214 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1218 void func_fourier_controller_think()
1223 self.nextthink = time + 0.1;
1224 if(self.owner.active != ACTIVE_ACTIVE)
1226 self.owner.velocity = '0 0 0';
1231 n = floor((tokenize_console(self.owner.netname)) / 5);
1232 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1234 v = self.owner.destvec;
1236 for(i = 0; i < n; ++i)
1238 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1239 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;
1242 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1243 // * 10 so it will arrive in 0.1 sec
1244 self.owner.velocity = (v - self.owner.origin) * 10;
1247 void spawnfunc_func_fourier()
1250 if (self.noise != "")
1252 precache_sound(self.noise);
1253 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1260 self.destvec = self.origin;
1261 self.cnt = 360 / self.speed;
1263 self.blocked = generic_plat_blocked;
1264 if(self.dmg && (self.message == ""))
1265 self.message = " was squished";
1266 if(self.dmg && (self.message2 == ""))
1267 self.message2 = "was squished by";
1268 if(self.dmg && (!self.dmgtime))
1269 self.dmgtime = 0.25;
1270 self.dmgtime2 = time;
1272 if(self.netname == "")
1273 self.netname = "1 0 0 0 1";
1275 if (!InitMovingBrushTrigger())
1278 self.active = ACTIVE_ACTIVE;
1280 // wait for targets to spawn
1281 controller = spawn();
1282 controller.classname = "func_fourier_controller";
1283 controller.owner = self;
1284 controller.nextthink = time + 1;
1285 controller.think = func_fourier_controller_think;
1286 self.nextthink = self.ltime + 999999999;
1287 self.think = SUB_NullThink; // for PushMove
1289 // Savage: Reduce bandwith, critical on e.g. nexdm02
1290 self.effects |= EF_LOWPRECISION;
1292 // TODO make a reset function for this one
1295 // reusing some fields havocbots declared
1296 .entity wp00, wp01, wp02, wp03;
1298 .float targetfactor, target2factor, target3factor, target4factor;
1299 .vector targetnormal, target2normal, target3normal, target4normal;
1301 vector func_vectormamamam_origin(entity o, float t)
1313 p = e.origin + t * e.velocity;
1315 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1317 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1323 p = e.origin + t * e.velocity;
1325 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1327 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1333 p = e.origin + t * e.velocity;
1335 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1337 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1343 p = e.origin + t * e.velocity;
1345 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1347 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1353 void func_vectormamamam_controller_think()
1355 self.nextthink = time + 0.1;
1357 if(self.owner.active != ACTIVE_ACTIVE)
1359 self.owner.velocity = '0 0 0';
1363 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1364 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1367 void func_vectormamamam_findtarget()
1369 if(self.target != "")
1370 self.wp00 = find(world, targetname, self.target);
1372 if(self.target2 != "")
1373 self.wp01 = find(world, targetname, self.target2);
1375 if(self.target3 != "")
1376 self.wp02 = find(world, targetname, self.target3);
1378 if(self.target4 != "")
1379 self.wp03 = find(world, targetname, self.target4);
1381 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1382 objerror("No reference entity found, so there is nothing to move. Aborting.");
1384 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
1387 controller = spawn();
1388 controller.classname = "func_vectormamamam_controller";
1389 controller.owner = self;
1390 controller.nextthink = time + 1;
1391 controller.think = func_vectormamamam_controller_think;
1394 void spawnfunc_func_vectormamamam()
1396 if (self.noise != "")
1398 precache_sound(self.noise);
1399 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1402 if(!self.targetfactor)
1403 self.targetfactor = 1;
1405 if(!self.target2factor)
1406 self.target2factor = 1;
1408 if(!self.target3factor)
1409 self.target3factor = 1;
1411 if(!self.target4factor)
1412 self.target4factor = 1;
1414 if(vlen(self.targetnormal))
1415 self.targetnormal = normalize(self.targetnormal);
1417 if(vlen(self.target2normal))
1418 self.target2normal = normalize(self.target2normal);
1420 if(vlen(self.target3normal))
1421 self.target3normal = normalize(self.target3normal);
1423 if(vlen(self.target4normal))
1424 self.target4normal = normalize(self.target4normal);
1426 self.blocked = generic_plat_blocked;
1427 if(self.dmg && (self.message == ""))
1428 self.message = " was squished";
1429 if(self.dmg && (self.message == ""))
1430 self.message2 = "was squished by";
1431 if(self.dmg && (!self.dmgtime))
1432 self.dmgtime = 0.25;
1433 self.dmgtime2 = time;
1435 if(self.netname == "")
1436 self.netname = "1 0 0 0 1";
1438 if (!InitMovingBrushTrigger())
1441 // wait for targets to spawn
1442 self.nextthink = self.ltime + 999999999;
1443 self.think = SUB_NullThink; // for PushMove
1445 // Savage: Reduce bandwith, critical on e.g. nexdm02
1446 self.effects |= EF_LOWPRECISION;
1448 self.active = ACTIVE_ACTIVE;
1450 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
1455 void conveyor_think()
1458 // TODO: check if this is what is causing the glitchiness when switching between them
1459 float dt = time - self.move_time;
1460 self.move_time = time;
1461 if(dt <= 0) { return; }
1465 // set myself as current conveyor where possible
1466 for(e = world; (e = findentity(e, conveyor, self)); )
1471 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
1472 if(!e.conveyor.state)
1475 vector emin = e.absmin;
1476 vector emax = e.absmax;
1477 if(self.solid == SOLID_BSP)
1482 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
1483 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
1487 for(e = world; (e = findentity(e, conveyor, self)); )
1489 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
1490 continue; // done in SV_PlayerPhysics continue;
1492 setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
1493 move_out_of_solid(e);
1495 UpdateCSQCProjectile(e);
1498 // stupid conveyor code
1499 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
1500 if(trace_fraction > 0)
1501 setorigin(e, trace_endpos);
1507 self.nextthink = time;
1515 self.state = !self.state;
1517 self.SendFlags |= 2;
1520 void conveyor_reset()
1522 self.state = (self.spawnflags & 1);
1524 self.SendFlags |= 2;
1527 float conveyor_send(entity to, float sf)
1529 WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
1530 WriteByte(MSG_ENTITY, sf);
1534 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
1535 WriteCoord(MSG_ENTITY, self.origin_x);
1536 WriteCoord(MSG_ENTITY, self.origin_y);
1537 WriteCoord(MSG_ENTITY, self.origin_z);
1539 WriteCoord(MSG_ENTITY, self.mins_x);
1540 WriteCoord(MSG_ENTITY, self.mins_y);
1541 WriteCoord(MSG_ENTITY, self.mins_z);
1542 WriteCoord(MSG_ENTITY, self.maxs_x);
1543 WriteCoord(MSG_ENTITY, self.maxs_y);
1544 WriteCoord(MSG_ENTITY, self.maxs_z);
1546 WriteCoord(MSG_ENTITY, self.movedir_x);
1547 WriteCoord(MSG_ENTITY, self.movedir_y);
1548 WriteCoord(MSG_ENTITY, self.movedir_z);
1550 WriteByte(MSG_ENTITY, self.speed);
1551 WriteByte(MSG_ENTITY, self.state);
1553 WriteString(MSG_ENTITY, self.targetname);
1554 WriteString(MSG_ENTITY, self.target);
1558 WriteByte(MSG_ENTITY, self.state);
1563 void conveyor_init()
1567 self.movedir = self.movedir * self.speed;
1568 self.think = conveyor_think;
1569 self.nextthink = time;
1572 self.use = conveyor_use;
1573 self.reset = conveyor_reset;
1579 Net_LinkEntity(self, 0, FALSE, conveyor_send);
1581 self.SendFlags |= 1;
1584 void spawnfunc_trigger_conveyor()
1591 void spawnfunc_func_conveyor()
1594 InitMovingBrushTrigger();
1595 self.movetype = MOVETYPE_NONE;
1601 void conveyor_init()
1603 self.draw = conveyor_think;
1604 self.drawmask = MASK_NORMAL;
1606 self.movetype = MOVETYPE_NONE;
1608 self.solid = SOLID_TRIGGER;
1609 self.move_origin = self.origin;
1610 self.move_time = time;
1615 float sf = ReadByte();
1619 self.warpzone_isboxy = ReadByte();
1620 self.origin_x = ReadCoord();
1621 self.origin_y = ReadCoord();
1622 self.origin_z = ReadCoord();
1623 setorigin(self, self.origin);
1625 self.mins_x = ReadCoord();
1626 self.mins_y = ReadCoord();
1627 self.mins_z = ReadCoord();
1628 self.maxs_x = ReadCoord();
1629 self.maxs_y = ReadCoord();
1630 self.maxs_z = ReadCoord();
1631 setsize(self, self.mins, self.maxs);
1633 self.movedir_x = ReadCoord();
1634 self.movedir_y = ReadCoord();
1635 self.movedir_z = ReadCoord();
1637 self.speed = ReadByte();
1638 self.state = ReadByte();
1640 self.targetname = strzone(ReadString());
1641 self.target = strzone(ReadString());
1647 self.state = ReadByte();