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 // Flag to set func_clientwall state
113 // 1 == deactivate, 2 == activate, 0 == do nothing
114 float aw_flag = self.antiwall_flag;
115 for(t = world; (t = find(t, targetname, s)); )
118 if(stemp.target_random)
120 RandomSelection_Add(t, 0, string_null, 1, 0);
124 if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary")
125 t.antiwall_flag = aw_flag;
135 if(stemp.target_random && RandomSelection_chosen_ent)
137 self = RandomSelection_chosen_ent;
149 //=============================================================================
151 // the wait time has passed, so set back up for another activation
156 self.health = self.max_health;
157 self.takedamage = DAMAGE_YES;
158 self.solid = SOLID_BBOX;
163 // the trigger was just touched/killed/used
164 // self.enemy should be set to the activator so it can be held through a delay
165 // so wait for the delay time before firing
168 if (self.nextthink > time)
170 return; // allready been triggered
173 if (self.classname == "trigger_secret")
175 if (!IS_PLAYER(self.enemy))
177 found_secrets = found_secrets + 1;
178 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
182 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
184 // don't trigger again until reset
185 self.takedamage = DAMAGE_NO;
187 activator = self.enemy;
188 other = self.goalentity;
193 self.think = multi_wait;
194 self.nextthink = time + self.wait;
196 else if (self.wait == 0)
198 multi_wait(); // waiting finished
201 { // we can't just remove (self) here, because this is a touch function
202 // called wheil C code is looping through area links...
203 self.touch = func_null;
209 self.goalentity = other;
210 self.enemy = activator;
216 if(!(self.spawnflags & 2))
217 if(!other.iscreature)
221 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
224 // if the trigger has an angles field, check player's facing direction
225 if (self.movedir != '0 0 0')
227 makevectors (other.angles);
228 if (v_forward * self.movedir < 0)
229 return; // not facing the right way
235 self.goalentity = other;
239 void multi_eventdamage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
241 if (!self.takedamage)
243 if(self.spawnflags & DOOR_NOSPLASH)
244 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
246 self.health = self.health - damage;
247 if (self.health <= 0)
249 self.enemy = attacker;
250 self.goalentity = inflictor;
257 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
258 self.touch = multi_touch;
261 self.health = self.max_health;
262 self.takedamage = DAMAGE_YES;
263 self.solid = SOLID_BBOX;
265 self.think = func_null;
267 self.team = self.team_saved;
270 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
271 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.
272 If "delay" is set, the trigger waits some time after activating before firing.
273 "wait" : Seconds between triggerings. (.2 default)
274 If notouch is set, the trigger is only fired by other entities, not by touching.
275 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
281 set "message" to text string
283 void spawnfunc_trigger_multiple()
285 self.reset = multi_reset;
286 if (self.sounds == 1)
288 precache_sound ("misc/secret.wav");
289 self.noise = "misc/secret.wav";
291 else if (self.sounds == 2)
293 precache_sound ("misc/talk.wav");
294 self.noise = "misc/talk.wav";
296 else if (self.sounds == 3)
298 precache_sound ("misc/trigger1.wav");
299 self.noise = "misc/trigger1.wav";
304 else if(self.wait < -1)
306 self.use = multi_use;
310 self.team_saved = self.team;
314 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
315 objerror ("health and notouch don't make sense\n");
316 self.max_health = self.health;
317 self.event_damage = multi_eventdamage;
318 self.takedamage = DAMAGE_YES;
319 self.solid = SOLID_BBOX;
320 setorigin (self, self.origin); // make sure it links into the world
324 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
326 self.touch = multi_touch;
327 setorigin (self, self.origin); // make sure it links into the world
333 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
334 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
335 "targetname". If "health" is set, the trigger must be killed to activate.
336 If notouch is set, the trigger is only fired by other entities, not by touching.
337 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
338 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.
344 set "message" to text string
346 void spawnfunc_trigger_once()
349 spawnfunc_trigger_multiple();
352 //=============================================================================
354 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
355 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
357 void spawnfunc_trigger_relay()
359 self.use = SUB_UseTargets;
360 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
365 self.think = SUB_UseTargets;
366 self.nextthink = self.wait;
371 self.think = func_null;
375 void spawnfunc_trigger_delay()
380 self.use = delay_use;
381 self.reset = delay_reset;
384 //=============================================================================
395 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
396 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
398 self.enemy = activator;
403 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
405 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
407 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
413 self.count = self.cnt;
417 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
418 Acts as an intermediary for an action that takes multiple inputs.
420 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
422 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
424 void spawnfunc_trigger_counter()
429 self.cnt = self.count;
431 self.use = counter_use;
432 self.reset = counter_reset;
435 void trigger_hurt_use()
437 if(IS_PLAYER(activator))
438 self.enemy = activator;
440 self.enemy = world; // let's just destroy it, if taking over is too much work
443 void trigger_hurt_touch()
445 if (self.active != ACTIVE_ACTIVE)
449 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
452 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
453 if (other.iscreature)
455 if (other.takedamage)
456 if (other.triggerhurttime < time)
459 other.triggerhurttime = time + 1;
466 self.enemy = world; // I still hate you all
469 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
472 else if(other.damagedbytriggers)
477 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
484 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
485 Any object touching this will be hurt
486 set dmg to damage amount
489 void spawnfunc_trigger_hurt()
492 self.active = ACTIVE_ACTIVE;
493 self.touch = trigger_hurt_touch;
494 self.use = trigger_hurt_use;
495 self.enemy = world; // I hate you all
498 if (self.message == "")
499 self.message = "was in the wrong place";
500 if (self.message2 == "")
501 self.message2 = "was thrown into a world of hurt by";
502 // self.message = "someone like %s always gets wrongplaced";
504 if(!trigger_hurt_first)
505 trigger_hurt_first = self;
506 if(trigger_hurt_last)
507 trigger_hurt_last.trigger_hurt_next = self;
508 trigger_hurt_last = self;
511 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
515 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
516 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
522 //////////////////////////////////////////////////////////////
526 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
528 //////////////////////////////////////////////////////////////
530 void trigger_heal_touch()
532 if (self.active != ACTIVE_ACTIVE)
535 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
536 if (other.iscreature)
538 if (other.takedamage)
540 if (other.triggerhealtime < time)
543 other.triggerhealtime = time + 1;
545 if (other.health < self.max_health)
547 other.health = min(other.health + self.health, self.max_health);
548 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
549 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
555 void spawnfunc_trigger_heal()
557 self.active = ACTIVE_ACTIVE;
560 self.touch = trigger_heal_touch;
563 if (!self.max_health)
564 self.max_health = 200; //Max health topoff for field
566 self.noise = "misc/mediumhealth.wav";
567 precache_sound(self.noise);
571 //////////////////////////////////////////////////////////////
577 //////////////////////////////////////////////////////////////
579 void trigger_gravity_remove(entity own)
581 if(own.trigger_gravity_check.owner == own)
583 UpdateCSQCProjectile(own);
584 own.gravity = own.trigger_gravity_check.gravity;
585 remove(own.trigger_gravity_check);
588 backtrace("Removing a trigger_gravity_check with no valid owner");
589 own.trigger_gravity_check = world;
591 void trigger_gravity_check_think()
593 // This spawns when a player enters the gravity zone and checks if he left.
594 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
595 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
598 if(self.owner.trigger_gravity_check == self)
599 trigger_gravity_remove(self.owner);
607 self.nextthink = time;
611 void trigger_gravity_use()
613 self.state = !self.state;
616 void trigger_gravity_touch()
620 if(self.state != true)
627 if (!(self.spawnflags & 1))
629 if(other.trigger_gravity_check)
631 if(self == other.trigger_gravity_check.enemy)
634 other.trigger_gravity_check.count = 2; // gravity one more frame...
639 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
640 trigger_gravity_remove(other);
644 other.trigger_gravity_check = spawn();
645 other.trigger_gravity_check.enemy = self;
646 other.trigger_gravity_check.owner = other;
647 other.trigger_gravity_check.gravity = other.gravity;
648 other.trigger_gravity_check.think = trigger_gravity_check_think;
649 other.trigger_gravity_check.nextthink = time;
650 other.trigger_gravity_check.count = 2;
655 if (other.gravity != g)
659 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
660 UpdateCSQCProjectile(self.owner);
664 void spawnfunc_trigger_gravity()
666 if(self.gravity == 1)
670 self.touch = trigger_gravity_touch;
672 precache_sound(self.noise);
677 self.use = trigger_gravity_use;
678 if(self.spawnflags & 2)
683 //=============================================================================
685 // TODO add a way to do looped sounds with sound(); then complete this entity
686 void target_speaker_use_activator()
688 if (!IS_REAL_CLIENT(activator))
691 if(substring(self.noise, 0, 1) == "*")
694 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
695 if(GetPlayerSoundSampleField_notFound)
696 snd = "misc/null.wav";
697 else if (activator.(sample) == "")
698 snd = "misc/null.wav";
701 tokenize_console(activator.(sample));
705 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
707 snd = strcat(argv(0), ".wav"); // randomization
712 msg_entity = activator;
713 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
715 void target_speaker_use_on()
718 if(substring(self.noise, 0, 1) == "*")
721 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
722 if(GetPlayerSoundSampleField_notFound)
723 snd = "misc/null.wav";
724 else if (activator.(sample) == "")
725 snd = "misc/null.wav";
728 tokenize_console(activator.(sample));
732 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
734 snd = strcat(argv(0), ".wav"); // randomization
739 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
740 if(self.spawnflags & 3)
741 self.use = target_speaker_use_off;
743 void target_speaker_use_off()
745 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
746 self.use = target_speaker_use_on;
748 void target_speaker_reset()
750 if(self.spawnflags & 1) // LOOPED_ON
752 if(self.use == target_speaker_use_on)
753 target_speaker_use_on();
755 else if(self.spawnflags & 2)
757 if(self.use == target_speaker_use_off)
758 target_speaker_use_off();
762 void spawnfunc_target_speaker()
764 // TODO: "*" prefix to sound file name
765 // TODO: wait and random (just, HOW? random is not a field)
767 precache_sound (self.noise);
769 if(!self.atten && !(self.spawnflags & 4))
772 self.atten = ATTEN_NORM;
774 self.atten = ATTEN_STATIC;
776 else if(self.atten < 0)
784 if(self.spawnflags & 8) // ACTIVATOR
785 self.use = target_speaker_use_activator;
786 else if(self.spawnflags & 1) // LOOPED_ON
788 target_speaker_use_on();
789 self.reset = target_speaker_reset;
791 else if(self.spawnflags & 2) // LOOPED_OFF
793 self.use = target_speaker_use_on;
794 self.reset = target_speaker_reset;
797 self.use = target_speaker_use_on;
799 else if(self.spawnflags & 1) // LOOPED_ON
801 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
804 else if(self.spawnflags & 2) // LOOPED_OFF
806 objerror("This sound entity can never be activated");
810 // Quake/Nexuiz fallback
811 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
817 void spawnfunc_func_stardust() {
818 self.effects = EF_STARDUST;
821 float pointparticles_SendEntity(entity to, float fl)
823 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
825 // optional features to save space
827 if(self.spawnflags & 2)
828 fl |= 0x10; // absolute count on toggle-on
829 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
830 fl |= 0x20; // 4 bytes - saves CPU
831 if(self.waterlevel || self.count != 1)
832 fl |= 0x40; // 4 bytes - obscure features almost never used
833 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
834 fl |= 0x80; // 14 bytes - saves lots of space
836 WriteByte(MSG_ENTITY, fl);
840 WriteCoord(MSG_ENTITY, self.impulse);
842 WriteCoord(MSG_ENTITY, 0); // off
846 WriteCoord(MSG_ENTITY, self.origin.x);
847 WriteCoord(MSG_ENTITY, self.origin.y);
848 WriteCoord(MSG_ENTITY, self.origin.z);
852 if(self.model != "null")
854 WriteShort(MSG_ENTITY, self.modelindex);
857 WriteCoord(MSG_ENTITY, self.mins.x);
858 WriteCoord(MSG_ENTITY, self.mins.y);
859 WriteCoord(MSG_ENTITY, self.mins.z);
860 WriteCoord(MSG_ENTITY, self.maxs.x);
861 WriteCoord(MSG_ENTITY, self.maxs.y);
862 WriteCoord(MSG_ENTITY, self.maxs.z);
867 WriteShort(MSG_ENTITY, 0);
870 WriteCoord(MSG_ENTITY, self.maxs.x);
871 WriteCoord(MSG_ENTITY, self.maxs.y);
872 WriteCoord(MSG_ENTITY, self.maxs.z);
875 WriteShort(MSG_ENTITY, self.cnt);
878 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
879 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
883 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
884 WriteByte(MSG_ENTITY, self.count * 16.0);
886 WriteString(MSG_ENTITY, self.noise);
889 WriteByte(MSG_ENTITY, floor(self.atten * 64));
890 WriteByte(MSG_ENTITY, floor(self.volume * 255));
892 WriteString(MSG_ENTITY, self.bgmscript);
893 if(self.bgmscript != "")
895 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
896 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
897 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
898 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
904 void pointparticles_use()
906 self.state = !self.state;
910 void pointparticles_think()
912 if(self.origin != self.oldorigin)
915 self.oldorigin = self.origin;
917 self.nextthink = time;
920 void pointparticles_reset()
922 if(self.spawnflags & 1)
928 void spawnfunc_func_pointparticles()
931 setmodel(self, self.model);
933 precache_sound (self.noise);
935 if(!self.bgmscriptsustain)
936 self.bgmscriptsustain = 1;
937 else if(self.bgmscriptsustain < 0)
938 self.bgmscriptsustain = 0;
941 self.atten = ATTEN_NORM;
942 else if(self.atten < 0)
953 setorigin(self, self.origin + self.mins);
954 setsize(self, '0 0 0', self.maxs - self.mins);
957 self.cnt = particleeffectnum(self.mdl);
959 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
963 self.use = pointparticles_use;
964 self.reset = pointparticles_reset;
969 self.think = pointparticles_think;
970 self.nextthink = time;
973 void spawnfunc_func_sparks()
975 // self.cnt is the amount of sparks that one burst will spawn
977 self.cnt = 25.0; // nice default value
980 // self.wait is the probability that a sparkthink will spawn a spark shower
981 // range: 0 - 1, but 0 makes little sense, so...
982 if(self.wait < 0.05) {
983 self.wait = 0.25; // nice default value
986 self.count = self.cnt;
989 self.velocity = '0 0 -1';
990 self.mdl = "TE_SPARK";
991 self.impulse = 10 * self.wait; // by default 2.5/sec
993 self.cnt = 0; // use mdl
995 spawnfunc_func_pointparticles();
998 float rainsnow_SendEntity(entity to, int sf)
1000 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1001 WriteByte(MSG_ENTITY, self.state);
1002 WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
1003 WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
1004 WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
1005 WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
1006 WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
1007 WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
1008 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1009 WriteShort(MSG_ENTITY, self.count);
1010 WriteByte(MSG_ENTITY, self.cnt);
1014 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1015 This is an invisible area like a trigger, which rain falls inside of.
1019 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1021 sets color of rain (default 12 - white)
1023 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1025 void spawnfunc_func_rain()
1027 self.dest = self.velocity;
1028 self.velocity = '0 0 0';
1030 self.dest = '0 0 -700';
1031 self.angles = '0 0 0';
1032 self.movetype = MOVETYPE_NONE;
1033 self.solid = SOLID_NOT;
1034 SetBrushEntityModel();
1039 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1042 if(self.count > 65535)
1045 self.state = 1; // 1 is rain, 0 is snow
1048 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1052 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1053 This is an invisible area like a trigger, which snow falls inside of.
1057 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1059 sets color of rain (default 12 - white)
1061 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1063 void spawnfunc_func_snow()
1065 self.dest = self.velocity;
1066 self.velocity = '0 0 0';
1068 self.dest = '0 0 -300';
1069 self.angles = '0 0 0';
1070 self.movetype = MOVETYPE_NONE;
1071 self.solid = SOLID_NOT;
1072 SetBrushEntityModel();
1077 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1080 if(self.count > 65535)
1083 self.state = 0; // 1 is rain, 0 is snow
1086 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1089 void misc_laser_aim()
1094 if(self.spawnflags & 2)
1096 if(self.enemy.origin != self.mangle)
1098 self.mangle = self.enemy.origin;
1099 self.SendFlags |= 2;
1104 a = vectoangles(self.enemy.origin - self.origin);
1106 if(a != self.mangle)
1109 self.SendFlags |= 2;
1115 if(self.angles != self.mangle)
1117 self.mangle = self.angles;
1118 self.SendFlags |= 2;
1121 if(self.origin != self.oldorigin)
1123 self.SendFlags |= 1;
1124 self.oldorigin = self.origin;
1128 void misc_laser_init()
1130 if(self.target != "")
1131 self.enemy = find(world, targetname, self.target);
1134 void misc_laser_think()
1141 self.nextthink = time;
1150 o = self.enemy.origin;
1151 if (!(self.spawnflags & 2))
1152 o = self.origin + normalize(o - self.origin) * 32768;
1156 makevectors(self.mangle);
1157 o = self.origin + v_forward * 32768;
1160 if(self.dmg || self.enemy.target != "")
1162 traceline(self.origin, o, MOVE_NORMAL, self);
1165 hitloc = trace_endpos;
1167 if(self.enemy.target != "") // DETECTOR laser
1169 if(trace_ent.iscreature)
1171 self.pusher = hitent;
1178 activator = self.pusher;
1191 activator = self.pusher;
1201 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1203 if(hitent.takedamage)
1204 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1208 float laser_SendEntity(entity to, float fl)
1210 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1211 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1212 if(self.spawnflags & 2)
1216 if(self.scale != 1 || self.modelscale != 1)
1218 if(self.spawnflags & 4)
1220 WriteByte(MSG_ENTITY, fl);
1223 WriteCoord(MSG_ENTITY, self.origin.x);
1224 WriteCoord(MSG_ENTITY, self.origin.y);
1225 WriteCoord(MSG_ENTITY, self.origin.z);
1229 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
1230 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
1231 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
1233 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1236 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1237 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1239 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1240 WriteShort(MSG_ENTITY, self.cnt + 1);
1246 WriteCoord(MSG_ENTITY, self.enemy.origin.x);
1247 WriteCoord(MSG_ENTITY, self.enemy.origin.y);
1248 WriteCoord(MSG_ENTITY, self.enemy.origin.z);
1252 WriteAngle(MSG_ENTITY, self.mangle.x);
1253 WriteAngle(MSG_ENTITY, self.mangle.y);
1257 WriteByte(MSG_ENTITY, self.state);
1261 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1262 Any object touching the beam will be hurt
1265 spawnfunc_target_position where the laser ends
1267 name of beam end effect to use
1269 color of the beam (default: red)
1271 damage per second (-1 for a laser that kills immediately)
1275 self.state = !self.state;
1276 self.SendFlags |= 4;
1282 if(self.spawnflags & 1)
1288 void spawnfunc_misc_laser()
1292 if(self.mdl == "none")
1296 self.cnt = particleeffectnum(self.mdl);
1299 self.cnt = particleeffectnum("laser_deadly");
1305 self.cnt = particleeffectnum("laser_deadly");
1312 if(self.colormod == '0 0 0')
1314 self.colormod = '1 0 0';
1315 if(self.message == "")
1316 self.message = "saw the light";
1317 if (self.message2 == "")
1318 self.message2 = "was pushed into a laser by";
1321 if(!self.modelscale)
1322 self.modelscale = 1;
1323 else if(self.modelscale < 0)
1324 self.modelscale = 0;
1325 self.think = misc_laser_think;
1326 self.nextthink = time;
1327 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1329 self.mangle = self.angles;
1331 Net_LinkEntity(self, false, 0, laser_SendEntity);
1335 self.reset = laser_reset;
1337 self.use = laser_use;
1343 // tZorks trigger impulse / gravity
1345 // targeted (directional) mode
1346 void trigger_impulse_touch1()
1349 float pushdeltatime;
1352 if (self.active != ACTIVE_ACTIVE)
1355 if (!isPushable(other))
1360 targ = find(world, targetname, self.target);
1363 objerror("trigger_force without a (valid) .target!\n");
1368 str = min(self.radius, vlen(self.origin - other.origin));
1370 if(self.falloff == 1)
1371 str = (str / self.radius) * self.strength;
1372 else if(self.falloff == 2)
1373 str = (1 - (str / self.radius)) * self.strength;
1375 str = self.strength;
1377 pushdeltatime = time - other.lastpushtime;
1378 if (pushdeltatime > 0.15) pushdeltatime = 0;
1379 other.lastpushtime = time;
1380 if(!pushdeltatime) return;
1382 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1383 other.flags &= ~FL_ONGROUND;
1384 UpdateCSQCProjectile(other);
1387 // Directionless (accelerator/decelerator) mode
1388 void trigger_impulse_touch2()
1390 float pushdeltatime;
1392 if (self.active != ACTIVE_ACTIVE)
1395 if (!isPushable(other))
1400 pushdeltatime = time - other.lastpushtime;
1401 if (pushdeltatime > 0.15) pushdeltatime = 0;
1402 other.lastpushtime = time;
1403 if(!pushdeltatime) return;
1405 // div0: ticrate independent, 1 = identity (not 20)
1406 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1407 UpdateCSQCProjectile(other);
1410 // Spherical (gravity/repulsor) mode
1411 void trigger_impulse_touch3()
1413 float pushdeltatime;
1416 if (self.active != ACTIVE_ACTIVE)
1419 if (!isPushable(other))
1424 pushdeltatime = time - other.lastpushtime;
1425 if (pushdeltatime > 0.15) pushdeltatime = 0;
1426 other.lastpushtime = time;
1427 if(!pushdeltatime) return;
1429 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1431 str = min(self.radius, vlen(self.origin - other.origin));
1433 if(self.falloff == 1)
1434 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1435 else if(self.falloff == 2)
1436 str = (str / self.radius) * self.strength; // 0 in the inside
1438 str = self.strength;
1440 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1441 UpdateCSQCProjectile(other);
1444 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1445 -------- KEYS --------
1446 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1447 If not, this trigger acts like a damper/accelerator field.
1449 strength : This is how mutch force to add in the direction of .target each second
1450 when .target is set. If not, this is hoe mutch to slow down/accelerate
1451 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1453 radius : If set, act as a spherical device rather then a liniar one.
1455 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1457 -------- NOTES --------
1458 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1459 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1462 void spawnfunc_trigger_impulse()
1464 self.active = ACTIVE_ACTIVE;
1469 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1470 setorigin(self, self.origin);
1471 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1472 self.touch = trigger_impulse_touch3;
1478 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1479 self.touch = trigger_impulse_touch1;
1483 if(!self.strength) self.strength = 0.9;
1484 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1485 self.touch = trigger_impulse_touch2;
1490 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1491 "Flip-flop" trigger gate... lets only every second trigger event through
1495 self.state = !self.state;
1500 void spawnfunc_trigger_flipflop()
1502 if(self.spawnflags & 1)
1504 self.use = flipflop_use;
1505 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1508 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1509 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1513 self.nextthink = time + self.wait;
1514 self.enemy = activator;
1520 void monoflop_fixed_use()
1524 self.nextthink = time + self.wait;
1526 self.enemy = activator;
1530 void monoflop_think()
1533 activator = self.enemy;
1537 void monoflop_reset()
1543 void spawnfunc_trigger_monoflop()
1547 if(self.spawnflags & 1)
1548 self.use = monoflop_fixed_use;
1550 self.use = monoflop_use;
1551 self.think = monoflop_think;
1553 self.reset = monoflop_reset;
1556 void multivibrator_send()
1561 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1563 newstate = (time < cyclestart + self.wait);
1566 if(self.state != newstate)
1568 self.state = newstate;
1571 self.nextthink = cyclestart + self.wait + 0.01;
1573 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1576 void multivibrator_toggle()
1578 if(self.nextthink == 0)
1580 multivibrator_send();
1593 void multivibrator_reset()
1595 if(!(self.spawnflags & 1))
1596 self.nextthink = 0; // wait for a trigger event
1598 self.nextthink = max(1, time);
1601 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1602 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1603 -------- KEYS --------
1604 target: trigger all entities with this targetname when it goes off
1605 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1606 phase: offset of the timing
1607 wait: "on" cycle time (default: 1)
1608 respawntime: "off" cycle time (default: same as wait)
1609 -------- SPAWNFLAGS --------
1610 START_ON: assume it is already turned on (when targeted)
1612 void spawnfunc_trigger_multivibrator()
1616 if(!self.respawntime)
1617 self.respawntime = self.wait;
1620 self.use = multivibrator_toggle;
1621 self.think = multivibrator_send;
1622 self.nextthink = max(1, time);
1625 multivibrator_reset();
1634 if(self.killtarget != "")
1635 src = find(world, targetname, self.killtarget);
1636 if(self.target != "")
1637 dst = find(world, targetname, self.target);
1641 objerror("follow: could not find target/killtarget");
1647 // already done :P entity must stay
1651 else if(!src || !dst)
1653 objerror("follow: could not find target/killtarget");
1656 else if(self.spawnflags & 1)
1659 if(self.spawnflags & 2)
1661 setattachment(dst, src, self.message);
1665 attach_sameorigin(dst, src, self.message);
1668 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1673 if(self.spawnflags & 2)
1675 dst.movetype = MOVETYPE_FOLLOW;
1677 // dst.punchangle = '0 0 0'; // keep unchanged
1678 dst.view_ofs = dst.origin;
1679 dst.v_angle = dst.angles;
1683 follow_sameorigin(dst, src);
1690 void spawnfunc_misc_follow()
1692 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1697 void gamestart_use() {
1703 void spawnfunc_trigger_gamestart() {
1704 self.use = gamestart_use;
1705 self.reset2 = spawnfunc_trigger_gamestart;
1709 self.think = self.use;
1710 self.nextthink = game_starttime + self.wait;
1713 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1719 void target_voicescript_clear(entity pl)
1721 pl.voicescript = world;
1724 void target_voicescript_use()
1726 if(activator.voicescript != self)
1728 activator.voicescript = self;
1729 activator.voicescript_index = 0;
1730 activator.voicescript_nextthink = time + self.delay;
1734 void target_voicescript_next(entity pl)
1739 vs = pl.voicescript;
1742 if(vs.message == "")
1749 if(time >= pl.voicescript_voiceend)
1751 if(time >= pl.voicescript_nextthink)
1753 // get the next voice...
1754 n = tokenize_console(vs.message);
1756 if(pl.voicescript_index < vs.cnt)
1757 i = pl.voicescript_index * 2;
1758 else if(n > vs.cnt * 2)
1759 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1765 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1766 dt = stof(argv(i + 1));
1769 pl.voicescript_voiceend = time + dt;
1770 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1774 pl.voicescript_voiceend = time - dt;
1775 pl.voicescript_nextthink = pl.voicescript_voiceend;
1778 pl.voicescript_index += 1;
1782 pl.voicescript = world; // stop trying then
1788 void spawnfunc_target_voicescript()
1790 // netname: directory of the sound files
1791 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1792 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1793 // Here, a - in front of the duration means that no delay is to be
1794 // added after this message
1795 // wait: average time between messages
1796 // delay: initial delay before the first message
1799 self.use = target_voicescript_use;
1801 n = tokenize_console(self.message);
1803 for(i = 0; i+1 < n; i += 2)
1810 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1816 void trigger_relay_teamcheck_use()
1820 if(self.spawnflags & 2)
1822 if(activator.team != self.team)
1827 if(activator.team == self.team)
1833 if(self.spawnflags & 1)
1838 void trigger_relay_teamcheck_reset()
1840 self.team = self.team_saved;
1843 void spawnfunc_trigger_relay_teamcheck()
1845 self.team_saved = self.team;
1846 self.use = trigger_relay_teamcheck_use;
1847 self.reset = trigger_relay_teamcheck_reset;
1852 void trigger_disablerelay_use()
1859 for(e = world; (e = find(e, targetname, self.target)); )
1861 if(e.use == SUB_UseTargets)
1863 e.use = SUB_DontUseTargets;
1866 else if(e.use == SUB_DontUseTargets)
1868 e.use = SUB_UseTargets;
1874 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1877 void spawnfunc_trigger_disablerelay()
1879 self.use = trigger_disablerelay_use;
1882 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1884 float domatch, dotrigger, matchstart, l;
1889 magicear_matched = false;
1891 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1892 domatch = ((ear.spawnflags & 32) || dotrigger);
1899 // we are in TUBA mode!
1900 if (!(ear.spawnflags & 256))
1903 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
1906 magicear_matched = true;
1913 savemessage = self.message;
1914 self.message = string_null;
1916 self.message = savemessage;
1920 if(ear.netname != "")
1926 if(ear.spawnflags & 256) // ENOTUBA
1931 if(ear.spawnflags & 4)
1937 if(ear.spawnflags & 1)
1940 if(ear.spawnflags & 2)
1943 if(ear.spawnflags & 8)
1948 l = strlen(ear.message);
1950 if(ear.spawnflags & 128)
1953 msg = strdecolorize(msgin);
1955 if(substring(ear.message, 0, 1) == "*")
1957 if(substring(ear.message, -1, 1) == "*")
1960 // as we need multi-replacement here...
1961 s = substring(ear.message, 1, -2);
1963 if(strstrofs(msg, s, 0) >= 0)
1964 matchstart = -2; // we use strreplace on s
1969 s = substring(ear.message, 1, -1);
1971 if(substring(msg, -l, l) == s)
1972 matchstart = strlen(msg) - l;
1977 if(substring(ear.message, -1, 1) == "*")
1980 s = substring(ear.message, 0, -2);
1982 if(substring(msg, 0, l) == s)
1989 if(msg == ear.message)
1994 if(matchstart == -1) // no match
1997 magicear_matched = true;
2004 savemessage = self.message;
2005 self.message = string_null;
2007 self.message = savemessage;
2011 if(ear.spawnflags & 16)
2015 else if(ear.netname != "")
2018 return strreplace(s, ear.netname, msg);
2021 substring(msg, 0, matchstart),
2023 substring(msg, matchstart + l, -1)
2030 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2034 for(ear = magicears; ear; ear = ear.enemy)
2036 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2037 if(!(ear.spawnflags & 64))
2038 if(magicear_matched)
2045 void spawnfunc_trigger_magicear()
2047 self.enemy = magicears;
2050 // actually handled in "say" processing
2053 // 2 = ignore teamsay
2055 // 8 = ignore tell to unknown player
2056 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2057 // 32 = perform the replacement even if outside the radius or dead
2058 // 64 = continue replacing/triggering even if this one matched
2059 // 128 = don't decolorize message before matching
2060 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2061 // 512 = tuba notes must be exact right pitch, no transposing
2071 // if set, replacement for the matched text
2073 // "hearing distance"
2077 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2079 self.movedir_x -= 1; // map to tuba instrument numbers
2082 void relay_activators_use()
2088 for(trg = world; (trg = find(trg, targetname, os.target)); )
2092 trg.setactive(os.cnt);
2095 //bprint("Not using setactive\n");
2096 if(os.cnt == ACTIVE_TOGGLE)
2097 if(trg.active == ACTIVE_ACTIVE)
2098 trg.active = ACTIVE_NOT;
2100 trg.active = ACTIVE_ACTIVE;
2102 trg.active = os.cnt;
2108 void spawnfunc_relay_activate()
2110 self.cnt = ACTIVE_ACTIVE;
2111 self.use = relay_activators_use;
2114 void spawnfunc_relay_deactivate()
2116 self.cnt = ACTIVE_NOT;
2117 self.use = relay_activators_use;
2120 void spawnfunc_relay_activatetoggle()
2122 self.cnt = ACTIVE_TOGGLE;
2123 self.use = relay_activators_use;
2126 void spawnfunc_target_changelevel_use()
2128 if(self.gametype != "")
2129 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2131 if (self.chmap == "")
2132 localcmd("endmatch\n");
2134 localcmd(strcat("changelevel ", self.chmap, "\n"));
2137 void spawnfunc_target_changelevel()
2139 self.use = spawnfunc_target_changelevel_use;