1 #include "g_triggers.qh"
2 #include "t_jumppads.qh"
4 void SUB_DontUseTargets()
11 activator = self.enemy;
17 ==============================
20 the global "activator" should be set to the entity that initiated the firing.
22 If self.delay is set, a DelayedUse entity will be created that will actually
23 do the SUB_UseTargets after that many seconds have passed.
25 Centerprints any self.message to the activator.
27 Removes all entities with a targetname that match self.killtarget,
28 and removes them, so some events can remove other triggers.
30 Search for (string)targetname in all entities that
31 match (string)self.target and call their .use function
33 ==============================
37 entity t, stemp, otemp, act;
46 // create a temp object to fire at a later time
48 t.classname = "DelayedUse";
49 t.nextthink = time + self.delay;
52 t.message = self.message;
53 t.killtarget = self.killtarget;
54 t.target = self.target;
55 t.target2 = self.target2;
56 t.target3 = self.target3;
57 t.target4 = self.target4;
66 if(IS_PLAYER(activator) && self.message != "")
67 if(IS_REAL_CLIENT(activator))
69 centerprint(activator, self.message);
71 play2(activator, "misc/talk.wav");
75 // kill the killtagets
80 for(t = world; (t = find(t, targetname, s)); )
91 if(stemp.target_random)
92 RandomSelection_Init();
94 for(i = 0; i < 4; ++i)
99 case 0: s = stemp.target; break;
100 case 1: s = stemp.target2; break;
101 case 2: s = stemp.target3; break;
102 case 3: s = stemp.target4; break;
106 // Flag to set func_clientwall state
107 // 1 == deactivate, 2 == activate, 0 == do nothing
108 float aw_flag = self.antiwall_flag;
109 for(t = world; (t = find(t, targetname, s)); )
112 if(stemp.target_random)
114 RandomSelection_Add(t, 0, string_null, 1, 0);
118 if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary")
119 t.antiwall_flag = aw_flag;
129 if(stemp.target_random && RandomSelection_chosen_ent)
131 self = RandomSelection_chosen_ent;
143 //=============================================================================
145 // the wait time has passed, so set back up for another activation
150 self.health = self.max_health;
151 self.takedamage = DAMAGE_YES;
152 self.solid = SOLID_BBOX;
157 // the trigger was just touched/killed/used
158 // self.enemy should be set to the activator so it can be held through a delay
159 // so wait for the delay time before firing
162 if (self.nextthink > time)
164 return; // allready been triggered
167 if (self.classname == "trigger_secret")
169 if (!IS_PLAYER(self.enemy))
171 found_secrets = found_secrets + 1;
172 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
176 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
178 // don't trigger again until reset
179 self.takedamage = DAMAGE_NO;
181 activator = self.enemy;
182 other = self.goalentity;
187 self.think = multi_wait;
188 self.nextthink = time + self.wait;
190 else if (self.wait == 0)
192 multi_wait(); // waiting finished
195 { // we can't just remove (self) here, because this is a touch function
196 // called wheil C code is looping through area links...
197 self.touch = func_null;
203 self.goalentity = other;
204 self.enemy = activator;
210 if(!(self.spawnflags & 2))
211 if(!other.iscreature)
215 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
218 // if the trigger has an angles field, check player's facing direction
219 if (self.movedir != '0 0 0')
221 makevectors (other.angles);
222 if (v_forward * self.movedir < 0)
223 return; // not facing the right way
229 self.goalentity = other;
233 void multi_eventdamage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
235 if (!self.takedamage)
237 if(self.spawnflags & DOOR_NOSPLASH)
238 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
240 self.health = self.health - damage;
241 if (self.health <= 0)
243 self.enemy = attacker;
244 self.goalentity = inflictor;
251 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
252 self.touch = multi_touch;
255 self.health = self.max_health;
256 self.takedamage = DAMAGE_YES;
257 self.solid = SOLID_BBOX;
259 self.think = func_null;
261 self.team = self.team_saved;
264 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
265 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.
266 If "delay" is set, the trigger waits some time after activating before firing.
267 "wait" : Seconds between triggerings. (.2 default)
268 If notouch is set, the trigger is only fired by other entities, not by touching.
269 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
275 set "message" to text string
277 void spawnfunc_trigger_multiple()
279 self.reset = multi_reset;
280 if (self.sounds == 1)
282 precache_sound ("misc/secret.wav");
283 self.noise = "misc/secret.wav";
285 else if (self.sounds == 2)
287 precache_sound ("misc/talk.wav");
288 self.noise = "misc/talk.wav";
290 else if (self.sounds == 3)
292 precache_sound ("misc/trigger1.wav");
293 self.noise = "misc/trigger1.wav";
298 else if(self.wait < -1)
300 self.use = multi_use;
304 self.team_saved = self.team;
308 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
309 objerror ("health and notouch don't make sense\n");
310 self.max_health = self.health;
311 self.event_damage = multi_eventdamage;
312 self.takedamage = DAMAGE_YES;
313 self.solid = SOLID_BBOX;
314 setorigin (self, self.origin); // make sure it links into the world
318 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
320 self.touch = multi_touch;
321 setorigin (self, self.origin); // make sure it links into the world
327 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
328 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
329 "targetname". If "health" is set, the trigger must be killed to activate.
330 If notouch is set, the trigger is only fired by other entities, not by touching.
331 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
332 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.
338 set "message" to text string
340 void spawnfunc_trigger_once()
343 spawnfunc_trigger_multiple();
346 //=============================================================================
348 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
349 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
351 void spawnfunc_trigger_relay()
353 self.use = SUB_UseTargets;
354 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
359 self.think = SUB_UseTargets;
360 self.nextthink = self.wait;
365 self.think = func_null;
369 void spawnfunc_trigger_delay()
374 self.use = delay_use;
375 self.reset = delay_reset;
378 //=============================================================================
389 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
390 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
392 self.enemy = activator;
397 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
399 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
401 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
407 self.count = self.cnt;
411 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
412 Acts as an intermediary for an action that takes multiple inputs.
414 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
416 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
418 void spawnfunc_trigger_counter()
423 self.cnt = self.count;
425 self.use = counter_use;
426 self.reset = counter_reset;
429 void trigger_hurt_use()
431 if(IS_PLAYER(activator))
432 self.enemy = activator;
434 self.enemy = world; // let's just destroy it, if taking over is too much work
437 void trigger_hurt_touch()
439 if (self.active != ACTIVE_ACTIVE)
443 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
446 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
447 if (other.iscreature)
449 if (other.takedamage)
450 if (other.triggerhurttime < time)
453 other.triggerhurttime = time + 1;
460 self.enemy = world; // I still hate you all
463 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
466 else if(other.damagedbytriggers)
471 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
478 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
479 Any object touching this will be hurt
480 set dmg to damage amount
483 void spawnfunc_trigger_hurt()
486 self.active = ACTIVE_ACTIVE;
487 self.touch = trigger_hurt_touch;
488 self.use = trigger_hurt_use;
489 self.enemy = world; // I hate you all
492 if (self.message == "")
493 self.message = "was in the wrong place";
494 if (self.message2 == "")
495 self.message2 = "was thrown into a world of hurt by";
496 // self.message = "someone like %s always gets wrongplaced";
498 if(!trigger_hurt_first)
499 trigger_hurt_first = self;
500 if(trigger_hurt_last)
501 trigger_hurt_last.trigger_hurt_next = self;
502 trigger_hurt_last = self;
505 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
509 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
510 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
516 //////////////////////////////////////////////////////////////
520 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
522 //////////////////////////////////////////////////////////////
524 void trigger_heal_touch()
526 if (self.active != ACTIVE_ACTIVE)
529 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
530 if (other.iscreature)
532 if (other.takedamage)
534 if (other.triggerhealtime < time)
537 other.triggerhealtime = time + 1;
539 if (other.health < self.max_health)
541 other.health = min(other.health + self.health, self.max_health);
542 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
543 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
549 void spawnfunc_trigger_heal()
551 self.active = ACTIVE_ACTIVE;
554 self.touch = trigger_heal_touch;
557 if (!self.max_health)
558 self.max_health = 200; //Max health topoff for field
560 self.noise = "misc/mediumhealth.wav";
561 precache_sound(self.noise);
565 //////////////////////////////////////////////////////////////
571 //////////////////////////////////////////////////////////////
573 void trigger_gravity_remove(entity own)
575 if(own.trigger_gravity_check.owner == own)
577 UpdateCSQCProjectile(own);
578 own.gravity = own.trigger_gravity_check.gravity;
579 remove(own.trigger_gravity_check);
582 backtrace("Removing a trigger_gravity_check with no valid owner");
583 own.trigger_gravity_check = world;
585 void trigger_gravity_check_think()
587 // This spawns when a player enters the gravity zone and checks if he left.
588 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
589 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
592 if(self.owner.trigger_gravity_check == self)
593 trigger_gravity_remove(self.owner);
601 self.nextthink = time;
605 void trigger_gravity_use()
607 self.state = !self.state;
610 void trigger_gravity_touch()
614 if(self.state != true)
621 if (!(self.spawnflags & 1))
623 if(other.trigger_gravity_check)
625 if(self == other.trigger_gravity_check.enemy)
628 other.trigger_gravity_check.count = 2; // gravity one more frame...
633 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
634 trigger_gravity_remove(other);
638 other.trigger_gravity_check = spawn();
639 other.trigger_gravity_check.enemy = self;
640 other.trigger_gravity_check.owner = other;
641 other.trigger_gravity_check.gravity = other.gravity;
642 other.trigger_gravity_check.think = trigger_gravity_check_think;
643 other.trigger_gravity_check.nextthink = time;
644 other.trigger_gravity_check.count = 2;
649 if (other.gravity != g)
653 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
654 UpdateCSQCProjectile(self.owner);
658 void spawnfunc_trigger_gravity()
660 if(self.gravity == 1)
664 self.touch = trigger_gravity_touch;
666 precache_sound(self.noise);
671 self.use = trigger_gravity_use;
672 if(self.spawnflags & 2)
677 //=============================================================================
679 // TODO add a way to do looped sounds with sound(); then complete this entity
680 void target_speaker_use_activator()
682 if (!IS_REAL_CLIENT(activator))
685 if(substring(self.noise, 0, 1) == "*")
688 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
689 if(GetPlayerSoundSampleField_notFound)
690 snd = "misc/null.wav";
691 else if(activator.sample == "")
692 snd = "misc/null.wav";
695 tokenize_console(activator.sample);
699 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
701 snd = strcat(argv(0), ".wav"); // randomization
706 msg_entity = activator;
707 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
709 void target_speaker_use_on()
712 if(substring(self.noise, 0, 1) == "*")
715 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
716 if(GetPlayerSoundSampleField_notFound)
717 snd = "misc/null.wav";
718 else if(activator.sample == "")
719 snd = "misc/null.wav";
722 tokenize_console(activator.sample);
726 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
728 snd = strcat(argv(0), ".wav"); // randomization
733 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
734 if(self.spawnflags & 3)
735 self.use = target_speaker_use_off;
737 void target_speaker_use_off()
739 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
740 self.use = target_speaker_use_on;
742 void target_speaker_reset()
744 if(self.spawnflags & 1) // LOOPED_ON
746 if(self.use == target_speaker_use_on)
747 target_speaker_use_on();
749 else if(self.spawnflags & 2)
751 if(self.use == target_speaker_use_off)
752 target_speaker_use_off();
756 void spawnfunc_target_speaker()
758 // TODO: "*" prefix to sound file name
759 // TODO: wait and random (just, HOW? random is not a field)
761 precache_sound (self.noise);
763 if(!self.atten && !(self.spawnflags & 4))
766 self.atten = ATTEN_NORM;
768 self.atten = ATTEN_STATIC;
770 else if(self.atten < 0)
778 if(self.spawnflags & 8) // ACTIVATOR
779 self.use = target_speaker_use_activator;
780 else if(self.spawnflags & 1) // LOOPED_ON
782 target_speaker_use_on();
783 self.reset = target_speaker_reset;
785 else if(self.spawnflags & 2) // LOOPED_OFF
787 self.use = target_speaker_use_on;
788 self.reset = target_speaker_reset;
791 self.use = target_speaker_use_on;
793 else if(self.spawnflags & 1) // LOOPED_ON
795 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
798 else if(self.spawnflags & 2) // LOOPED_OFF
800 objerror("This sound entity can never be activated");
804 // Quake/Nexuiz fallback
805 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
811 void spawnfunc_func_stardust() {
812 self.effects = EF_STARDUST;
815 float pointparticles_SendEntity(entity to, float fl)
817 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
819 // optional features to save space
821 if(self.spawnflags & 2)
822 fl |= 0x10; // absolute count on toggle-on
823 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
824 fl |= 0x20; // 4 bytes - saves CPU
825 if(self.waterlevel || self.count != 1)
826 fl |= 0x40; // 4 bytes - obscure features almost never used
827 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
828 fl |= 0x80; // 14 bytes - saves lots of space
830 WriteByte(MSG_ENTITY, fl);
834 WriteCoord(MSG_ENTITY, self.impulse);
836 WriteCoord(MSG_ENTITY, 0); // off
840 WriteCoord(MSG_ENTITY, self.origin.x);
841 WriteCoord(MSG_ENTITY, self.origin.y);
842 WriteCoord(MSG_ENTITY, self.origin.z);
846 if(self.model != "null")
848 WriteShort(MSG_ENTITY, self.modelindex);
851 WriteCoord(MSG_ENTITY, self.mins.x);
852 WriteCoord(MSG_ENTITY, self.mins.y);
853 WriteCoord(MSG_ENTITY, self.mins.z);
854 WriteCoord(MSG_ENTITY, self.maxs.x);
855 WriteCoord(MSG_ENTITY, self.maxs.y);
856 WriteCoord(MSG_ENTITY, self.maxs.z);
861 WriteShort(MSG_ENTITY, 0);
864 WriteCoord(MSG_ENTITY, self.maxs.x);
865 WriteCoord(MSG_ENTITY, self.maxs.y);
866 WriteCoord(MSG_ENTITY, self.maxs.z);
869 WriteShort(MSG_ENTITY, self.cnt);
872 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
873 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
877 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
878 WriteByte(MSG_ENTITY, self.count * 16.0);
880 WriteString(MSG_ENTITY, self.noise);
883 WriteByte(MSG_ENTITY, floor(self.atten * 64));
884 WriteByte(MSG_ENTITY, floor(self.volume * 255));
886 WriteString(MSG_ENTITY, self.bgmscript);
887 if(self.bgmscript != "")
889 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
890 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
891 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
892 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
898 void pointparticles_use()
900 self.state = !self.state;
904 void pointparticles_think()
906 if(self.origin != self.oldorigin)
909 self.oldorigin = self.origin;
911 self.nextthink = time;
914 void pointparticles_reset()
916 if(self.spawnflags & 1)
922 void spawnfunc_func_pointparticles()
925 setmodel(self, self.model);
927 precache_sound (self.noise);
929 if(!self.bgmscriptsustain)
930 self.bgmscriptsustain = 1;
931 else if(self.bgmscriptsustain < 0)
932 self.bgmscriptsustain = 0;
935 self.atten = ATTEN_NORM;
936 else if(self.atten < 0)
947 setorigin(self, self.origin + self.mins);
948 setsize(self, '0 0 0', self.maxs - self.mins);
951 self.cnt = particleeffectnum(self.mdl);
953 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
957 self.use = pointparticles_use;
958 self.reset = pointparticles_reset;
963 self.think = pointparticles_think;
964 self.nextthink = time;
967 void spawnfunc_func_sparks()
969 // self.cnt is the amount of sparks that one burst will spawn
971 self.cnt = 25.0; // nice default value
974 // self.wait is the probability that a sparkthink will spawn a spark shower
975 // range: 0 - 1, but 0 makes little sense, so...
976 if(self.wait < 0.05) {
977 self.wait = 0.25; // nice default value
980 self.count = self.cnt;
983 self.velocity = '0 0 -1';
984 self.mdl = "TE_SPARK";
985 self.impulse = 10 * self.wait; // by default 2.5/sec
987 self.cnt = 0; // use mdl
989 spawnfunc_func_pointparticles();
992 float rainsnow_SendEntity(entity to, float sf)
994 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
995 WriteByte(MSG_ENTITY, self.state);
996 WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
997 WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
998 WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
999 WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
1000 WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
1001 WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
1002 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1003 WriteShort(MSG_ENTITY, self.count);
1004 WriteByte(MSG_ENTITY, self.cnt);
1008 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1009 This is an invisible area like a trigger, which rain falls inside of.
1013 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1015 sets color of rain (default 12 - white)
1017 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1019 void spawnfunc_func_rain()
1021 self.dest = self.velocity;
1022 self.velocity = '0 0 0';
1024 self.dest = '0 0 -700';
1025 self.angles = '0 0 0';
1026 self.movetype = MOVETYPE_NONE;
1027 self.solid = SOLID_NOT;
1028 SetBrushEntityModel();
1033 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1036 if(self.count > 65535)
1039 self.state = 1; // 1 is rain, 0 is snow
1042 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1046 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1047 This is an invisible area like a trigger, which snow falls inside of.
1051 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1053 sets color of rain (default 12 - white)
1055 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1057 void spawnfunc_func_snow()
1059 self.dest = self.velocity;
1060 self.velocity = '0 0 0';
1062 self.dest = '0 0 -300';
1063 self.angles = '0 0 0';
1064 self.movetype = MOVETYPE_NONE;
1065 self.solid = SOLID_NOT;
1066 SetBrushEntityModel();
1071 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1074 if(self.count > 65535)
1077 self.state = 0; // 1 is rain, 0 is snow
1080 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1083 void misc_laser_aim()
1088 if(self.spawnflags & 2)
1090 if(self.enemy.origin != self.mangle)
1092 self.mangle = self.enemy.origin;
1093 self.SendFlags |= 2;
1098 a = vectoangles(self.enemy.origin - self.origin);
1100 if(a != self.mangle)
1103 self.SendFlags |= 2;
1109 if(self.angles != self.mangle)
1111 self.mangle = self.angles;
1112 self.SendFlags |= 2;
1115 if(self.origin != self.oldorigin)
1117 self.SendFlags |= 1;
1118 self.oldorigin = self.origin;
1122 void misc_laser_init()
1124 if(self.target != "")
1125 self.enemy = find(world, targetname, self.target);
1128 void misc_laser_think()
1135 self.nextthink = time;
1144 o = self.enemy.origin;
1145 if (!(self.spawnflags & 2))
1146 o = self.origin + normalize(o - self.origin) * 32768;
1150 makevectors(self.mangle);
1151 o = self.origin + v_forward * 32768;
1154 if(self.dmg || self.enemy.target != "")
1156 traceline(self.origin, o, MOVE_NORMAL, self);
1159 hitloc = trace_endpos;
1161 if(self.enemy.target != "") // DETECTOR laser
1163 if(trace_ent.iscreature)
1165 self.pusher = hitent;
1172 activator = self.pusher;
1185 activator = self.pusher;
1195 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1197 if(hitent.takedamage)
1198 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1202 float laser_SendEntity(entity to, float fl)
1204 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1205 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1206 if(self.spawnflags & 2)
1210 if(self.scale != 1 || self.modelscale != 1)
1212 if(self.spawnflags & 4)
1214 WriteByte(MSG_ENTITY, fl);
1217 WriteCoord(MSG_ENTITY, self.origin.x);
1218 WriteCoord(MSG_ENTITY, self.origin.y);
1219 WriteCoord(MSG_ENTITY, self.origin.z);
1223 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
1224 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
1225 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
1227 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1230 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1231 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1233 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1234 WriteShort(MSG_ENTITY, self.cnt + 1);
1240 WriteCoord(MSG_ENTITY, self.enemy.origin.x);
1241 WriteCoord(MSG_ENTITY, self.enemy.origin.y);
1242 WriteCoord(MSG_ENTITY, self.enemy.origin.z);
1246 WriteAngle(MSG_ENTITY, self.mangle.x);
1247 WriteAngle(MSG_ENTITY, self.mangle.y);
1251 WriteByte(MSG_ENTITY, self.state);
1255 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1256 Any object touching the beam will be hurt
1259 spawnfunc_target_position where the laser ends
1261 name of beam end effect to use
1263 color of the beam (default: red)
1265 damage per second (-1 for a laser that kills immediately)
1269 self.state = !self.state;
1270 self.SendFlags |= 4;
1276 if(self.spawnflags & 1)
1282 void spawnfunc_misc_laser()
1286 if(self.mdl == "none")
1290 self.cnt = particleeffectnum(self.mdl);
1293 self.cnt = particleeffectnum("laser_deadly");
1299 self.cnt = particleeffectnum("laser_deadly");
1306 if(self.colormod == '0 0 0')
1308 self.colormod = '1 0 0';
1309 if(self.message == "")
1310 self.message = "saw the light";
1311 if (self.message2 == "")
1312 self.message2 = "was pushed into a laser by";
1315 if(!self.modelscale)
1316 self.modelscale = 1;
1317 else if(self.modelscale < 0)
1318 self.modelscale = 0;
1319 self.think = misc_laser_think;
1320 self.nextthink = time;
1321 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1323 self.mangle = self.angles;
1325 Net_LinkEntity(self, false, 0, laser_SendEntity);
1329 self.reset = laser_reset;
1331 self.use = laser_use;
1337 // tZorks trigger impulse / gravity
1339 // targeted (directional) mode
1340 void trigger_impulse_touch1()
1343 float pushdeltatime;
1346 if (self.active != ACTIVE_ACTIVE)
1349 if (!isPushable(other))
1354 targ = find(world, targetname, self.target);
1357 objerror("trigger_force without a (valid) .target!\n");
1362 str = min(self.radius, vlen(self.origin - other.origin));
1364 if(self.falloff == 1)
1365 str = (str / self.radius) * self.strength;
1366 else if(self.falloff == 2)
1367 str = (1 - (str / self.radius)) * self.strength;
1369 str = self.strength;
1371 pushdeltatime = time - other.lastpushtime;
1372 if (pushdeltatime > 0.15) pushdeltatime = 0;
1373 other.lastpushtime = time;
1374 if(!pushdeltatime) return;
1376 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1377 other.flags &= ~FL_ONGROUND;
1378 UpdateCSQCProjectile(other);
1381 // Directionless (accelerator/decelerator) mode
1382 void trigger_impulse_touch2()
1384 float pushdeltatime;
1386 if (self.active != ACTIVE_ACTIVE)
1389 if (!isPushable(other))
1394 pushdeltatime = time - other.lastpushtime;
1395 if (pushdeltatime > 0.15) pushdeltatime = 0;
1396 other.lastpushtime = time;
1397 if(!pushdeltatime) return;
1399 // div0: ticrate independent, 1 = identity (not 20)
1400 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1401 UpdateCSQCProjectile(other);
1404 // Spherical (gravity/repulsor) mode
1405 void trigger_impulse_touch3()
1407 float pushdeltatime;
1410 if (self.active != ACTIVE_ACTIVE)
1413 if (!isPushable(other))
1418 pushdeltatime = time - other.lastpushtime;
1419 if (pushdeltatime > 0.15) pushdeltatime = 0;
1420 other.lastpushtime = time;
1421 if(!pushdeltatime) return;
1423 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1425 str = min(self.radius, vlen(self.origin - other.origin));
1427 if(self.falloff == 1)
1428 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1429 else if(self.falloff == 2)
1430 str = (str / self.radius) * self.strength; // 0 in the inside
1432 str = self.strength;
1434 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1435 UpdateCSQCProjectile(other);
1438 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1439 -------- KEYS --------
1440 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1441 If not, this trigger acts like a damper/accelerator field.
1443 strength : This is how mutch force to add in the direction of .target each second
1444 when .target is set. If not, this is hoe mutch to slow down/accelerate
1445 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1447 radius : If set, act as a spherical device rather then a liniar one.
1449 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1451 -------- NOTES --------
1452 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1453 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1456 void spawnfunc_trigger_impulse()
1458 self.active = ACTIVE_ACTIVE;
1463 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1464 setorigin(self, self.origin);
1465 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1466 self.touch = trigger_impulse_touch3;
1472 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1473 self.touch = trigger_impulse_touch1;
1477 if(!self.strength) self.strength = 0.9;
1478 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1479 self.touch = trigger_impulse_touch2;
1484 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1485 "Flip-flop" trigger gate... lets only every second trigger event through
1489 self.state = !self.state;
1494 void spawnfunc_trigger_flipflop()
1496 if(self.spawnflags & 1)
1498 self.use = flipflop_use;
1499 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1502 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1503 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1507 self.nextthink = time + self.wait;
1508 self.enemy = activator;
1514 void monoflop_fixed_use()
1518 self.nextthink = time + self.wait;
1520 self.enemy = activator;
1524 void monoflop_think()
1527 activator = self.enemy;
1531 void monoflop_reset()
1537 void spawnfunc_trigger_monoflop()
1541 if(self.spawnflags & 1)
1542 self.use = monoflop_fixed_use;
1544 self.use = monoflop_use;
1545 self.think = monoflop_think;
1547 self.reset = monoflop_reset;
1550 void multivibrator_send()
1555 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1557 newstate = (time < cyclestart + self.wait);
1560 if(self.state != newstate)
1562 self.state = newstate;
1565 self.nextthink = cyclestart + self.wait + 0.01;
1567 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1570 void multivibrator_toggle()
1572 if(self.nextthink == 0)
1574 multivibrator_send();
1587 void multivibrator_reset()
1589 if(!(self.spawnflags & 1))
1590 self.nextthink = 0; // wait for a trigger event
1592 self.nextthink = max(1, time);
1595 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1596 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1597 -------- KEYS --------
1598 target: trigger all entities with this targetname when it goes off
1599 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1600 phase: offset of the timing
1601 wait: "on" cycle time (default: 1)
1602 respawntime: "off" cycle time (default: same as wait)
1603 -------- SPAWNFLAGS --------
1604 START_ON: assume it is already turned on (when targeted)
1606 void spawnfunc_trigger_multivibrator()
1610 if(!self.respawntime)
1611 self.respawntime = self.wait;
1614 self.use = multivibrator_toggle;
1615 self.think = multivibrator_send;
1616 self.nextthink = max(1, time);
1619 multivibrator_reset();
1628 if(self.killtarget != "")
1629 src = find(world, targetname, self.killtarget);
1630 if(self.target != "")
1631 dst = find(world, targetname, self.target);
1635 objerror("follow: could not find target/killtarget");
1641 // already done :P entity must stay
1645 else if(!src || !dst)
1647 objerror("follow: could not find target/killtarget");
1650 else if(self.spawnflags & 1)
1653 if(self.spawnflags & 2)
1655 setattachment(dst, src, self.message);
1659 attach_sameorigin(dst, src, self.message);
1662 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1667 if(self.spawnflags & 2)
1669 dst.movetype = MOVETYPE_FOLLOW;
1671 // dst.punchangle = '0 0 0'; // keep unchanged
1672 dst.view_ofs = dst.origin;
1673 dst.v_angle = dst.angles;
1677 follow_sameorigin(dst, src);
1684 void spawnfunc_misc_follow()
1686 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1691 void gamestart_use() {
1697 void spawnfunc_trigger_gamestart() {
1698 self.use = gamestart_use;
1699 self.reset2 = spawnfunc_trigger_gamestart;
1703 self.think = self.use;
1704 self.nextthink = game_starttime + self.wait;
1707 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1713 void target_voicescript_clear(entity pl)
1715 pl.voicescript = world;
1718 void target_voicescript_use()
1720 if(activator.voicescript != self)
1722 activator.voicescript = self;
1723 activator.voicescript_index = 0;
1724 activator.voicescript_nextthink = time + self.delay;
1728 void target_voicescript_next(entity pl)
1733 vs = pl.voicescript;
1736 if(vs.message == "")
1743 if(time >= pl.voicescript_voiceend)
1745 if(time >= pl.voicescript_nextthink)
1747 // get the next voice...
1748 n = tokenize_console(vs.message);
1750 if(pl.voicescript_index < vs.cnt)
1751 i = pl.voicescript_index * 2;
1752 else if(n > vs.cnt * 2)
1753 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1759 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1760 dt = stof(argv(i + 1));
1763 pl.voicescript_voiceend = time + dt;
1764 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1768 pl.voicescript_voiceend = time - dt;
1769 pl.voicescript_nextthink = pl.voicescript_voiceend;
1772 pl.voicescript_index += 1;
1776 pl.voicescript = world; // stop trying then
1782 void spawnfunc_target_voicescript()
1784 // netname: directory of the sound files
1785 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1786 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1787 // Here, a - in front of the duration means that no delay is to be
1788 // added after this message
1789 // wait: average time between messages
1790 // delay: initial delay before the first message
1793 self.use = target_voicescript_use;
1795 n = tokenize_console(self.message);
1797 for(i = 0; i+1 < n; i += 2)
1804 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1810 void trigger_relay_teamcheck_use()
1814 if(self.spawnflags & 2)
1816 if(activator.team != self.team)
1821 if(activator.team == self.team)
1827 if(self.spawnflags & 1)
1832 void trigger_relay_teamcheck_reset()
1834 self.team = self.team_saved;
1837 void spawnfunc_trigger_relay_teamcheck()
1839 self.team_saved = self.team;
1840 self.use = trigger_relay_teamcheck_use;
1841 self.reset = trigger_relay_teamcheck_reset;
1846 void trigger_disablerelay_use()
1853 for(e = world; (e = find(e, targetname, self.target)); )
1855 if(e.use == SUB_UseTargets)
1857 e.use = SUB_DontUseTargets;
1860 else if(e.use == SUB_DontUseTargets)
1862 e.use = SUB_UseTargets;
1868 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1871 void spawnfunc_trigger_disablerelay()
1873 self.use = trigger_disablerelay_use;
1876 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1878 float domatch, dotrigger, matchstart, l;
1883 magicear_matched = false;
1885 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1886 domatch = ((ear.spawnflags & 32) || dotrigger);
1893 // we are in TUBA mode!
1894 if (!(ear.spawnflags & 256))
1897 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
1900 magicear_matched = true;
1907 savemessage = self.message;
1908 self.message = string_null;
1910 self.message = savemessage;
1914 if(ear.netname != "")
1920 if(ear.spawnflags & 256) // ENOTUBA
1925 if(ear.spawnflags & 4)
1931 if(ear.spawnflags & 1)
1934 if(ear.spawnflags & 2)
1937 if(ear.spawnflags & 8)
1942 l = strlen(ear.message);
1944 if(ear.spawnflags & 128)
1947 msg = strdecolorize(msgin);
1949 if(substring(ear.message, 0, 1) == "*")
1951 if(substring(ear.message, -1, 1) == "*")
1954 // as we need multi-replacement here...
1955 s = substring(ear.message, 1, -2);
1957 if(strstrofs(msg, s, 0) >= 0)
1958 matchstart = -2; // we use strreplace on s
1963 s = substring(ear.message, 1, -1);
1965 if(substring(msg, -l, l) == s)
1966 matchstart = strlen(msg) - l;
1971 if(substring(ear.message, -1, 1) == "*")
1974 s = substring(ear.message, 0, -2);
1976 if(substring(msg, 0, l) == s)
1983 if(msg == ear.message)
1988 if(matchstart == -1) // no match
1991 magicear_matched = true;
1998 savemessage = self.message;
1999 self.message = string_null;
2001 self.message = savemessage;
2005 if(ear.spawnflags & 16)
2009 else if(ear.netname != "")
2012 return strreplace(s, ear.netname, msg);
2015 substring(msg, 0, matchstart),
2017 substring(msg, matchstart + l, -1)
2024 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2028 for(ear = magicears; ear; ear = ear.enemy)
2030 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2031 if(!(ear.spawnflags & 64))
2032 if(magicear_matched)
2039 void spawnfunc_trigger_magicear()
2041 self.enemy = magicears;
2044 // actually handled in "say" processing
2047 // 2 = ignore teamsay
2049 // 8 = ignore tell to unknown player
2050 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2051 // 32 = perform the replacement even if outside the radius or dead
2052 // 64 = continue replacing/triggering even if this one matched
2053 // 128 = don't decolorize message before matching
2054 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2055 // 512 = tuba notes must be exact right pitch, no transposing
2065 // if set, replacement for the matched text
2067 // "hearing distance"
2071 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2073 self.movedir_x -= 1; // map to tuba instrument numbers
2076 void relay_activators_use()
2082 for(trg = world; (trg = find(trg, targetname, os.target)); )
2086 trg.setactive(os.cnt);
2089 //bprint("Not using setactive\n");
2090 if(os.cnt == ACTIVE_TOGGLE)
2091 if(trg.active == ACTIVE_ACTIVE)
2092 trg.active = ACTIVE_NOT;
2094 trg.active = ACTIVE_ACTIVE;
2096 trg.active = os.cnt;
2102 void spawnfunc_relay_activate()
2104 self.cnt = ACTIVE_ACTIVE;
2105 self.use = relay_activators_use;
2108 void spawnfunc_relay_deactivate()
2110 self.cnt = ACTIVE_NOT;
2111 self.use = relay_activators_use;
2114 void spawnfunc_relay_activatetoggle()
2116 self.cnt = ACTIVE_TOGGLE;
2117 self.use = relay_activators_use;
2120 void spawnfunc_target_changelevel_use()
2122 if(self.gametype != "")
2123 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2125 if (self.chmap == "")
2126 localcmd("endmatch\n");
2128 localcmd(strcat("changelevel ", self.chmap, "\n"));
2131 void spawnfunc_target_changelevel()
2133 self.use = spawnfunc_target_changelevel_use;