1 #include "g_triggers.qh"
4 #include "weapons/csqcprojectile.qh"
5 #include "../common/deathtypes.qh"
6 #include "../warpzonelib/util_server.qh"
8 #include "t_jumppads.qh"
10 void SUB_DontUseTargets()
17 activator = self.enemy;
23 ==============================
26 the global "activator" should be set to the entity that initiated the firing.
28 If self.delay is set, a DelayedUse entity will be created that will actually
29 do the SUB_UseTargets after that many seconds have passed.
31 Centerprints any self.message to the activator.
33 Removes all entities with a targetname that match self.killtarget,
34 and removes them, so some events can remove other triggers.
36 Search for (string)targetname in all entities that
37 match (string)self.target and call their .use function
39 ==============================
43 entity t, stemp, otemp, act;
52 // create a temp object to fire at a later time
54 t.classname = "DelayedUse";
55 t.nextthink = time + self.delay;
58 t.message = self.message;
59 t.killtarget = self.killtarget;
60 t.target = self.target;
61 t.target2 = self.target2;
62 t.target3 = self.target3;
63 t.target4 = self.target4;
72 if(IS_PLAYER(activator) && self.message != "")
73 if(IS_REAL_CLIENT(activator))
75 centerprint(activator, self.message);
77 play2(activator, "misc/talk.wav");
81 // kill the killtagets
86 for(t = world; (t = find(t, targetname, s)); )
97 if(stemp.target_random)
98 RandomSelection_Init();
100 for(i = 0; i < 4; ++i)
105 case 0: s = stemp.target; break;
106 case 1: s = stemp.target2; break;
107 case 2: s = stemp.target3; break;
108 case 3: s = stemp.target4; break;
112 for(t = world; (t = find(t, targetname, s)); )
115 if(stemp.target_random)
117 RandomSelection_Add(t, 0, string_null, 1, 0);
130 if(stemp.target_random && RandomSelection_chosen_ent)
132 self = RandomSelection_chosen_ent;
144 //=============================================================================
146 // the wait time has passed, so set back up for another activation
151 self.health = self.max_health;
152 self.takedamage = DAMAGE_YES;
153 self.solid = SOLID_BBOX;
158 // the trigger was just touched/killed/used
159 // self.enemy should be set to the activator so it can be held through a delay
160 // so wait for the delay time before firing
163 if (self.nextthink > time)
165 return; // allready been triggered
168 if (self.classname == "trigger_secret")
170 if (!IS_PLAYER(self.enemy))
172 found_secrets = found_secrets + 1;
173 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
177 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
179 // don't trigger again until reset
180 self.takedamage = DAMAGE_NO;
182 activator = self.enemy;
183 other = self.goalentity;
188 self.think = multi_wait;
189 self.nextthink = time + self.wait;
191 else if (self.wait == 0)
193 multi_wait(); // waiting finished
196 { // we can't just remove (self) here, because this is a touch function
197 // called wheil C code is looping through area links...
198 self.touch = func_null;
204 self.goalentity = other;
205 self.enemy = activator;
211 if(!(self.spawnflags & 2))
212 if(!other.iscreature)
216 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
219 // if the trigger has an angles field, check player's facing direction
220 if (self.movedir != '0 0 0')
222 makevectors (other.angles);
223 if (v_forward * self.movedir < 0)
224 return; // not facing the right way
230 self.goalentity = other;
234 void multi_eventdamage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
236 if (!self.takedamage)
238 if(self.spawnflags & DOOR_NOSPLASH)
239 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
241 self.health = self.health - damage;
242 if (self.health <= 0)
244 self.enemy = attacker;
245 self.goalentity = inflictor;
252 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
253 self.touch = multi_touch;
256 self.health = self.max_health;
257 self.takedamage = DAMAGE_YES;
258 self.solid = SOLID_BBOX;
260 self.think = func_null;
262 self.team = self.team_saved;
265 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
266 Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
267 If "delay" is set, the trigger waits some time after activating before firing.
268 "wait" : Seconds between triggerings. (.2 default)
269 If notouch is set, the trigger is only fired by other entities, not by touching.
270 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
276 set "message" to text string
278 void spawnfunc_trigger_multiple()
280 self.reset = multi_reset;
281 if (self.sounds == 1)
283 precache_sound ("misc/secret.wav");
284 self.noise = "misc/secret.wav";
286 else if (self.sounds == 2)
288 precache_sound ("misc/talk.wav");
289 self.noise = "misc/talk.wav";
291 else if (self.sounds == 3)
293 precache_sound ("misc/trigger1.wav");
294 self.noise = "misc/trigger1.wav";
299 else if(self.wait < -1)
301 self.use = multi_use;
305 self.team_saved = self.team;
309 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
310 objerror ("health and notouch don't make sense\n");
311 self.max_health = self.health;
312 self.event_damage = multi_eventdamage;
313 self.takedamage = DAMAGE_YES;
314 self.solid = SOLID_BBOX;
315 setorigin (self, self.origin); // make sure it links into the world
319 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
321 self.touch = multi_touch;
322 setorigin (self, self.origin); // make sure it links into the world
328 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
329 Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
330 "targetname". If "health" is set, the trigger must be killed to activate.
331 If notouch is set, the trigger is only fired by other entities, not by touching.
332 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
333 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
339 set "message" to text string
341 void spawnfunc_trigger_once()
344 spawnfunc_trigger_multiple();
347 //=============================================================================
349 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
350 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
352 void spawnfunc_trigger_relay()
354 self.use = SUB_UseTargets;
355 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
360 self.think = SUB_UseTargets;
361 self.nextthink = self.wait;
366 self.think = func_null;
370 void spawnfunc_trigger_delay()
375 self.use = delay_use;
376 self.reset = delay_reset;
379 //=============================================================================
390 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
391 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
393 self.enemy = activator;
398 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
400 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
402 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
408 self.count = self.cnt;
412 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
413 Acts as an intermediary for an action that takes multiple inputs.
415 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
417 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
419 void spawnfunc_trigger_counter()
424 self.cnt = self.count;
426 self.use = counter_use;
427 self.reset = counter_reset;
430 void trigger_hurt_use()
432 if(IS_PLAYER(activator))
433 self.enemy = activator;
435 self.enemy = world; // let's just destroy it, if taking over is too much work
438 void trigger_hurt_touch()
440 if (self.active != ACTIVE_ACTIVE)
444 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
447 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
448 if (other.iscreature)
450 if (other.takedamage)
451 if (other.triggerhurttime < time)
454 other.triggerhurttime = time + 1;
461 self.enemy = world; // I still hate you all
464 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
467 else if(other.damagedbytriggers)
472 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
479 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
480 Any object touching this will be hurt
481 set dmg to damage amount
484 void spawnfunc_trigger_hurt()
487 self.active = ACTIVE_ACTIVE;
488 self.touch = trigger_hurt_touch;
489 self.use = trigger_hurt_use;
490 self.enemy = world; // I hate you all
493 if (self.message == "")
494 self.message = "was in the wrong place";
495 if (self.message2 == "")
496 self.message2 = "was thrown into a world of hurt by";
497 // self.message = "someone like %s always gets wrongplaced";
499 if(!trigger_hurt_first)
500 trigger_hurt_first = self;
501 if(trigger_hurt_last)
502 trigger_hurt_last.trigger_hurt_next = self;
503 trigger_hurt_last = self;
506 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
510 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
511 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
517 //////////////////////////////////////////////////////////////
521 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
523 //////////////////////////////////////////////////////////////
525 void trigger_heal_touch()
527 if (self.active != ACTIVE_ACTIVE)
530 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
531 if (other.iscreature)
533 if (other.takedamage)
535 if (other.triggerhealtime < time)
538 other.triggerhealtime = time + 1;
540 if (other.health < self.max_health)
542 other.health = min(other.health + self.health, self.max_health);
543 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
544 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
550 void spawnfunc_trigger_heal()
552 self.active = ACTIVE_ACTIVE;
555 self.touch = trigger_heal_touch;
558 if (!self.max_health)
559 self.max_health = 200; //Max health topoff for field
561 self.noise = "misc/mediumhealth.wav";
562 precache_sound(self.noise);
566 //////////////////////////////////////////////////////////////
572 //////////////////////////////////////////////////////////////
574 void trigger_gravity_remove(entity own)
576 if(own.trigger_gravity_check.owner == own)
578 UpdateCSQCProjectile(own);
579 own.gravity = own.trigger_gravity_check.gravity;
580 remove(own.trigger_gravity_check);
583 backtrace("Removing a trigger_gravity_check with no valid owner");
584 own.trigger_gravity_check = world;
586 void trigger_gravity_check_think()
588 // This spawns when a player enters the gravity zone and checks if he left.
589 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
590 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
593 if(self.owner.trigger_gravity_check == self)
594 trigger_gravity_remove(self.owner);
602 self.nextthink = time;
606 void trigger_gravity_use()
608 self.state = !self.state;
611 void trigger_gravity_touch()
615 if(self.state != true)
622 if (!(self.spawnflags & 1))
624 if(other.trigger_gravity_check)
626 if(self == other.trigger_gravity_check.enemy)
629 other.trigger_gravity_check.count = 2; // gravity one more frame...
634 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
635 trigger_gravity_remove(other);
639 other.trigger_gravity_check = spawn();
640 other.trigger_gravity_check.enemy = self;
641 other.trigger_gravity_check.owner = other;
642 other.trigger_gravity_check.gravity = other.gravity;
643 other.trigger_gravity_check.think = trigger_gravity_check_think;
644 other.trigger_gravity_check.nextthink = time;
645 other.trigger_gravity_check.count = 2;
650 if (other.gravity != g)
654 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
655 UpdateCSQCProjectile(self.owner);
659 void spawnfunc_trigger_gravity()
661 if(self.gravity == 1)
665 self.touch = trigger_gravity_touch;
667 precache_sound(self.noise);
672 self.use = trigger_gravity_use;
673 if(self.spawnflags & 2)
678 //=============================================================================
680 // TODO add a way to do looped sounds with sound(); then complete this entity
681 void target_speaker_use_activator()
683 if (!IS_REAL_CLIENT(activator))
686 if(substring(self.noise, 0, 1) == "*")
689 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
690 if(GetPlayerSoundSampleField_notFound)
691 snd = "misc/null.wav";
692 else if (activator.(sample) == "")
693 snd = "misc/null.wav";
696 tokenize_console(activator.(sample));
700 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
702 snd = strcat(argv(0), ".wav"); // randomization
707 msg_entity = activator;
708 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
710 void target_speaker_use_on()
713 if(substring(self.noise, 0, 1) == "*")
716 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
717 if(GetPlayerSoundSampleField_notFound)
718 snd = "misc/null.wav";
719 else if (activator.(sample) == "")
720 snd = "misc/null.wav";
723 tokenize_console(activator.(sample));
727 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
729 snd = strcat(argv(0), ".wav"); // randomization
734 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
735 if(self.spawnflags & 3)
736 self.use = target_speaker_use_off;
738 void target_speaker_use_off()
740 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
741 self.use = target_speaker_use_on;
743 void target_speaker_reset()
745 if(self.spawnflags & 1) // LOOPED_ON
747 if(self.use == target_speaker_use_on)
748 target_speaker_use_on();
750 else if(self.spawnflags & 2)
752 if(self.use == target_speaker_use_off)
753 target_speaker_use_off();
757 void spawnfunc_target_speaker()
759 // TODO: "*" prefix to sound file name
760 // TODO: wait and random (just, HOW? random is not a field)
762 precache_sound (self.noise);
764 if(!self.atten && !(self.spawnflags & 4))
767 self.atten = ATTEN_NORM;
769 self.atten = ATTEN_STATIC;
771 else if(self.atten < 0)
779 if(self.spawnflags & 8) // ACTIVATOR
780 self.use = target_speaker_use_activator;
781 else if(self.spawnflags & 1) // LOOPED_ON
783 target_speaker_use_on();
784 self.reset = target_speaker_reset;
786 else if(self.spawnflags & 2) // LOOPED_OFF
788 self.use = target_speaker_use_on;
789 self.reset = target_speaker_reset;
792 self.use = target_speaker_use_on;
794 else if(self.spawnflags & 1) // LOOPED_ON
796 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
799 else if(self.spawnflags & 2) // LOOPED_OFF
801 objerror("This sound entity can never be activated");
805 // Quake/Nexuiz fallback
806 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
812 void spawnfunc_func_stardust() {
813 self.effects = EF_STARDUST;
816 float pointparticles_SendEntity(entity to, float fl)
818 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
820 // optional features to save space
822 if(self.spawnflags & 2)
823 fl |= 0x10; // absolute count on toggle-on
824 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
825 fl |= 0x20; // 4 bytes - saves CPU
826 if(self.waterlevel || self.count != 1)
827 fl |= 0x40; // 4 bytes - obscure features almost never used
828 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
829 fl |= 0x80; // 14 bytes - saves lots of space
831 WriteByte(MSG_ENTITY, fl);
835 WriteCoord(MSG_ENTITY, self.impulse);
837 WriteCoord(MSG_ENTITY, 0); // off
841 WriteCoord(MSG_ENTITY, self.origin.x);
842 WriteCoord(MSG_ENTITY, self.origin.y);
843 WriteCoord(MSG_ENTITY, self.origin.z);
847 if(self.model != "null")
849 WriteShort(MSG_ENTITY, self.modelindex);
852 WriteCoord(MSG_ENTITY, self.mins.x);
853 WriteCoord(MSG_ENTITY, self.mins.y);
854 WriteCoord(MSG_ENTITY, self.mins.z);
855 WriteCoord(MSG_ENTITY, self.maxs.x);
856 WriteCoord(MSG_ENTITY, self.maxs.y);
857 WriteCoord(MSG_ENTITY, self.maxs.z);
862 WriteShort(MSG_ENTITY, 0);
865 WriteCoord(MSG_ENTITY, self.maxs.x);
866 WriteCoord(MSG_ENTITY, self.maxs.y);
867 WriteCoord(MSG_ENTITY, self.maxs.z);
870 WriteShort(MSG_ENTITY, self.cnt);
873 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
874 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
878 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
879 WriteByte(MSG_ENTITY, self.count * 16.0);
881 WriteString(MSG_ENTITY, self.noise);
884 WriteByte(MSG_ENTITY, floor(self.atten * 64));
885 WriteByte(MSG_ENTITY, floor(self.volume * 255));
887 WriteString(MSG_ENTITY, self.bgmscript);
888 if(self.bgmscript != "")
890 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
891 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
892 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
893 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
899 void pointparticles_use()
901 self.state = !self.state;
905 void pointparticles_think()
907 if(self.origin != self.oldorigin)
910 self.oldorigin = self.origin;
912 self.nextthink = time;
915 void pointparticles_reset()
917 if(self.spawnflags & 1)
923 void spawnfunc_func_pointparticles()
926 setmodel(self, self.model);
928 precache_sound (self.noise);
930 if(!self.bgmscriptsustain)
931 self.bgmscriptsustain = 1;
932 else if(self.bgmscriptsustain < 0)
933 self.bgmscriptsustain = 0;
936 self.atten = ATTEN_NORM;
937 else if(self.atten < 0)
948 setorigin(self, self.origin + self.mins);
949 setsize(self, '0 0 0', self.maxs - self.mins);
952 self.cnt = particleeffectnum(self.mdl);
954 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
958 self.use = pointparticles_use;
959 self.reset = pointparticles_reset;
964 self.think = pointparticles_think;
965 self.nextthink = time;
968 void spawnfunc_func_sparks()
970 // self.cnt is the amount of sparks that one burst will spawn
972 self.cnt = 25.0; // nice default value
975 // self.wait is the probability that a sparkthink will spawn a spark shower
976 // range: 0 - 1, but 0 makes little sense, so...
977 if(self.wait < 0.05) {
978 self.wait = 0.25; // nice default value
981 self.count = self.cnt;
984 self.velocity = '0 0 -1';
985 self.mdl = "TE_SPARK";
986 self.impulse = 10 * self.wait; // by default 2.5/sec
988 self.cnt = 0; // use mdl
990 spawnfunc_func_pointparticles();
993 float rainsnow_SendEntity(entity to, int sf)
995 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
996 WriteByte(MSG_ENTITY, self.state);
997 WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
998 WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
999 WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
1000 WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
1001 WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
1002 WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
1003 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1004 WriteShort(MSG_ENTITY, self.count);
1005 WriteByte(MSG_ENTITY, self.cnt);
1009 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1010 This is an invisible area like a trigger, which rain falls inside of.
1014 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1016 sets color of rain (default 12 - white)
1018 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1020 void spawnfunc_func_rain()
1022 self.dest = self.velocity;
1023 self.velocity = '0 0 0';
1025 self.dest = '0 0 -700';
1026 self.angles = '0 0 0';
1027 self.movetype = MOVETYPE_NONE;
1028 self.solid = SOLID_NOT;
1029 SetBrushEntityModel();
1034 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1037 if(self.count > 65535)
1040 self.state = 1; // 1 is rain, 0 is snow
1043 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1047 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1048 This is an invisible area like a trigger, which snow falls inside of.
1052 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1054 sets color of rain (default 12 - white)
1056 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1058 void spawnfunc_func_snow()
1060 self.dest = self.velocity;
1061 self.velocity = '0 0 0';
1063 self.dest = '0 0 -300';
1064 self.angles = '0 0 0';
1065 self.movetype = MOVETYPE_NONE;
1066 self.solid = SOLID_NOT;
1067 SetBrushEntityModel();
1072 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1075 if(self.count > 65535)
1078 self.state = 0; // 1 is rain, 0 is snow
1081 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1084 void misc_laser_aim()
1089 if(self.spawnflags & 2)
1091 if(self.enemy.origin != self.mangle)
1093 self.mangle = self.enemy.origin;
1094 self.SendFlags |= 2;
1099 a = vectoangles(self.enemy.origin - self.origin);
1101 if(a != self.mangle)
1104 self.SendFlags |= 2;
1110 if(self.angles != self.mangle)
1112 self.mangle = self.angles;
1113 self.SendFlags |= 2;
1116 if(self.origin != self.oldorigin)
1118 self.SendFlags |= 1;
1119 self.oldorigin = self.origin;
1123 void misc_laser_init()
1125 if(self.target != "")
1126 self.enemy = find(world, targetname, self.target);
1129 void misc_laser_think()
1136 self.nextthink = time;
1145 o = self.enemy.origin;
1146 if (!(self.spawnflags & 2))
1147 o = self.origin + normalize(o - self.origin) * 32768;
1151 makevectors(self.mangle);
1152 o = self.origin + v_forward * 32768;
1155 if(self.dmg || self.enemy.target != "")
1157 traceline(self.origin, o, MOVE_NORMAL, self);
1160 hitloc = trace_endpos;
1162 if(self.enemy.target != "") // DETECTOR laser
1164 if(trace_ent.iscreature)
1166 self.pusher = hitent;
1173 activator = self.pusher;
1186 activator = self.pusher;
1196 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1198 if(hitent.takedamage)
1199 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1203 float laser_SendEntity(entity to, float fl)
1205 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1206 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1207 if(self.spawnflags & 2)
1211 if(self.scale != 1 || self.modelscale != 1)
1213 if(self.spawnflags & 4)
1215 WriteByte(MSG_ENTITY, fl);
1218 WriteCoord(MSG_ENTITY, self.origin.x);
1219 WriteCoord(MSG_ENTITY, self.origin.y);
1220 WriteCoord(MSG_ENTITY, self.origin.z);
1224 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
1225 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
1226 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
1228 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1231 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1232 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1234 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1235 WriteShort(MSG_ENTITY, self.cnt + 1);
1241 WriteCoord(MSG_ENTITY, self.enemy.origin.x);
1242 WriteCoord(MSG_ENTITY, self.enemy.origin.y);
1243 WriteCoord(MSG_ENTITY, self.enemy.origin.z);
1247 WriteAngle(MSG_ENTITY, self.mangle.x);
1248 WriteAngle(MSG_ENTITY, self.mangle.y);
1252 WriteByte(MSG_ENTITY, self.state);
1256 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1257 Any object touching the beam will be hurt
1260 spawnfunc_target_position where the laser ends
1262 name of beam end effect to use
1264 color of the beam (default: red)
1266 damage per second (-1 for a laser that kills immediately)
1270 self.state = !self.state;
1271 self.SendFlags |= 4;
1277 if(self.spawnflags & 1)
1283 void spawnfunc_misc_laser()
1287 if(self.mdl == "none")
1291 self.cnt = particleeffectnum(self.mdl);
1294 self.cnt = particleeffectnum("laser_deadly");
1300 self.cnt = particleeffectnum("laser_deadly");
1307 if(self.colormod == '0 0 0')
1309 self.colormod = '1 0 0';
1310 if(self.message == "")
1311 self.message = "saw the light";
1312 if (self.message2 == "")
1313 self.message2 = "was pushed into a laser by";
1316 if(!self.modelscale)
1317 self.modelscale = 1;
1318 else if(self.modelscale < 0)
1319 self.modelscale = 0;
1320 self.think = misc_laser_think;
1321 self.nextthink = time;
1322 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1324 self.mangle = self.angles;
1326 Net_LinkEntity(self, false, 0, laser_SendEntity);
1330 self.reset = laser_reset;
1332 self.use = laser_use;
1338 // tZorks trigger impulse / gravity
1340 // targeted (directional) mode
1341 void trigger_impulse_touch1()
1344 float pushdeltatime;
1347 if (self.active != ACTIVE_ACTIVE)
1350 if (!isPushable(other))
1355 targ = find(world, targetname, self.target);
1358 objerror("trigger_force without a (valid) .target!\n");
1363 str = min(self.radius, vlen(self.origin - other.origin));
1365 if(self.falloff == 1)
1366 str = (str / self.radius) * self.strength;
1367 else if(self.falloff == 2)
1368 str = (1 - (str / self.radius)) * self.strength;
1370 str = self.strength;
1372 pushdeltatime = time - other.lastpushtime;
1373 if (pushdeltatime > 0.15) pushdeltatime = 0;
1374 other.lastpushtime = time;
1375 if(!pushdeltatime) return;
1377 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1378 other.flags &= ~FL_ONGROUND;
1379 UpdateCSQCProjectile(other);
1382 // Directionless (accelerator/decelerator) mode
1383 void trigger_impulse_touch2()
1385 float pushdeltatime;
1387 if (self.active != ACTIVE_ACTIVE)
1390 if (!isPushable(other))
1395 pushdeltatime = time - other.lastpushtime;
1396 if (pushdeltatime > 0.15) pushdeltatime = 0;
1397 other.lastpushtime = time;
1398 if(!pushdeltatime) return;
1400 // div0: ticrate independent, 1 = identity (not 20)
1401 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1402 UpdateCSQCProjectile(other);
1405 // Spherical (gravity/repulsor) mode
1406 void trigger_impulse_touch3()
1408 float pushdeltatime;
1411 if (self.active != ACTIVE_ACTIVE)
1414 if (!isPushable(other))
1419 pushdeltatime = time - other.lastpushtime;
1420 if (pushdeltatime > 0.15) pushdeltatime = 0;
1421 other.lastpushtime = time;
1422 if(!pushdeltatime) return;
1424 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1426 str = min(self.radius, vlen(self.origin - other.origin));
1428 if(self.falloff == 1)
1429 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1430 else if(self.falloff == 2)
1431 str = (str / self.radius) * self.strength; // 0 in the inside
1433 str = self.strength;
1435 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1436 UpdateCSQCProjectile(other);
1439 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1440 -------- KEYS --------
1441 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1442 If not, this trigger acts like a damper/accelerator field.
1444 strength : This is how mutch force to add in the direction of .target each second
1445 when .target is set. If not, this is hoe mutch to slow down/accelerate
1446 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1448 radius : If set, act as a spherical device rather then a liniar one.
1450 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1452 -------- NOTES --------
1453 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1454 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1457 void spawnfunc_trigger_impulse()
1459 self.active = ACTIVE_ACTIVE;
1464 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1465 setorigin(self, self.origin);
1466 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1467 self.touch = trigger_impulse_touch3;
1473 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1474 self.touch = trigger_impulse_touch1;
1478 if(!self.strength) self.strength = 0.9;
1479 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1480 self.touch = trigger_impulse_touch2;
1485 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1486 "Flip-flop" trigger gate... lets only every second trigger event through
1490 self.state = !self.state;
1495 void spawnfunc_trigger_flipflop()
1497 if(self.spawnflags & 1)
1499 self.use = flipflop_use;
1500 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1503 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1504 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1508 self.nextthink = time + self.wait;
1509 self.enemy = activator;
1515 void monoflop_fixed_use()
1519 self.nextthink = time + self.wait;
1521 self.enemy = activator;
1525 void monoflop_think()
1528 activator = self.enemy;
1532 void monoflop_reset()
1538 void spawnfunc_trigger_monoflop()
1542 if(self.spawnflags & 1)
1543 self.use = monoflop_fixed_use;
1545 self.use = monoflop_use;
1546 self.think = monoflop_think;
1548 self.reset = monoflop_reset;
1551 void multivibrator_send()
1556 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1558 newstate = (time < cyclestart + self.wait);
1561 if(self.state != newstate)
1563 self.state = newstate;
1566 self.nextthink = cyclestart + self.wait + 0.01;
1568 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1571 void multivibrator_toggle()
1573 if(self.nextthink == 0)
1575 multivibrator_send();
1588 void multivibrator_reset()
1590 if(!(self.spawnflags & 1))
1591 self.nextthink = 0; // wait for a trigger event
1593 self.nextthink = max(1, time);
1596 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1597 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1598 -------- KEYS --------
1599 target: trigger all entities with this targetname when it goes off
1600 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1601 phase: offset of the timing
1602 wait: "on" cycle time (default: 1)
1603 respawntime: "off" cycle time (default: same as wait)
1604 -------- SPAWNFLAGS --------
1605 START_ON: assume it is already turned on (when targeted)
1607 void spawnfunc_trigger_multivibrator()
1611 if(!self.respawntime)
1612 self.respawntime = self.wait;
1615 self.use = multivibrator_toggle;
1616 self.think = multivibrator_send;
1617 self.nextthink = max(1, time);
1620 multivibrator_reset();
1629 if(self.killtarget != "")
1630 src = find(world, targetname, self.killtarget);
1631 if(self.target != "")
1632 dst = find(world, targetname, self.target);
1636 objerror("follow: could not find target/killtarget");
1642 // already done :P entity must stay
1646 else if(!src || !dst)
1648 objerror("follow: could not find target/killtarget");
1651 else if(self.spawnflags & 1)
1654 if(self.spawnflags & 2)
1656 setattachment(dst, src, self.message);
1660 attach_sameorigin(dst, src, self.message);
1663 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1668 if(self.spawnflags & 2)
1670 dst.movetype = MOVETYPE_FOLLOW;
1672 // dst.punchangle = '0 0 0'; // keep unchanged
1673 dst.view_ofs = dst.origin;
1674 dst.v_angle = dst.angles;
1678 follow_sameorigin(dst, src);
1685 void spawnfunc_misc_follow()
1687 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1692 void gamestart_use() {
1698 void spawnfunc_trigger_gamestart() {
1699 self.use = gamestart_use;
1700 self.reset2 = spawnfunc_trigger_gamestart;
1704 self.think = self.use;
1705 self.nextthink = game_starttime + self.wait;
1708 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1714 void target_voicescript_clear(entity pl)
1716 pl.voicescript = world;
1719 void target_voicescript_use()
1721 if(activator.voicescript != self)
1723 activator.voicescript = self;
1724 activator.voicescript_index = 0;
1725 activator.voicescript_nextthink = time + self.delay;
1729 void target_voicescript_next(entity pl)
1734 vs = pl.voicescript;
1737 if(vs.message == "")
1744 if(time >= pl.voicescript_voiceend)
1746 if(time >= pl.voicescript_nextthink)
1748 // get the next voice...
1749 n = tokenize_console(vs.message);
1751 if(pl.voicescript_index < vs.cnt)
1752 i = pl.voicescript_index * 2;
1753 else if(n > vs.cnt * 2)
1754 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1760 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1761 dt = stof(argv(i + 1));
1764 pl.voicescript_voiceend = time + dt;
1765 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1769 pl.voicescript_voiceend = time - dt;
1770 pl.voicescript_nextthink = pl.voicescript_voiceend;
1773 pl.voicescript_index += 1;
1777 pl.voicescript = world; // stop trying then
1783 void spawnfunc_target_voicescript()
1785 // netname: directory of the sound files
1786 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1787 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1788 // Here, a - in front of the duration means that no delay is to be
1789 // added after this message
1790 // wait: average time between messages
1791 // delay: initial delay before the first message
1794 self.use = target_voicescript_use;
1796 n = tokenize_console(self.message);
1798 for(i = 0; i+1 < n; i += 2)
1805 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1811 void trigger_relay_teamcheck_use()
1815 if(self.spawnflags & 2)
1817 if(activator.team != self.team)
1822 if(activator.team == self.team)
1828 if(self.spawnflags & 1)
1833 void trigger_relay_teamcheck_reset()
1835 self.team = self.team_saved;
1838 void spawnfunc_trigger_relay_teamcheck()
1840 self.team_saved = self.team;
1841 self.use = trigger_relay_teamcheck_use;
1842 self.reset = trigger_relay_teamcheck_reset;
1847 void trigger_disablerelay_use()
1854 for(e = world; (e = find(e, targetname, self.target)); )
1856 if(e.use == SUB_UseTargets)
1858 e.use = SUB_DontUseTargets;
1861 else if(e.use == SUB_DontUseTargets)
1863 e.use = SUB_UseTargets;
1869 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1872 void spawnfunc_trigger_disablerelay()
1874 self.use = trigger_disablerelay_use;
1877 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1879 float domatch, dotrigger, matchstart, l;
1884 magicear_matched = false;
1886 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1887 domatch = ((ear.spawnflags & 32) || dotrigger);
1894 // we are in TUBA mode!
1895 if (!(ear.spawnflags & 256))
1898 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
1901 magicear_matched = true;
1908 savemessage = self.message;
1909 self.message = string_null;
1911 self.message = savemessage;
1915 if(ear.netname != "")
1921 if(ear.spawnflags & 256) // ENOTUBA
1926 if(ear.spawnflags & 4)
1932 if(ear.spawnflags & 1)
1935 if(ear.spawnflags & 2)
1938 if(ear.spawnflags & 8)
1943 l = strlen(ear.message);
1945 if(ear.spawnflags & 128)
1948 msg = strdecolorize(msgin);
1950 if(substring(ear.message, 0, 1) == "*")
1952 if(substring(ear.message, -1, 1) == "*")
1955 // as we need multi-replacement here...
1956 s = substring(ear.message, 1, -2);
1958 if(strstrofs(msg, s, 0) >= 0)
1959 matchstart = -2; // we use strreplace on s
1964 s = substring(ear.message, 1, -1);
1966 if(substring(msg, -l, l) == s)
1967 matchstart = strlen(msg) - l;
1972 if(substring(ear.message, -1, 1) == "*")
1975 s = substring(ear.message, 0, -2);
1977 if(substring(msg, 0, l) == s)
1984 if(msg == ear.message)
1989 if(matchstart == -1) // no match
1992 magicear_matched = true;
1999 savemessage = self.message;
2000 self.message = string_null;
2002 self.message = savemessage;
2006 if(ear.spawnflags & 16)
2010 else if(ear.netname != "")
2013 return strreplace(s, ear.netname, msg);
2016 substring(msg, 0, matchstart),
2018 substring(msg, matchstart + l, -1)
2025 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2029 for(ear = magicears; ear; ear = ear.enemy)
2031 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2032 if(!(ear.spawnflags & 64))
2033 if(magicear_matched)
2040 void spawnfunc_trigger_magicear()
2042 self.enemy = magicears;
2045 // actually handled in "say" processing
2048 // 2 = ignore teamsay
2050 // 8 = ignore tell to unknown player
2051 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2052 // 32 = perform the replacement even if outside the radius or dead
2053 // 64 = continue replacing/triggering even if this one matched
2054 // 128 = don't decolorize message before matching
2055 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2056 // 512 = tuba notes must be exact right pitch, no transposing
2066 // if set, replacement for the matched text
2068 // "hearing distance"
2072 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2074 self.movedir_x -= 1; // map to tuba instrument numbers
2077 void relay_activators_use()
2083 for(trg = world; (trg = find(trg, targetname, os.target)); )
2087 trg.setactive(os.cnt);
2090 //bprint("Not using setactive\n");
2091 if(os.cnt == ACTIVE_TOGGLE)
2092 if(trg.active == ACTIVE_ACTIVE)
2093 trg.active = ACTIVE_NOT;
2095 trg.active = ACTIVE_ACTIVE;
2097 trg.active = os.cnt;
2103 void spawnfunc_relay_activate()
2105 self.cnt = ACTIVE_ACTIVE;
2106 self.use = relay_activators_use;
2109 void spawnfunc_relay_deactivate()
2111 self.cnt = ACTIVE_NOT;
2112 self.use = relay_activators_use;
2115 void spawnfunc_relay_activatetoggle()
2117 self.cnt = ACTIVE_TOGGLE;
2118 self.use = relay_activators_use;
2121 void spawnfunc_target_changelevel_use()
2123 if(self.gametype != "")
2124 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2126 if (self.chmap == "")
2127 localcmd("endmatch\n");
2129 localcmd(strcat("changelevel ", self.chmap, "\n"));
2132 void spawnfunc_target_changelevel()
2134 self.use = spawnfunc_target_changelevel_use;