1 void SUB_DontUseTargets()
10 activator = self.enemy;
16 ==============================
19 the global "activator" should be set to the entity that initiated the firing.
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
24 Centerprints any self.message to the activator.
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
32 ==============================
36 entity t, stemp, otemp, act;
45 // create a temp object to fire at a later time
47 t.classname = "DelayedUse";
48 t.nextthink = time + self.delay;
51 t.message = self.message;
52 t.killtarget = self.killtarget;
53 t.target = self.target;
54 t.target2 = self.target2;
55 t.target3 = self.target3;
56 t.target4 = self.target4;
64 if (IS_PLAYER(activator) && self.message != "")
66 if(IS_REAL_CLIENT(activator))
68 centerprint (activator, self.message);
70 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 const float SPAWNFLAG_NOMESSAGE = 1;
141 const float SPAWNFLAG_NOTOUCH = 1;
143 // the wait time has passed, so set back up for another activation
148 self.health = self.max_health;
149 self.takedamage = DAMAGE_YES;
150 self.solid = SOLID_BBOX;
155 // the trigger was just touched/killed/used
156 // self.enemy should be set to the activator so it can be held through a delay
157 // so wait for the delay time before firing
160 if (self.nextthink > time)
162 return; // allready been triggered
165 if (self.classname == "trigger_secret")
167 if (!IS_PLAYER(self.enemy))
169 found_secrets = found_secrets + 1;
170 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
174 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
176 // don't trigger again until reset
177 self.takedamage = DAMAGE_NO;
179 activator = self.enemy;
180 other = self.goalentity;
185 self.think = multi_wait;
186 self.nextthink = time + self.wait;
188 else if (self.wait == 0)
190 multi_wait(); // waiting finished
193 { // we can't just remove (self) here, because this is a touch function
194 // called wheil C code is looping through area links...
195 self.touch = func_null;
201 self.goalentity = other;
202 self.enemy = activator;
208 if (!(self.spawnflags & 2))
209 if (!other.iscreature)
213 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
216 // if the trigger has an angles field, check player's facing direction
217 if (self.movedir != '0 0 0')
219 makevectors (other.angles);
220 if (v_forward * self.movedir < 0)
221 return; // not facing the right way
227 self.goalentity = other;
231 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
233 if (!self.takedamage)
235 if(self.spawnflags & DOOR_NOSPLASH)
236 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
238 self.health = self.health - damage;
239 if (self.health <= 0)
241 self.enemy = attacker;
242 self.goalentity = inflictor;
249 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
250 self.touch = multi_touch;
253 self.health = self.max_health;
254 self.takedamage = DAMAGE_YES;
255 self.solid = SOLID_BBOX;
257 self.think = func_null;
259 self.team = self.team_saved;
262 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
263 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.
264 If "delay" is set, the trigger waits some time after activating before firing.
265 "wait" : Seconds between triggerings. (.2 default)
266 If notouch is set, the trigger is only fired by other entities, not by touching.
267 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
273 set "message" to text string
275 void spawnfunc_trigger_multiple()
277 self.reset = multi_reset;
278 if (self.sounds == 1)
280 precache_sound ("misc/secret.wav");
281 self.noise = "misc/secret.wav";
283 else if (self.sounds == 2)
285 precache_sound ("misc/talk.wav");
286 self.noise = "misc/talk.wav";
288 else if (self.sounds == 3)
290 precache_sound ("misc/trigger1.wav");
291 self.noise = "misc/trigger1.wav";
296 else if(self.wait < -1)
298 self.use = multi_use;
302 self.team_saved = self.team;
306 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
307 objerror ("health and notouch don't make sense\n");
308 self.max_health = self.health;
309 self.event_damage = multi_eventdamage;
310 self.takedamage = DAMAGE_YES;
311 self.solid = SOLID_BBOX;
312 setorigin (self, self.origin); // make sure it links into the world
316 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
318 self.touch = multi_touch;
319 setorigin (self, self.origin); // make sure it links into the world
325 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
326 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
327 "targetname". If "health" is set, the trigger must be killed to activate.
328 If notouch is set, the trigger is only fired by other entities, not by touching.
329 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
330 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.
336 set "message" to text string
338 void spawnfunc_trigger_once()
341 spawnfunc_trigger_multiple();
344 //=============================================================================
346 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
347 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
349 void spawnfunc_trigger_relay()
351 self.use = SUB_UseTargets;
352 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
357 self.think = SUB_UseTargets;
358 self.nextthink = self.wait;
363 self.think = func_null;
367 void spawnfunc_trigger_delay()
372 self.use = delay_use;
373 self.reset = delay_reset;
376 //=============================================================================
381 self.count = self.count - 1;
387 if (IS_PLAYER(activator)
388 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
391 centerprint (activator, "There are more to go...");
392 else if (self.count == 3)
393 centerprint (activator, "Only 3 more to go...");
394 else if (self.count == 2)
395 centerprint (activator, "Only 2 more to go...");
397 centerprint (activator, "Only 1 more to go...");
402 if (IS_PLAYER(activator)
403 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
404 centerprint(activator, "Sequence completed!");
405 self.enemy = activator;
411 self.count = self.cnt;
415 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
416 Acts as an intermediary for an action that takes multiple inputs.
418 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
420 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
422 void spawnfunc_trigger_counter()
427 self.cnt = self.count;
429 self.use = counter_use;
430 self.reset = counter_reset;
433 void trigger_hurt_use()
435 if(IS_PLAYER(activator))
436 self.enemy = activator;
438 self.enemy = world; // let's just destroy it, if taking over is too much work
441 .float triggerhurttime;
442 void trigger_hurt_touch()
444 if (self.active != ACTIVE_ACTIVE)
448 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
451 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
452 if (other.iscreature)
454 if (other.takedamage)
455 if (other.triggerhurttime < time)
458 other.triggerhurttime = time + 1;
465 self.enemy = world; // I still hate you all
468 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
471 else if(other.damagedbytriggers)
476 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
483 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
484 Any object touching this will be hurt
485 set dmg to damage amount
488 .entity trigger_hurt_next;
489 entity trigger_hurt_last;
490 entity trigger_hurt_first;
491 void spawnfunc_trigger_hurt()
494 self.active = ACTIVE_ACTIVE;
495 self.touch = trigger_hurt_touch;
496 self.use = trigger_hurt_use;
497 self.enemy = world; // I hate you all
500 if (self.message == "")
501 self.message = "was in the wrong place";
502 if (self.message2 == "")
503 self.message2 = "was thrown into a world of hurt by";
504 // self.message = "someone like %s always gets wrongplaced";
506 if(!trigger_hurt_first)
507 trigger_hurt_first = self;
508 if(trigger_hurt_last)
509 trigger_hurt_last.trigger_hurt_next = self;
510 trigger_hurt_last = self;
513 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
517 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
518 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
524 //////////////////////////////////////////////////////////////
528 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
530 //////////////////////////////////////////////////////////////
532 .float triggerhealtime;
533 void trigger_heal_touch()
535 if (self.active != ACTIVE_ACTIVE)
538 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
539 if (other.iscreature)
541 if (other.takedamage)
543 if (other.triggerhealtime < time)
546 other.triggerhealtime = time + 1;
548 if (other.health < self.max_health)
550 other.health = min(other.health + self.health, self.max_health);
551 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
552 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
558 void spawnfunc_trigger_heal()
560 self.active = ACTIVE_ACTIVE;
563 self.touch = trigger_heal_touch;
566 if (!self.max_health)
567 self.max_health = 200; //Max health topoff for field
569 self.noise = "misc/mediumhealth.wav";
570 precache_sound(self.noise);
574 //////////////////////////////////////////////////////////////
580 //////////////////////////////////////////////////////////////
582 .entity trigger_gravity_check;
583 void trigger_gravity_remove(entity own)
585 if(own.trigger_gravity_check.owner == own)
587 UpdateCSQCProjectile(own);
588 own.gravity = own.trigger_gravity_check.gravity;
589 remove(own.trigger_gravity_check);
592 backtrace("Removing a trigger_gravity_check with no valid owner");
593 own.trigger_gravity_check = world;
595 void trigger_gravity_check_think()
597 // This spawns when a player enters the gravity zone and checks if he left.
598 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
599 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
602 if(self.owner.trigger_gravity_check == self)
603 trigger_gravity_remove(self.owner);
611 self.nextthink = time;
615 void trigger_gravity_use()
617 self.state = !self.state;
620 void trigger_gravity_touch()
624 if(self.state != TRUE)
631 if (!(self.spawnflags & 1))
633 if(other.trigger_gravity_check)
635 if(self == other.trigger_gravity_check.enemy)
638 other.trigger_gravity_check.count = 2; // gravity one more frame...
643 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
644 trigger_gravity_remove(other);
648 other.trigger_gravity_check = spawn();
649 other.trigger_gravity_check.enemy = self;
650 other.trigger_gravity_check.owner = other;
651 other.trigger_gravity_check.gravity = other.gravity;
652 other.trigger_gravity_check.think = trigger_gravity_check_think;
653 other.trigger_gravity_check.nextthink = time;
654 other.trigger_gravity_check.count = 2;
659 if (other.gravity != g)
663 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
664 UpdateCSQCProjectile(self.owner);
668 void spawnfunc_trigger_gravity()
670 if(self.gravity == 1)
674 self.touch = trigger_gravity_touch;
676 precache_sound(self.noise);
681 self.use = trigger_gravity_use;
682 if(self.spawnflags & 2)
687 //=============================================================================
689 // TODO add a way to do looped sounds with sound(); then complete this entity
690 .float volume, atten;
691 void target_speaker_use_off();
692 void target_speaker_use_activator()
694 if (!IS_REAL_CLIENT(activator))
697 if(substring(self.noise, 0, 1) == "*")
700 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
701 if(GetPlayerSoundSampleField_notFound)
702 snd = "misc/null.wav";
703 else if(activator.sample == "")
704 snd = "misc/null.wav";
707 tokenize_console(activator.sample);
711 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
713 snd = strcat(argv(0), ".wav"); // randomization
718 msg_entity = activator;
719 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
721 void target_speaker_use_on()
724 if(substring(self.noise, 0, 1) == "*")
727 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
728 if(GetPlayerSoundSampleField_notFound)
729 snd = "misc/null.wav";
730 else if(activator.sample == "")
731 snd = "misc/null.wav";
734 tokenize_console(activator.sample);
738 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
740 snd = strcat(argv(0), ".wav"); // randomization
745 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
746 if(self.spawnflags & 3)
747 self.use = target_speaker_use_off;
749 void target_speaker_use_off()
751 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
752 self.use = target_speaker_use_on;
754 void target_speaker_reset()
756 if(self.spawnflags & 1) // LOOPED_ON
758 if(self.use == target_speaker_use_on)
759 target_speaker_use_on();
761 else if(self.spawnflags & 2)
763 if(self.use == target_speaker_use_off)
764 target_speaker_use_off();
768 void spawnfunc_target_speaker()
770 // TODO: "*" prefix to sound file name
771 // TODO: wait and random (just, HOW? random is not a field)
773 precache_sound (self.noise);
775 if(!self.atten && !(self.spawnflags & 4))
778 self.atten = ATTEN_NORM;
780 self.atten = ATTEN_STATIC;
782 else if(self.atten < 0)
790 if(self.spawnflags & 8) // ACTIVATOR
791 self.use = target_speaker_use_activator;
792 else if(self.spawnflags & 1) // LOOPED_ON
794 target_speaker_use_on();
795 self.reset = target_speaker_reset;
797 else if(self.spawnflags & 2) // LOOPED_OFF
799 self.use = target_speaker_use_on;
800 self.reset = target_speaker_reset;
803 self.use = target_speaker_use_on;
805 else if(self.spawnflags & 1) // LOOPED_ON
807 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
810 else if(self.spawnflags & 2) // LOOPED_OFF
812 objerror("This sound entity can never be activated");
816 // Quake/Nexuiz fallback
817 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
823 void spawnfunc_func_stardust() {
824 self.effects = EF_STARDUST;
828 .float bgmscriptattack;
829 .float bgmscriptdecay;
830 .float bgmscriptsustain;
831 .float bgmscriptrelease;
832 float pointparticles_SendEntity(entity to, float fl)
834 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
836 // optional features to save space
838 if(self.spawnflags & 2)
839 fl |= 0x10; // absolute count on toggle-on
840 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
841 fl |= 0x20; // 4 bytes - saves CPU
842 if(self.waterlevel || self.count != 1)
843 fl |= 0x40; // 4 bytes - obscure features almost never used
844 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
845 fl |= 0x80; // 14 bytes - saves lots of space
847 WriteByte(MSG_ENTITY, fl);
851 WriteCoord(MSG_ENTITY, self.impulse);
853 WriteCoord(MSG_ENTITY, 0); // off
857 WriteCoord(MSG_ENTITY, self.origin_x);
858 WriteCoord(MSG_ENTITY, self.origin_y);
859 WriteCoord(MSG_ENTITY, self.origin_z);
863 if(self.model != "null")
865 WriteShort(MSG_ENTITY, self.modelindex);
868 WriteCoord(MSG_ENTITY, self.mins_x);
869 WriteCoord(MSG_ENTITY, self.mins_y);
870 WriteCoord(MSG_ENTITY, self.mins_z);
871 WriteCoord(MSG_ENTITY, self.maxs_x);
872 WriteCoord(MSG_ENTITY, self.maxs_y);
873 WriteCoord(MSG_ENTITY, self.maxs_z);
878 WriteShort(MSG_ENTITY, 0);
881 WriteCoord(MSG_ENTITY, self.maxs_x);
882 WriteCoord(MSG_ENTITY, self.maxs_y);
883 WriteCoord(MSG_ENTITY, self.maxs_z);
886 WriteShort(MSG_ENTITY, self.cnt);
889 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
890 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
894 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
895 WriteByte(MSG_ENTITY, self.count * 16.0);
897 WriteString(MSG_ENTITY, self.noise);
900 WriteByte(MSG_ENTITY, floor(self.atten * 64));
901 WriteByte(MSG_ENTITY, floor(self.volume * 255));
903 WriteString(MSG_ENTITY, self.bgmscript);
904 if(self.bgmscript != "")
906 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
907 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
908 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
909 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
915 void pointparticles_use()
917 self.state = !self.state;
921 void pointparticles_think()
923 if(self.origin != self.oldorigin)
926 self.oldorigin = self.origin;
928 self.nextthink = time;
931 void pointparticles_reset()
933 if(self.spawnflags & 1)
939 void spawnfunc_func_pointparticles()
942 setmodel(self, self.model);
944 precache_sound (self.noise);
946 if(!self.bgmscriptsustain)
947 self.bgmscriptsustain = 1;
948 else if(self.bgmscriptsustain < 0)
949 self.bgmscriptsustain = 0;
952 self.atten = ATTEN_NORM;
953 else if(self.atten < 0)
964 setorigin(self, self.origin + self.mins);
965 setsize(self, '0 0 0', self.maxs - self.mins);
968 self.cnt = particleeffectnum(self.mdl);
970 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
974 self.use = pointparticles_use;
975 self.reset = pointparticles_reset;
980 self.think = pointparticles_think;
981 self.nextthink = time;
984 void spawnfunc_func_sparks()
986 // self.cnt is the amount of sparks that one burst will spawn
988 self.cnt = 25.0; // nice default value
991 // self.wait is the probability that a sparkthink will spawn a spark shower
992 // range: 0 - 1, but 0 makes little sense, so...
993 if(self.wait < 0.05) {
994 self.wait = 0.25; // nice default value
997 self.count = self.cnt;
1000 self.velocity = '0 0 -1';
1001 self.mdl = "TE_SPARK";
1002 self.impulse = 10 * self.wait; // by default 2.5/sec
1004 self.cnt = 0; // use mdl
1006 spawnfunc_func_pointparticles();
1009 float rainsnow_SendEntity(entity to, float sf)
1011 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1012 WriteByte(MSG_ENTITY, self.state);
1013 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1014 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1015 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1016 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1017 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1018 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1019 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1020 WriteShort(MSG_ENTITY, self.count);
1021 WriteByte(MSG_ENTITY, self.cnt);
1025 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1026 This is an invisible area like a trigger, which rain falls inside of.
1030 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1032 sets color of rain (default 12 - white)
1034 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1036 void spawnfunc_func_rain()
1038 self.dest = self.velocity;
1039 self.velocity = '0 0 0';
1041 self.dest = '0 0 -700';
1042 self.angles = '0 0 0';
1043 self.movetype = MOVETYPE_NONE;
1044 self.solid = SOLID_NOT;
1045 SetBrushEntityModel();
1050 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1053 if(self.count > 65535)
1056 self.state = 1; // 1 is rain, 0 is snow
1059 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1063 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1064 This is an invisible area like a trigger, which snow falls inside of.
1068 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1070 sets color of rain (default 12 - white)
1072 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1074 void spawnfunc_func_snow()
1076 self.dest = self.velocity;
1077 self.velocity = '0 0 0';
1079 self.dest = '0 0 -300';
1080 self.angles = '0 0 0';
1081 self.movetype = MOVETYPE_NONE;
1082 self.solid = SOLID_NOT;
1083 SetBrushEntityModel();
1088 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1091 if(self.count > 65535)
1094 self.state = 0; // 1 is rain, 0 is snow
1097 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1101 void misc_laser_aim()
1106 if(self.spawnflags & 2)
1108 if(self.enemy.origin != self.mangle)
1110 self.mangle = self.enemy.origin;
1111 self.SendFlags |= 2;
1116 a = vectoangles(self.enemy.origin - self.origin);
1118 if(a != self.mangle)
1121 self.SendFlags |= 2;
1127 if(self.angles != self.mangle)
1129 self.mangle = self.angles;
1130 self.SendFlags |= 2;
1133 if(self.origin != self.oldorigin)
1135 self.SendFlags |= 1;
1136 self.oldorigin = self.origin;
1140 void misc_laser_init()
1142 if(self.target != "")
1143 self.enemy = find(world, targetname, self.target);
1147 void misc_laser_think()
1154 self.nextthink = time;
1163 o = self.enemy.origin;
1164 if (!(self.spawnflags & 2))
1165 o = self.origin + normalize(o - self.origin) * 32768;
1169 makevectors(self.mangle);
1170 o = self.origin + v_forward * 32768;
1173 if(self.dmg || self.enemy.target != "")
1175 traceline(self.origin, o, MOVE_NORMAL, self);
1178 hitloc = trace_endpos;
1180 if(self.enemy.target != "") // DETECTOR laser
1182 if(trace_ent.iscreature)
1184 self.pusher = hitent;
1191 activator = self.pusher;
1204 activator = self.pusher;
1214 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1216 if(hitent.takedamage)
1217 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1221 float laser_SendEntity(entity to, float fl)
1223 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1224 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1225 if(self.spawnflags & 2)
1229 if(self.scale != 1 || self.modelscale != 1)
1231 if(self.spawnflags & 4)
1233 WriteByte(MSG_ENTITY, fl);
1236 WriteCoord(MSG_ENTITY, self.origin_x);
1237 WriteCoord(MSG_ENTITY, self.origin_y);
1238 WriteCoord(MSG_ENTITY, self.origin_z);
1242 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1243 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1244 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1246 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1249 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1250 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1252 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1253 WriteShort(MSG_ENTITY, self.cnt + 1);
1259 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1260 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1261 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1265 WriteAngle(MSG_ENTITY, self.mangle_x);
1266 WriteAngle(MSG_ENTITY, self.mangle_y);
1270 WriteByte(MSG_ENTITY, self.state);
1274 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1275 Any object touching the beam will be hurt
1278 spawnfunc_target_position where the laser ends
1280 name of beam end effect to use
1282 color of the beam (default: red)
1284 damage per second (-1 for a laser that kills immediately)
1288 self.state = !self.state;
1289 self.SendFlags |= 4;
1295 if(self.spawnflags & 1)
1301 void spawnfunc_misc_laser()
1305 if(self.mdl == "none")
1309 self.cnt = particleeffectnum(self.mdl);
1312 self.cnt = particleeffectnum("laser_deadly");
1318 self.cnt = particleeffectnum("laser_deadly");
1325 if(self.colormod == '0 0 0')
1327 self.colormod = '1 0 0';
1328 if(self.message == "")
1329 self.message = "saw the light";
1330 if (self.message2 == "")
1331 self.message2 = "was pushed into a laser by";
1334 if(!self.modelscale)
1335 self.modelscale = 1;
1336 else if(self.modelscale < 0)
1337 self.modelscale = 0;
1338 self.think = misc_laser_think;
1339 self.nextthink = time;
1340 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1342 self.mangle = self.angles;
1344 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1348 self.reset = laser_reset;
1350 self.use = laser_use;
1356 // tZorks trigger impulse / gravity
1360 .float lastpushtime;
1362 // targeted (directional) mode
1363 void trigger_impulse_touch1()
1366 float pushdeltatime;
1369 if (self.active != ACTIVE_ACTIVE)
1372 if (!isPushable(other))
1377 targ = find(world, targetname, self.target);
1380 objerror("trigger_force without a (valid) .target!\n");
1385 str = min(self.radius, vlen(self.origin - other.origin));
1387 if(self.falloff == 1)
1388 str = (str / self.radius) * self.strength;
1389 else if(self.falloff == 2)
1390 str = (1 - (str / self.radius)) * self.strength;
1392 str = self.strength;
1394 pushdeltatime = time - other.lastpushtime;
1395 if (pushdeltatime > 0.15) pushdeltatime = 0;
1396 other.lastpushtime = time;
1397 if(!pushdeltatime) return;
1399 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1400 other.flags &= ~FL_ONGROUND;
1401 UpdateCSQCProjectile(other);
1404 // Directionless (accelerator/decelerator) mode
1405 void trigger_impulse_touch2()
1407 float pushdeltatime;
1409 if (self.active != ACTIVE_ACTIVE)
1412 if (!isPushable(other))
1417 pushdeltatime = time - other.lastpushtime;
1418 if (pushdeltatime > 0.15) pushdeltatime = 0;
1419 other.lastpushtime = time;
1420 if(!pushdeltatime) return;
1422 // div0: ticrate independent, 1 = identity (not 20)
1423 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1424 UpdateCSQCProjectile(other);
1427 // Spherical (gravity/repulsor) mode
1428 void trigger_impulse_touch3()
1430 float pushdeltatime;
1433 if (self.active != ACTIVE_ACTIVE)
1436 if (!isPushable(other))
1441 pushdeltatime = time - other.lastpushtime;
1442 if (pushdeltatime > 0.15) pushdeltatime = 0;
1443 other.lastpushtime = time;
1444 if(!pushdeltatime) return;
1446 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1448 str = min(self.radius, vlen(self.origin - other.origin));
1450 if(self.falloff == 1)
1451 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1452 else if(self.falloff == 2)
1453 str = (str / self.radius) * self.strength; // 0 in the inside
1455 str = self.strength;
1457 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1458 UpdateCSQCProjectile(other);
1461 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1462 -------- KEYS --------
1463 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1464 If not, this trigger acts like a damper/accelerator field.
1466 strength : This is how mutch force to add in the direction of .target each second
1467 when .target is set. If not, this is hoe mutch to slow down/accelerate
1468 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1470 radius : If set, act as a spherical device rather then a liniar one.
1472 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1474 -------- NOTES --------
1475 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1476 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1479 void spawnfunc_trigger_impulse()
1481 self.active = ACTIVE_ACTIVE;
1486 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1487 setorigin(self, self.origin);
1488 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1489 self.touch = trigger_impulse_touch3;
1495 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1496 self.touch = trigger_impulse_touch1;
1500 if(!self.strength) self.strength = 0.9;
1501 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1502 self.touch = trigger_impulse_touch2;
1507 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1508 "Flip-flop" trigger gate... lets only every second trigger event through
1512 self.state = !self.state;
1517 void spawnfunc_trigger_flipflop()
1519 if(self.spawnflags & 1)
1521 self.use = flipflop_use;
1522 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1525 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1526 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1530 self.nextthink = time + self.wait;
1531 self.enemy = activator;
1537 void monoflop_fixed_use()
1541 self.nextthink = time + self.wait;
1543 self.enemy = activator;
1547 void monoflop_think()
1550 activator = self.enemy;
1554 void monoflop_reset()
1560 void spawnfunc_trigger_monoflop()
1564 if(self.spawnflags & 1)
1565 self.use = monoflop_fixed_use;
1567 self.use = monoflop_use;
1568 self.think = monoflop_think;
1570 self.reset = monoflop_reset;
1573 void multivibrator_send()
1578 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1580 newstate = (time < cyclestart + self.wait);
1583 if(self.state != newstate)
1585 self.state = newstate;
1588 self.nextthink = cyclestart + self.wait + 0.01;
1590 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1593 void multivibrator_toggle()
1595 if(self.nextthink == 0)
1597 multivibrator_send();
1610 void multivibrator_reset()
1612 if(!(self.spawnflags & 1))
1613 self.nextthink = 0; // wait for a trigger event
1615 self.nextthink = max(1, time);
1618 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1619 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1620 -------- KEYS --------
1621 target: trigger all entities with this targetname when it goes off
1622 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1623 phase: offset of the timing
1624 wait: "on" cycle time (default: 1)
1625 respawntime: "off" cycle time (default: same as wait)
1626 -------- SPAWNFLAGS --------
1627 START_ON: assume it is already turned on (when targeted)
1629 void spawnfunc_trigger_multivibrator()
1633 if(!self.respawntime)
1634 self.respawntime = self.wait;
1637 self.use = multivibrator_toggle;
1638 self.think = multivibrator_send;
1639 self.nextthink = max(1, time);
1642 multivibrator_reset();
1651 if(self.killtarget != "")
1652 src = find(world, targetname, self.killtarget);
1653 if(self.target != "")
1654 dst = find(world, targetname, self.target);
1658 objerror("follow: could not find target/killtarget");
1664 // already done :P entity must stay
1668 else if(!src || !dst)
1670 objerror("follow: could not find target/killtarget");
1673 else if(self.spawnflags & 1)
1676 if(self.spawnflags & 2)
1678 setattachment(dst, src, self.message);
1682 attach_sameorigin(dst, src, self.message);
1685 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1690 if(self.spawnflags & 2)
1692 dst.movetype = MOVETYPE_FOLLOW;
1694 // dst.punchangle = '0 0 0'; // keep unchanged
1695 dst.view_ofs = dst.origin;
1696 dst.v_angle = dst.angles;
1700 follow_sameorigin(dst, src);
1707 void spawnfunc_misc_follow()
1709 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1714 void gamestart_use() {
1720 void spawnfunc_trigger_gamestart() {
1721 self.use = gamestart_use;
1722 self.reset2 = spawnfunc_trigger_gamestart;
1726 self.think = self.use;
1727 self.nextthink = game_starttime + self.wait;
1730 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1736 .entity voicescript; // attached voice script
1737 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1738 .float voicescript_nextthink; // time to play next voice
1739 .float voicescript_voiceend; // time when this voice ends
1741 void target_voicescript_clear(entity pl)
1743 pl.voicescript = world;
1746 void target_voicescript_use()
1748 if(activator.voicescript != self)
1750 activator.voicescript = self;
1751 activator.voicescript_index = 0;
1752 activator.voicescript_nextthink = time + self.delay;
1756 void target_voicescript_next(entity pl)
1761 vs = pl.voicescript;
1764 if(vs.message == "")
1771 if(time >= pl.voicescript_voiceend)
1773 if(time >= pl.voicescript_nextthink)
1775 // get the next voice...
1776 n = tokenize_console(vs.message);
1778 if(pl.voicescript_index < vs.cnt)
1779 i = pl.voicescript_index * 2;
1780 else if(n > vs.cnt * 2)
1781 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1787 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1788 dt = stof(argv(i + 1));
1791 pl.voicescript_voiceend = time + dt;
1792 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1796 pl.voicescript_voiceend = time - dt;
1797 pl.voicescript_nextthink = pl.voicescript_voiceend;
1800 pl.voicescript_index += 1;
1804 pl.voicescript = world; // stop trying then
1810 void spawnfunc_target_voicescript()
1812 // netname: directory of the sound files
1813 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1814 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1815 // Here, a - in front of the duration means that no delay is to be
1816 // added after this message
1817 // wait: average time between messages
1818 // delay: initial delay before the first message
1821 self.use = target_voicescript_use;
1823 n = tokenize_console(self.message);
1825 for(i = 0; i+1 < n; i += 2)
1832 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1838 void trigger_relay_teamcheck_use()
1842 if(self.spawnflags & 2)
1844 if(activator.team != self.team)
1849 if(activator.team == self.team)
1855 if(self.spawnflags & 1)
1860 void trigger_relay_teamcheck_reset()
1862 self.team = self.team_saved;
1865 void spawnfunc_trigger_relay_teamcheck()
1867 self.team_saved = self.team;
1868 self.use = trigger_relay_teamcheck_use;
1869 self.reset = trigger_relay_teamcheck_reset;
1874 void trigger_disablerelay_use()
1881 for(e = world; (e = find(e, targetname, self.target)); )
1883 if(e.use == SUB_UseTargets)
1885 e.use = SUB_DontUseTargets;
1888 else if(e.use == SUB_DontUseTargets)
1890 e.use = SUB_UseTargets;
1896 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1899 void spawnfunc_trigger_disablerelay()
1901 self.use = trigger_disablerelay_use;
1904 float magicear_matched;
1905 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1906 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1908 float domatch, dotrigger, matchstart, l;
1913 magicear_matched = FALSE;
1915 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1916 domatch = ((ear.spawnflags & 32) || dotrigger);
1923 // we are in TUBA mode!
1924 if (!(ear.spawnflags & 256))
1927 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1930 magicear_matched = TRUE;
1937 savemessage = self.message;
1938 self.message = string_null;
1940 self.message = savemessage;
1944 if(ear.netname != "")
1950 if(ear.spawnflags & 256) // ENOTUBA
1955 if(ear.spawnflags & 4)
1961 if(ear.spawnflags & 1)
1964 if(ear.spawnflags & 2)
1967 if(ear.spawnflags & 8)
1972 l = strlen(ear.message);
1974 if(ear.spawnflags & 128)
1977 msg = strdecolorize(msgin);
1979 if(substring(ear.message, 0, 1) == "*")
1981 if(substring(ear.message, -1, 1) == "*")
1984 // as we need multi-replacement here...
1985 s = substring(ear.message, 1, -2);
1987 if(strstrofs(msg, s, 0) >= 0)
1988 matchstart = -2; // we use strreplace on s
1993 s = substring(ear.message, 1, -1);
1995 if(substring(msg, -l, l) == s)
1996 matchstart = strlen(msg) - l;
2001 if(substring(ear.message, -1, 1) == "*")
2004 s = substring(ear.message, 0, -2);
2006 if(substring(msg, 0, l) == s)
2013 if(msg == ear.message)
2018 if(matchstart == -1) // no match
2021 magicear_matched = TRUE;
2028 savemessage = self.message;
2029 self.message = string_null;
2031 self.message = savemessage;
2035 if(ear.spawnflags & 16)
2039 else if(ear.netname != "")
2042 return strreplace(s, ear.netname, msg);
2045 substring(msg, 0, matchstart),
2047 substring(msg, matchstart + l, -1)
2055 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2059 for(ear = magicears; ear; ear = ear.enemy)
2061 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2062 if (!(ear.spawnflags & 64))
2063 if(magicear_matched)
2070 void spawnfunc_trigger_magicear()
2072 self.enemy = magicears;
2075 // actually handled in "say" processing
2078 // 2 = ignore teamsay
2080 // 8 = ignore tell to unknown player
2081 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2082 // 32 = perform the replacement even if outside the radius or dead
2083 // 64 = continue replacing/triggering even if this one matched
2084 // 128 = don't decolorize message before matching
2085 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2086 // 512 = tuba notes must be exact right pitch, no transposing
2096 // if set, replacement for the matched text
2098 // "hearing distance"
2102 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2104 self.movedir_x -= 1; // map to tuba instrument numbers
2107 void relay_activators_use()
2113 for(trg = world; (trg = find(trg, targetname, os.target)); )
2117 trg.setactive(os.cnt);
2120 //bprint("Not using setactive\n");
2121 if(os.cnt == ACTIVE_TOGGLE)
2122 if(trg.active == ACTIVE_ACTIVE)
2123 trg.active = ACTIVE_NOT;
2125 trg.active = ACTIVE_ACTIVE;
2127 trg.active = os.cnt;
2133 void spawnfunc_relay_activate()
2135 self.cnt = ACTIVE_ACTIVE;
2136 self.use = relay_activators_use;
2139 void spawnfunc_relay_deactivate()
2141 self.cnt = ACTIVE_NOT;
2142 self.use = relay_activators_use;
2145 void spawnfunc_relay_activatetoggle()
2147 self.cnt = ACTIVE_TOGGLE;
2148 self.use = relay_activators_use;
2151 .string chmap, gametype;
2152 void spawnfunc_target_changelevel_use()
2154 if(self.gametype != "")
2155 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2157 if (self.chmap == "")
2158 localcmd("endmatch\n");
2160 localcmd(strcat("changelevel ", self.chmap, "\n"));
2163 void spawnfunc_target_changelevel()
2165 self.use = spawnfunc_target_changelevel_use;