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 for(t = world; (t = find(t, targetname, s)); )
109 if(stemp.target_random)
111 RandomSelection_Add(t, 0, string_null, 1, 0);
124 if(stemp.target_random && RandomSelection_chosen_ent)
126 self = RandomSelection_chosen_ent;
138 //=============================================================================
140 // the wait time has passed, so set back up for another activation
145 self.health = self.max_health;
146 self.takedamage = DAMAGE_YES;
147 self.solid = SOLID_BBOX;
152 // the trigger was just touched/killed/used
153 // self.enemy should be set to the activator so it can be held through a delay
154 // so wait for the delay time before firing
157 if (self.nextthink > time)
159 return; // allready been triggered
162 if (self.classname == "trigger_secret")
164 if (!IS_PLAYER(self.enemy))
166 found_secrets = found_secrets + 1;
167 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
171 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
173 // don't trigger again until reset
174 self.takedamage = DAMAGE_NO;
176 activator = self.enemy;
177 other = self.goalentity;
182 self.think = multi_wait;
183 self.nextthink = time + self.wait;
185 else if (self.wait == 0)
187 multi_wait(); // waiting finished
190 { // we can't just remove (self) here, because this is a touch function
191 // called wheil C code is looping through area links...
192 self.touch = func_null;
198 self.goalentity = other;
199 self.enemy = activator;
205 if(!(self.spawnflags & 2))
206 if(!other.iscreature)
210 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
213 // if the trigger has an angles field, check player's facing direction
214 if (self.movedir != '0 0 0')
216 makevectors (other.angles);
217 if (v_forward * self.movedir < 0)
218 return; // not facing the right way
224 self.goalentity = other;
228 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
230 if (!self.takedamage)
232 if(self.spawnflags & DOOR_NOSPLASH)
233 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
235 self.health = self.health - damage;
236 if (self.health <= 0)
238 self.enemy = attacker;
239 self.goalentity = inflictor;
246 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
247 self.touch = multi_touch;
250 self.health = self.max_health;
251 self.takedamage = DAMAGE_YES;
252 self.solid = SOLID_BBOX;
254 self.think = func_null;
256 self.team = self.team_saved;
259 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
260 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.
261 If "delay" is set, the trigger waits some time after activating before firing.
262 "wait" : Seconds between triggerings. (.2 default)
263 If notouch is set, the trigger is only fired by other entities, not by touching.
264 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
270 set "message" to text string
272 void spawnfunc_trigger_multiple()
274 self.reset = multi_reset;
275 if (self.sounds == 1)
277 precache_sound ("misc/secret.wav");
278 self.noise = "misc/secret.wav";
280 else if (self.sounds == 2)
282 precache_sound ("misc/talk.wav");
283 self.noise = "misc/talk.wav";
285 else if (self.sounds == 3)
287 precache_sound ("misc/trigger1.wav");
288 self.noise = "misc/trigger1.wav";
293 else if(self.wait < -1)
295 self.use = multi_use;
299 self.team_saved = self.team;
303 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
304 objerror ("health and notouch don't make sense\n");
305 self.max_health = self.health;
306 self.event_damage = multi_eventdamage;
307 self.takedamage = DAMAGE_YES;
308 self.solid = SOLID_BBOX;
309 setorigin (self, self.origin); // make sure it links into the world
313 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
315 self.touch = multi_touch;
316 setorigin (self, self.origin); // make sure it links into the world
322 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
323 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
324 "targetname". If "health" is set, the trigger must be killed to activate.
325 If notouch is set, the trigger is only fired by other entities, not by touching.
326 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
327 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.
333 set "message" to text string
335 void spawnfunc_trigger_once()
338 spawnfunc_trigger_multiple();
341 //=============================================================================
343 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
344 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
346 void spawnfunc_trigger_relay()
348 self.use = SUB_UseTargets;
349 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
354 self.think = SUB_UseTargets;
355 self.nextthink = self.wait;
360 self.think = func_null;
364 void spawnfunc_trigger_delay()
369 self.use = delay_use;
370 self.reset = delay_reset;
373 //=============================================================================
384 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
385 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
387 self.enemy = activator;
392 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
394 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
396 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
402 self.count = self.cnt;
406 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
407 Acts as an intermediary for an action that takes multiple inputs.
409 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
411 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
413 void spawnfunc_trigger_counter()
418 self.cnt = self.count;
420 self.use = counter_use;
421 self.reset = counter_reset;
424 void trigger_hurt_use()
426 if(IS_PLAYER(activator))
427 self.enemy = activator;
429 self.enemy = world; // let's just destroy it, if taking over is too much work
432 void trigger_hurt_touch()
434 if (self.active != ACTIVE_ACTIVE)
438 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
441 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
442 if (other.iscreature)
444 if (other.takedamage)
445 if (other.triggerhurttime < time)
448 other.triggerhurttime = time + 1;
455 self.enemy = world; // I still hate you all
458 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
461 else if(other.damagedbytriggers)
466 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
473 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
474 Any object touching this will be hurt
475 set dmg to damage amount
478 void spawnfunc_trigger_hurt()
481 self.active = ACTIVE_ACTIVE;
482 self.touch = trigger_hurt_touch;
483 self.use = trigger_hurt_use;
484 self.enemy = world; // I hate you all
487 if (self.message == "")
488 self.message = "was in the wrong place";
489 if (self.message2 == "")
490 self.message2 = "was thrown into a world of hurt by";
491 // self.message = "someone like %s always gets wrongplaced";
493 if(!trigger_hurt_first)
494 trigger_hurt_first = self;
495 if(trigger_hurt_last)
496 trigger_hurt_last.trigger_hurt_next = self;
497 trigger_hurt_last = self;
500 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
504 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
505 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
511 //////////////////////////////////////////////////////////////
515 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
517 //////////////////////////////////////////////////////////////
519 void trigger_heal_touch()
521 if (self.active != ACTIVE_ACTIVE)
524 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
525 if (other.iscreature)
527 if (other.takedamage)
529 if (other.triggerhealtime < time)
532 other.triggerhealtime = time + 1;
534 if (other.health < self.max_health)
536 other.health = min(other.health + self.health, self.max_health);
537 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
538 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
544 void spawnfunc_trigger_heal()
546 self.active = ACTIVE_ACTIVE;
549 self.touch = trigger_heal_touch;
552 if (!self.max_health)
553 self.max_health = 200; //Max health topoff for field
555 self.noise = "misc/mediumhealth.wav";
556 precache_sound(self.noise);
560 //////////////////////////////////////////////////////////////
566 //////////////////////////////////////////////////////////////
568 void trigger_gravity_remove(entity own)
570 if(own.trigger_gravity_check.owner == own)
572 UpdateCSQCProjectile(own);
573 own.gravity = own.trigger_gravity_check.gravity;
574 remove(own.trigger_gravity_check);
577 backtrace("Removing a trigger_gravity_check with no valid owner");
578 own.trigger_gravity_check = world;
580 void trigger_gravity_check_think()
582 // This spawns when a player enters the gravity zone and checks if he left.
583 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
584 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
587 if(self.owner.trigger_gravity_check == self)
588 trigger_gravity_remove(self.owner);
596 self.nextthink = time;
600 void trigger_gravity_use()
602 self.state = !self.state;
605 void trigger_gravity_touch()
609 if(self.state != true)
616 if (!(self.spawnflags & 1))
618 if(other.trigger_gravity_check)
620 if(self == other.trigger_gravity_check.enemy)
623 other.trigger_gravity_check.count = 2; // gravity one more frame...
628 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
629 trigger_gravity_remove(other);
633 other.trigger_gravity_check = spawn();
634 other.trigger_gravity_check.enemy = self;
635 other.trigger_gravity_check.owner = other;
636 other.trigger_gravity_check.gravity = other.gravity;
637 other.trigger_gravity_check.think = trigger_gravity_check_think;
638 other.trigger_gravity_check.nextthink = time;
639 other.trigger_gravity_check.count = 2;
644 if (other.gravity != g)
648 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
649 UpdateCSQCProjectile(self.owner);
653 void spawnfunc_trigger_gravity()
655 if(self.gravity == 1)
659 self.touch = trigger_gravity_touch;
661 precache_sound(self.noise);
666 self.use = trigger_gravity_use;
667 if(self.spawnflags & 2)
672 //=============================================================================
674 // TODO add a way to do looped sounds with sound(); then complete this entity
675 void target_speaker_use_activator()
677 if (!IS_REAL_CLIENT(activator))
680 if(substring(self.noise, 0, 1) == "*")
683 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
684 if(GetPlayerSoundSampleField_notFound)
685 snd = "misc/null.wav";
686 else if(activator.sample == "")
687 snd = "misc/null.wav";
690 tokenize_console(activator.sample);
694 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
696 snd = strcat(argv(0), ".wav"); // randomization
701 msg_entity = activator;
702 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
704 void target_speaker_use_on()
707 if(substring(self.noise, 0, 1) == "*")
710 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
711 if(GetPlayerSoundSampleField_notFound)
712 snd = "misc/null.wav";
713 else if(activator.sample == "")
714 snd = "misc/null.wav";
717 tokenize_console(activator.sample);
721 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
723 snd = strcat(argv(0), ".wav"); // randomization
728 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
729 if(self.spawnflags & 3)
730 self.use = target_speaker_use_off;
732 void target_speaker_use_off()
734 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
735 self.use = target_speaker_use_on;
737 void target_speaker_reset()
739 if(self.spawnflags & 1) // LOOPED_ON
741 if(self.use == target_speaker_use_on)
742 target_speaker_use_on();
744 else if(self.spawnflags & 2)
746 if(self.use == target_speaker_use_off)
747 target_speaker_use_off();
751 void spawnfunc_target_speaker()
753 // TODO: "*" prefix to sound file name
754 // TODO: wait and random (just, HOW? random is not a field)
756 precache_sound (self.noise);
758 if(!self.atten && !(self.spawnflags & 4))
761 self.atten = ATTEN_NORM;
763 self.atten = ATTEN_STATIC;
765 else if(self.atten < 0)
773 if(self.spawnflags & 8) // ACTIVATOR
774 self.use = target_speaker_use_activator;
775 else if(self.spawnflags & 1) // LOOPED_ON
777 target_speaker_use_on();
778 self.reset = target_speaker_reset;
780 else if(self.spawnflags & 2) // LOOPED_OFF
782 self.use = target_speaker_use_on;
783 self.reset = target_speaker_reset;
786 self.use = target_speaker_use_on;
788 else if(self.spawnflags & 1) // LOOPED_ON
790 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
793 else if(self.spawnflags & 2) // LOOPED_OFF
795 objerror("This sound entity can never be activated");
799 // Quake/Nexuiz fallback
800 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
806 void spawnfunc_func_stardust() {
807 self.effects = EF_STARDUST;
810 float pointparticles_SendEntity(entity to, float fl)
812 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
814 // optional features to save space
816 if(self.spawnflags & 2)
817 fl |= 0x10; // absolute count on toggle-on
818 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
819 fl |= 0x20; // 4 bytes - saves CPU
820 if(self.waterlevel || self.count != 1)
821 fl |= 0x40; // 4 bytes - obscure features almost never used
822 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
823 fl |= 0x80; // 14 bytes - saves lots of space
825 WriteByte(MSG_ENTITY, fl);
829 WriteCoord(MSG_ENTITY, self.impulse);
831 WriteCoord(MSG_ENTITY, 0); // off
835 WriteCoord(MSG_ENTITY, self.origin.x);
836 WriteCoord(MSG_ENTITY, self.origin.y);
837 WriteCoord(MSG_ENTITY, self.origin.z);
841 if(self.model != "null")
843 WriteShort(MSG_ENTITY, self.modelindex);
846 WriteCoord(MSG_ENTITY, self.mins.x);
847 WriteCoord(MSG_ENTITY, self.mins.y);
848 WriteCoord(MSG_ENTITY, self.mins.z);
849 WriteCoord(MSG_ENTITY, self.maxs.x);
850 WriteCoord(MSG_ENTITY, self.maxs.y);
851 WriteCoord(MSG_ENTITY, self.maxs.z);
856 WriteShort(MSG_ENTITY, 0);
859 WriteCoord(MSG_ENTITY, self.maxs.x);
860 WriteCoord(MSG_ENTITY, self.maxs.y);
861 WriteCoord(MSG_ENTITY, self.maxs.z);
864 WriteShort(MSG_ENTITY, self.cnt);
867 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
868 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
872 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
873 WriteByte(MSG_ENTITY, self.count * 16.0);
875 WriteString(MSG_ENTITY, self.noise);
878 WriteByte(MSG_ENTITY, floor(self.atten * 64));
879 WriteByte(MSG_ENTITY, floor(self.volume * 255));
881 WriteString(MSG_ENTITY, self.bgmscript);
882 if(self.bgmscript != "")
884 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
885 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
886 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
887 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
893 void pointparticles_use()
895 self.state = !self.state;
899 void pointparticles_think()
901 if(self.origin != self.oldorigin)
904 self.oldorigin = self.origin;
906 self.nextthink = time;
909 void pointparticles_reset()
911 if(self.spawnflags & 1)
917 void spawnfunc_func_pointparticles()
920 setmodel(self, self.model);
922 precache_sound (self.noise);
924 if(!self.bgmscriptsustain)
925 self.bgmscriptsustain = 1;
926 else if(self.bgmscriptsustain < 0)
927 self.bgmscriptsustain = 0;
930 self.atten = ATTEN_NORM;
931 else if(self.atten < 0)
942 setorigin(self, self.origin + self.mins);
943 setsize(self, '0 0 0', self.maxs - self.mins);
946 self.cnt = particleeffectnum(self.mdl);
948 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
952 self.use = pointparticles_use;
953 self.reset = pointparticles_reset;
958 self.think = pointparticles_think;
959 self.nextthink = time;
962 void spawnfunc_func_sparks()
964 // self.cnt is the amount of sparks that one burst will spawn
966 self.cnt = 25.0; // nice default value
969 // self.wait is the probability that a sparkthink will spawn a spark shower
970 // range: 0 - 1, but 0 makes little sense, so...
971 if(self.wait < 0.05) {
972 self.wait = 0.25; // nice default value
975 self.count = self.cnt;
978 self.velocity = '0 0 -1';
979 self.mdl = "TE_SPARK";
980 self.impulse = 10 * self.wait; // by default 2.5/sec
982 self.cnt = 0; // use mdl
984 spawnfunc_func_pointparticles();
987 float rainsnow_SendEntity(entity to, float sf)
989 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
990 WriteByte(MSG_ENTITY, self.state);
991 WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
992 WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
993 WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
994 WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
995 WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
996 WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
997 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
998 WriteShort(MSG_ENTITY, self.count);
999 WriteByte(MSG_ENTITY, self.cnt);
1003 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1004 This is an invisible area like a trigger, which rain falls inside of.
1008 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1010 sets color of rain (default 12 - white)
1012 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1014 void spawnfunc_func_rain()
1016 self.dest = self.velocity;
1017 self.velocity = '0 0 0';
1019 self.dest = '0 0 -700';
1020 self.angles = '0 0 0';
1021 self.movetype = MOVETYPE_NONE;
1022 self.solid = SOLID_NOT;
1023 SetBrushEntityModel();
1028 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1031 if(self.count > 65535)
1034 self.state = 1; // 1 is rain, 0 is snow
1037 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1041 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1042 This is an invisible area like a trigger, which snow falls inside of.
1046 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1048 sets color of rain (default 12 - white)
1050 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1052 void spawnfunc_func_snow()
1054 self.dest = self.velocity;
1055 self.velocity = '0 0 0';
1057 self.dest = '0 0 -300';
1058 self.angles = '0 0 0';
1059 self.movetype = MOVETYPE_NONE;
1060 self.solid = SOLID_NOT;
1061 SetBrushEntityModel();
1066 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1069 if(self.count > 65535)
1072 self.state = 0; // 1 is rain, 0 is snow
1075 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1078 void misc_laser_aim()
1083 if(self.spawnflags & 2)
1085 if(self.enemy.origin != self.mangle)
1087 self.mangle = self.enemy.origin;
1088 self.SendFlags |= 2;
1093 a = vectoangles(self.enemy.origin - self.origin);
1095 if(a != self.mangle)
1098 self.SendFlags |= 2;
1104 if(self.angles != self.mangle)
1106 self.mangle = self.angles;
1107 self.SendFlags |= 2;
1110 if(self.origin != self.oldorigin)
1112 self.SendFlags |= 1;
1113 self.oldorigin = self.origin;
1117 void misc_laser_init()
1119 if(self.target != "")
1120 self.enemy = find(world, targetname, self.target);
1123 void misc_laser_think()
1130 self.nextthink = time;
1139 o = self.enemy.origin;
1140 if (!(self.spawnflags & 2))
1141 o = self.origin + normalize(o - self.origin) * 32768;
1145 makevectors(self.mangle);
1146 o = self.origin + v_forward * 32768;
1149 if(self.dmg || self.enemy.target != "")
1151 traceline(self.origin, o, MOVE_NORMAL, self);
1154 hitloc = trace_endpos;
1156 if(self.enemy.target != "") // DETECTOR laser
1158 if(trace_ent.iscreature)
1160 self.pusher = hitent;
1167 activator = self.pusher;
1180 activator = self.pusher;
1190 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1192 if(hitent.takedamage)
1193 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1197 float laser_SendEntity(entity to, float fl)
1199 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1200 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1201 if(self.spawnflags & 2)
1205 if(self.scale != 1 || self.modelscale != 1)
1207 if(self.spawnflags & 4)
1209 WriteByte(MSG_ENTITY, fl);
1212 WriteCoord(MSG_ENTITY, self.origin.x);
1213 WriteCoord(MSG_ENTITY, self.origin.y);
1214 WriteCoord(MSG_ENTITY, self.origin.z);
1218 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
1219 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
1220 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
1222 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1225 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1226 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1228 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1229 WriteShort(MSG_ENTITY, self.cnt + 1);
1235 WriteCoord(MSG_ENTITY, self.enemy.origin.x);
1236 WriteCoord(MSG_ENTITY, self.enemy.origin.y);
1237 WriteCoord(MSG_ENTITY, self.enemy.origin.z);
1241 WriteAngle(MSG_ENTITY, self.mangle.x);
1242 WriteAngle(MSG_ENTITY, self.mangle.y);
1246 WriteByte(MSG_ENTITY, self.state);
1250 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1251 Any object touching the beam will be hurt
1254 spawnfunc_target_position where the laser ends
1256 name of beam end effect to use
1258 color of the beam (default: red)
1260 damage per second (-1 for a laser that kills immediately)
1264 self.state = !self.state;
1265 self.SendFlags |= 4;
1271 if(self.spawnflags & 1)
1277 void spawnfunc_misc_laser()
1281 if(self.mdl == "none")
1285 self.cnt = particleeffectnum(self.mdl);
1288 self.cnt = particleeffectnum("laser_deadly");
1294 self.cnt = particleeffectnum("laser_deadly");
1301 if(self.colormod == '0 0 0')
1303 self.colormod = '1 0 0';
1304 if(self.message == "")
1305 self.message = "saw the light";
1306 if (self.message2 == "")
1307 self.message2 = "was pushed into a laser by";
1310 if(!self.modelscale)
1311 self.modelscale = 1;
1312 else if(self.modelscale < 0)
1313 self.modelscale = 0;
1314 self.think = misc_laser_think;
1315 self.nextthink = time;
1316 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1318 self.mangle = self.angles;
1320 Net_LinkEntity(self, false, 0, laser_SendEntity);
1324 self.reset = laser_reset;
1326 self.use = laser_use;
1332 // tZorks trigger impulse / gravity
1334 // targeted (directional) mode
1335 void trigger_impulse_touch1()
1338 float pushdeltatime;
1341 if (self.active != ACTIVE_ACTIVE)
1344 if (!isPushable(other))
1349 targ = find(world, targetname, self.target);
1352 objerror("trigger_force without a (valid) .target!\n");
1357 str = min(self.radius, vlen(self.origin - other.origin));
1359 if(self.falloff == 1)
1360 str = (str / self.radius) * self.strength;
1361 else if(self.falloff == 2)
1362 str = (1 - (str / self.radius)) * self.strength;
1364 str = self.strength;
1366 pushdeltatime = time - other.lastpushtime;
1367 if (pushdeltatime > 0.15) pushdeltatime = 0;
1368 other.lastpushtime = time;
1369 if(!pushdeltatime) return;
1371 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1372 other.flags &= ~FL_ONGROUND;
1373 UpdateCSQCProjectile(other);
1376 // Directionless (accelerator/decelerator) mode
1377 void trigger_impulse_touch2()
1379 float pushdeltatime;
1381 if (self.active != ACTIVE_ACTIVE)
1384 if (!isPushable(other))
1389 pushdeltatime = time - other.lastpushtime;
1390 if (pushdeltatime > 0.15) pushdeltatime = 0;
1391 other.lastpushtime = time;
1392 if(!pushdeltatime) return;
1394 // div0: ticrate independent, 1 = identity (not 20)
1395 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1396 UpdateCSQCProjectile(other);
1399 // Spherical (gravity/repulsor) mode
1400 void trigger_impulse_touch3()
1402 float pushdeltatime;
1405 if (self.active != ACTIVE_ACTIVE)
1408 if (!isPushable(other))
1413 pushdeltatime = time - other.lastpushtime;
1414 if (pushdeltatime > 0.15) pushdeltatime = 0;
1415 other.lastpushtime = time;
1416 if(!pushdeltatime) return;
1418 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1420 str = min(self.radius, vlen(self.origin - other.origin));
1422 if(self.falloff == 1)
1423 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1424 else if(self.falloff == 2)
1425 str = (str / self.radius) * self.strength; // 0 in the inside
1427 str = self.strength;
1429 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1430 UpdateCSQCProjectile(other);
1433 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1434 -------- KEYS --------
1435 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1436 If not, this trigger acts like a damper/accelerator field.
1438 strength : This is how mutch force to add in the direction of .target each second
1439 when .target is set. If not, this is hoe mutch to slow down/accelerate
1440 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1442 radius : If set, act as a spherical device rather then a liniar one.
1444 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1446 -------- NOTES --------
1447 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1448 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1451 void spawnfunc_trigger_impulse()
1453 self.active = ACTIVE_ACTIVE;
1458 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1459 setorigin(self, self.origin);
1460 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1461 self.touch = trigger_impulse_touch3;
1467 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1468 self.touch = trigger_impulse_touch1;
1472 if(!self.strength) self.strength = 0.9;
1473 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1474 self.touch = trigger_impulse_touch2;
1479 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1480 "Flip-flop" trigger gate... lets only every second trigger event through
1484 self.state = !self.state;
1489 void spawnfunc_trigger_flipflop()
1491 if(self.spawnflags & 1)
1493 self.use = flipflop_use;
1494 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1497 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1498 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1502 self.nextthink = time + self.wait;
1503 self.enemy = activator;
1509 void monoflop_fixed_use()
1513 self.nextthink = time + self.wait;
1515 self.enemy = activator;
1519 void monoflop_think()
1522 activator = self.enemy;
1526 void monoflop_reset()
1532 void spawnfunc_trigger_monoflop()
1536 if(self.spawnflags & 1)
1537 self.use = monoflop_fixed_use;
1539 self.use = monoflop_use;
1540 self.think = monoflop_think;
1542 self.reset = monoflop_reset;
1545 void multivibrator_send()
1550 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1552 newstate = (time < cyclestart + self.wait);
1555 if(self.state != newstate)
1557 self.state = newstate;
1560 self.nextthink = cyclestart + self.wait + 0.01;
1562 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1565 void multivibrator_toggle()
1567 if(self.nextthink == 0)
1569 multivibrator_send();
1582 void multivibrator_reset()
1584 if(!(self.spawnflags & 1))
1585 self.nextthink = 0; // wait for a trigger event
1587 self.nextthink = max(1, time);
1590 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1591 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1592 -------- KEYS --------
1593 target: trigger all entities with this targetname when it goes off
1594 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1595 phase: offset of the timing
1596 wait: "on" cycle time (default: 1)
1597 respawntime: "off" cycle time (default: same as wait)
1598 -------- SPAWNFLAGS --------
1599 START_ON: assume it is already turned on (when targeted)
1601 void spawnfunc_trigger_multivibrator()
1605 if(!self.respawntime)
1606 self.respawntime = self.wait;
1609 self.use = multivibrator_toggle;
1610 self.think = multivibrator_send;
1611 self.nextthink = max(1, time);
1614 multivibrator_reset();
1623 if(self.killtarget != "")
1624 src = find(world, targetname, self.killtarget);
1625 if(self.target != "")
1626 dst = find(world, targetname, self.target);
1630 objerror("follow: could not find target/killtarget");
1636 // already done :P entity must stay
1640 else if(!src || !dst)
1642 objerror("follow: could not find target/killtarget");
1645 else if(self.spawnflags & 1)
1648 if(self.spawnflags & 2)
1650 setattachment(dst, src, self.message);
1654 attach_sameorigin(dst, src, self.message);
1657 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1662 if(self.spawnflags & 2)
1664 dst.movetype = MOVETYPE_FOLLOW;
1666 // dst.punchangle = '0 0 0'; // keep unchanged
1667 dst.view_ofs = dst.origin;
1668 dst.v_angle = dst.angles;
1672 follow_sameorigin(dst, src);
1679 void spawnfunc_misc_follow()
1681 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1686 void gamestart_use() {
1692 void spawnfunc_trigger_gamestart() {
1693 self.use = gamestart_use;
1694 self.reset2 = spawnfunc_trigger_gamestart;
1698 self.think = self.use;
1699 self.nextthink = game_starttime + self.wait;
1702 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1708 void target_voicescript_clear(entity pl)
1710 pl.voicescript = world;
1713 void target_voicescript_use()
1715 if(activator.voicescript != self)
1717 activator.voicescript = self;
1718 activator.voicescript_index = 0;
1719 activator.voicescript_nextthink = time + self.delay;
1723 void target_voicescript_next(entity pl)
1728 vs = pl.voicescript;
1731 if(vs.message == "")
1738 if(time >= pl.voicescript_voiceend)
1740 if(time >= pl.voicescript_nextthink)
1742 // get the next voice...
1743 n = tokenize_console(vs.message);
1745 if(pl.voicescript_index < vs.cnt)
1746 i = pl.voicescript_index * 2;
1747 else if(n > vs.cnt * 2)
1748 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1754 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1755 dt = stof(argv(i + 1));
1758 pl.voicescript_voiceend = time + dt;
1759 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1763 pl.voicescript_voiceend = time - dt;
1764 pl.voicescript_nextthink = pl.voicescript_voiceend;
1767 pl.voicescript_index += 1;
1771 pl.voicescript = world; // stop trying then
1777 void spawnfunc_target_voicescript()
1779 // netname: directory of the sound files
1780 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1781 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1782 // Here, a - in front of the duration means that no delay is to be
1783 // added after this message
1784 // wait: average time between messages
1785 // delay: initial delay before the first message
1788 self.use = target_voicescript_use;
1790 n = tokenize_console(self.message);
1792 for(i = 0; i+1 < n; i += 2)
1799 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1805 void trigger_relay_teamcheck_use()
1809 if(self.spawnflags & 2)
1811 if(activator.team != self.team)
1816 if(activator.team == self.team)
1822 if(self.spawnflags & 1)
1827 void trigger_relay_teamcheck_reset()
1829 self.team = self.team_saved;
1832 void spawnfunc_trigger_relay_teamcheck()
1834 self.team_saved = self.team;
1835 self.use = trigger_relay_teamcheck_use;
1836 self.reset = trigger_relay_teamcheck_reset;
1841 void trigger_disablerelay_use()
1848 for(e = world; (e = find(e, targetname, self.target)); )
1850 if(e.use == SUB_UseTargets)
1852 e.use = SUB_DontUseTargets;
1855 else if(e.use == SUB_DontUseTargets)
1857 e.use = SUB_UseTargets;
1863 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1866 void spawnfunc_trigger_disablerelay()
1868 self.use = trigger_disablerelay_use;
1871 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1873 float domatch, dotrigger, matchstart, l;
1878 magicear_matched = false;
1880 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1881 domatch = ((ear.spawnflags & 32) || dotrigger);
1888 // we are in TUBA mode!
1889 if (!(ear.spawnflags & 256))
1892 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
1895 magicear_matched = true;
1902 savemessage = self.message;
1903 self.message = string_null;
1905 self.message = savemessage;
1909 if(ear.netname != "")
1915 if(ear.spawnflags & 256) // ENOTUBA
1920 if(ear.spawnflags & 4)
1926 if(ear.spawnflags & 1)
1929 if(ear.spawnflags & 2)
1932 if(ear.spawnflags & 8)
1937 l = strlen(ear.message);
1939 if(ear.spawnflags & 128)
1942 msg = strdecolorize(msgin);
1944 if(substring(ear.message, 0, 1) == "*")
1946 if(substring(ear.message, -1, 1) == "*")
1949 // as we need multi-replacement here...
1950 s = substring(ear.message, 1, -2);
1952 if(strstrofs(msg, s, 0) >= 0)
1953 matchstart = -2; // we use strreplace on s
1958 s = substring(ear.message, 1, -1);
1960 if(substring(msg, -l, l) == s)
1961 matchstart = strlen(msg) - l;
1966 if(substring(ear.message, -1, 1) == "*")
1969 s = substring(ear.message, 0, -2);
1971 if(substring(msg, 0, l) == s)
1978 if(msg == ear.message)
1983 if(matchstart == -1) // no match
1986 magicear_matched = true;
1993 savemessage = self.message;
1994 self.message = string_null;
1996 self.message = savemessage;
2000 if(ear.spawnflags & 16)
2004 else if(ear.netname != "")
2007 return strreplace(s, ear.netname, msg);
2010 substring(msg, 0, matchstart),
2012 substring(msg, matchstart + l, -1)
2019 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2023 for(ear = magicears; ear; ear = ear.enemy)
2025 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2026 if(!(ear.spawnflags & 64))
2027 if(magicear_matched)
2034 void spawnfunc_trigger_magicear()
2036 self.enemy = magicears;
2039 // actually handled in "say" processing
2042 // 2 = ignore teamsay
2044 // 8 = ignore tell to unknown player
2045 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2046 // 32 = perform the replacement even if outside the radius or dead
2047 // 64 = continue replacing/triggering even if this one matched
2048 // 128 = don't decolorize message before matching
2049 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2050 // 512 = tuba notes must be exact right pitch, no transposing
2060 // if set, replacement for the matched text
2062 // "hearing distance"
2066 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2068 self.movedir_x -= 1; // map to tuba instrument numbers
2071 void relay_activators_use()
2077 for(trg = world; (trg = find(trg, targetname, os.target)); )
2081 trg.setactive(os.cnt);
2084 //bprint("Not using setactive\n");
2085 if(os.cnt == ACTIVE_TOGGLE)
2086 if(trg.active == ACTIVE_ACTIVE)
2087 trg.active = ACTIVE_NOT;
2089 trg.active = ACTIVE_ACTIVE;
2091 trg.active = os.cnt;
2097 void spawnfunc_relay_activate()
2099 self.cnt = ACTIVE_ACTIVE;
2100 self.use = relay_activators_use;
2103 void spawnfunc_relay_deactivate()
2105 self.cnt = ACTIVE_NOT;
2106 self.use = relay_activators_use;
2109 void spawnfunc_relay_activatetoggle()
2111 self.cnt = ACTIVE_TOGGLE;
2112 self.use = relay_activators_use;
2115 void spawnfunc_target_changelevel_use()
2117 if(self.gametype != "")
2118 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2120 if (self.chmap == "")
2121 localcmd("endmatch\n");
2123 localcmd(strcat("changelevel ", self.chmap, "\n"));
2126 void spawnfunc_target_changelevel()
2128 self.use = spawnfunc_target_changelevel_use;