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 not(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 not(self.spawnflags & 2)
209 if not(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;
462 if not(IS_PLAYER(own))
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 not(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 not(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 FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1104 void misc_laser_aim()
1109 if(self.spawnflags & 2)
1111 if(self.enemy.origin != self.mangle)
1113 self.mangle = self.enemy.origin;
1114 self.SendFlags |= 2;
1119 a = vectoangles(self.enemy.origin - self.origin);
1121 if(a != self.mangle)
1124 self.SendFlags |= 2;
1130 if(self.angles != self.mangle)
1132 self.mangle = self.angles;
1133 self.SendFlags |= 2;
1136 if(self.origin != self.oldorigin)
1138 self.SendFlags |= 1;
1139 self.oldorigin = self.origin;
1143 void misc_laser_init()
1145 if(self.target != "")
1146 self.enemy = find(world, targetname, self.target);
1150 void misc_laser_think()
1157 self.nextthink = time;
1166 o = self.enemy.origin;
1167 if not(self.spawnflags & 2)
1168 o = self.origin + normalize(o - self.origin) * 32768;
1172 makevectors(self.mangle);
1173 o = self.origin + v_forward * 32768;
1176 if(self.dmg || self.enemy.target != "")
1178 traceline(self.origin, o, MOVE_NORMAL, self);
1181 hitloc = trace_endpos;
1183 if(self.enemy.target != "") // DETECTOR laser
1185 if(trace_ent.iscreature)
1187 self.pusher = hitent;
1194 activator = self.pusher;
1207 activator = self.pusher;
1217 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1219 if(hitent.takedamage)
1220 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1224 float laser_SendEntity(entity to, float fl)
1226 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1227 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1228 if(self.spawnflags & 2)
1232 if(self.scale != 1 || self.modelscale != 1)
1234 if(self.spawnflags & 4)
1236 WriteByte(MSG_ENTITY, fl);
1239 WriteCoord(MSG_ENTITY, self.origin_x);
1240 WriteCoord(MSG_ENTITY, self.origin_y);
1241 WriteCoord(MSG_ENTITY, self.origin_z);
1245 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1246 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1247 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1249 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1252 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1253 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1255 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1256 WriteShort(MSG_ENTITY, self.cnt + 1);
1262 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1263 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1264 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1268 WriteAngle(MSG_ENTITY, self.mangle_x);
1269 WriteAngle(MSG_ENTITY, self.mangle_y);
1273 WriteByte(MSG_ENTITY, self.state);
1277 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1278 Any object touching the beam will be hurt
1281 spawnfunc_target_position where the laser ends
1283 name of beam end effect to use
1285 color of the beam (default: red)
1287 damage per second (-1 for a laser that kills immediately)
1291 self.state = !self.state;
1292 self.SendFlags |= 4;
1298 if(self.spawnflags & 1)
1304 void spawnfunc_misc_laser()
1308 if(self.mdl == "none")
1312 self.cnt = particleeffectnum(self.mdl);
1315 self.cnt = particleeffectnum("laser_deadly");
1321 self.cnt = particleeffectnum("laser_deadly");
1328 if(self.colormod == '0 0 0')
1330 self.colormod = '1 0 0';
1331 if(self.message == "")
1332 self.message = "saw the light";
1333 if (self.message2 == "")
1334 self.message2 = "was pushed into a laser by";
1337 if(!self.modelscale)
1338 self.modelscale = 1;
1339 else if(self.modelscale < 0)
1340 self.modelscale = 0;
1341 self.think = misc_laser_think;
1342 self.nextthink = time;
1343 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1345 self.mangle = self.angles;
1347 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1351 self.reset = laser_reset;
1353 self.use = laser_use;
1359 // tZorks trigger impulse / gravity
1363 .float lastpushtime;
1365 // targeted (directional) mode
1366 void trigger_impulse_touch1()
1369 float pushdeltatime;
1372 if (self.active != ACTIVE_ACTIVE)
1375 if (!isPushable(other))
1380 targ = find(world, targetname, self.target);
1383 objerror("trigger_force without a (valid) .target!\n");
1388 str = min(self.radius, vlen(self.origin - other.origin));
1390 if(self.falloff == 1)
1391 str = (str / self.radius) * self.strength;
1392 else if(self.falloff == 2)
1393 str = (1 - (str / self.radius)) * self.strength;
1395 str = self.strength;
1397 pushdeltatime = time - other.lastpushtime;
1398 if (pushdeltatime > 0.15) pushdeltatime = 0;
1399 other.lastpushtime = time;
1400 if(!pushdeltatime) return;
1402 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1403 other.flags &= ~FL_ONGROUND;
1404 UpdateCSQCProjectile(other);
1407 // Directionless (accelerator/decelerator) mode
1408 void trigger_impulse_touch2()
1410 float pushdeltatime;
1412 if (self.active != ACTIVE_ACTIVE)
1415 if (!isPushable(other))
1420 pushdeltatime = time - other.lastpushtime;
1421 if (pushdeltatime > 0.15) pushdeltatime = 0;
1422 other.lastpushtime = time;
1423 if(!pushdeltatime) return;
1425 // div0: ticrate independent, 1 = identity (not 20)
1426 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1427 UpdateCSQCProjectile(other);
1430 // Spherical (gravity/repulsor) mode
1431 void trigger_impulse_touch3()
1433 float pushdeltatime;
1436 if (self.active != ACTIVE_ACTIVE)
1439 if (!isPushable(other))
1444 pushdeltatime = time - other.lastpushtime;
1445 if (pushdeltatime > 0.15) pushdeltatime = 0;
1446 other.lastpushtime = time;
1447 if(!pushdeltatime) return;
1449 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1451 str = min(self.radius, vlen(self.origin - other.origin));
1453 if(self.falloff == 1)
1454 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1455 else if(self.falloff == 2)
1456 str = (str / self.radius) * self.strength; // 0 in the inside
1458 str = self.strength;
1460 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1461 UpdateCSQCProjectile(other);
1464 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1465 -------- KEYS --------
1466 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1467 If not, this trigger acts like a damper/accelerator field.
1469 strength : This is how mutch force to add in the direction of .target each second
1470 when .target is set. If not, this is hoe mutch to slow down/accelerate
1471 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1473 radius : If set, act as a spherical device rather then a liniar one.
1475 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1477 -------- NOTES --------
1478 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1479 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1482 void spawnfunc_trigger_impulse()
1484 self.active = ACTIVE_ACTIVE;
1489 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1490 setorigin(self, self.origin);
1491 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1492 self.touch = trigger_impulse_touch3;
1498 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1499 self.touch = trigger_impulse_touch1;
1503 if(!self.strength) self.strength = 0.9;
1504 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1505 self.touch = trigger_impulse_touch2;
1510 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1511 "Flip-flop" trigger gate... lets only every second trigger event through
1515 self.state = !self.state;
1520 void spawnfunc_trigger_flipflop()
1522 if(self.spawnflags & 1)
1524 self.use = flipflop_use;
1525 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1528 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1529 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1533 self.nextthink = time + self.wait;
1534 self.enemy = activator;
1540 void monoflop_fixed_use()
1544 self.nextthink = time + self.wait;
1546 self.enemy = activator;
1550 void monoflop_think()
1553 activator = self.enemy;
1557 void monoflop_reset()
1563 void spawnfunc_trigger_monoflop()
1567 if(self.spawnflags & 1)
1568 self.use = monoflop_fixed_use;
1570 self.use = monoflop_use;
1571 self.think = monoflop_think;
1573 self.reset = monoflop_reset;
1576 void multivibrator_send()
1581 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1583 newstate = (time < cyclestart + self.wait);
1586 if(self.state != newstate)
1588 self.state = newstate;
1591 self.nextthink = cyclestart + self.wait + 0.01;
1593 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1596 void multivibrator_toggle()
1598 if(self.nextthink == 0)
1600 multivibrator_send();
1613 void multivibrator_reset()
1615 if(!(self.spawnflags & 1))
1616 self.nextthink = 0; // wait for a trigger event
1618 self.nextthink = max(1, time);
1621 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1622 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1623 -------- KEYS --------
1624 target: trigger all entities with this targetname when it goes off
1625 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1626 phase: offset of the timing
1627 wait: "on" cycle time (default: 1)
1628 respawntime: "off" cycle time (default: same as wait)
1629 -------- SPAWNFLAGS --------
1630 START_ON: assume it is already turned on (when targeted)
1632 void spawnfunc_trigger_multivibrator()
1636 if(!self.respawntime)
1637 self.respawntime = self.wait;
1640 self.use = multivibrator_toggle;
1641 self.think = multivibrator_send;
1642 self.nextthink = max(1, time);
1645 multivibrator_reset();
1654 if(self.killtarget != "")
1655 src = find(world, targetname, self.killtarget);
1656 if(self.target != "")
1657 dst = find(world, targetname, self.target);
1661 objerror("follow: could not find target/killtarget");
1667 // already done :P entity must stay
1671 else if(!src || !dst)
1673 objerror("follow: could not find target/killtarget");
1676 else if(self.spawnflags & 1)
1679 if(self.spawnflags & 2)
1681 setattachment(dst, src, self.message);
1685 attach_sameorigin(dst, src, self.message);
1688 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1693 if(self.spawnflags & 2)
1695 dst.movetype = MOVETYPE_FOLLOW;
1697 // dst.punchangle = '0 0 0'; // keep unchanged
1698 dst.view_ofs = dst.origin;
1699 dst.v_angle = dst.angles;
1703 follow_sameorigin(dst, src);
1710 void spawnfunc_misc_follow()
1712 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1717 void gamestart_use() {
1723 void spawnfunc_trigger_gamestart() {
1724 self.use = gamestart_use;
1725 self.reset2 = spawnfunc_trigger_gamestart;
1729 self.think = self.use;
1730 self.nextthink = game_starttime + self.wait;
1733 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1739 .entity voicescript; // attached voice script
1740 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1741 .float voicescript_nextthink; // time to play next voice
1742 .float voicescript_voiceend; // time when this voice ends
1744 void target_voicescript_clear(entity pl)
1746 pl.voicescript = world;
1749 void target_voicescript_use()
1751 if(activator.voicescript != self)
1753 activator.voicescript = self;
1754 activator.voicescript_index = 0;
1755 activator.voicescript_nextthink = time + self.delay;
1759 void target_voicescript_next(entity pl)
1764 vs = pl.voicescript;
1767 if(vs.message == "")
1769 if not(IS_PLAYER(pl))
1774 if(time >= pl.voicescript_voiceend)
1776 if(time >= pl.voicescript_nextthink)
1778 // get the next voice...
1779 n = tokenize_console(vs.message);
1781 if(pl.voicescript_index < vs.cnt)
1782 i = pl.voicescript_index * 2;
1783 else if(n > vs.cnt * 2)
1784 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1790 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1791 dt = stof(argv(i + 1));
1794 pl.voicescript_voiceend = time + dt;
1795 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1799 pl.voicescript_voiceend = time - dt;
1800 pl.voicescript_nextthink = pl.voicescript_voiceend;
1803 pl.voicescript_index += 1;
1807 pl.voicescript = world; // stop trying then
1813 void spawnfunc_target_voicescript()
1815 // netname: directory of the sound files
1816 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1817 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1818 // Here, a - in front of the duration means that no delay is to be
1819 // added after this message
1820 // wait: average time between messages
1821 // delay: initial delay before the first message
1824 self.use = target_voicescript_use;
1826 n = tokenize_console(self.message);
1828 for(i = 0; i+1 < n; i += 2)
1835 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1841 void trigger_relay_teamcheck_use()
1845 if(self.spawnflags & 2)
1847 if(activator.team != self.team)
1852 if(activator.team == self.team)
1858 if(self.spawnflags & 1)
1863 void trigger_relay_teamcheck_reset()
1865 self.team = self.team_saved;
1868 void spawnfunc_trigger_relay_teamcheck()
1870 self.team_saved = self.team;
1871 self.use = trigger_relay_teamcheck_use;
1872 self.reset = trigger_relay_teamcheck_reset;
1877 void trigger_disablerelay_use()
1884 for(e = world; (e = find(e, targetname, self.target)); )
1886 if(e.use == SUB_UseTargets)
1888 e.use = SUB_DontUseTargets;
1891 else if(e.use == SUB_DontUseTargets)
1893 e.use = SUB_UseTargets;
1899 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1902 void spawnfunc_trigger_disablerelay()
1904 self.use = trigger_disablerelay_use;
1907 float magicear_matched;
1908 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1909 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1911 float domatch, dotrigger, matchstart, l;
1916 magicear_matched = FALSE;
1918 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1919 domatch = ((ear.spawnflags & 32) || dotrigger);
1926 // we are in TUBA mode!
1927 if not(ear.spawnflags & 256)
1930 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1933 magicear_matched = TRUE;
1940 savemessage = self.message;
1941 self.message = string_null;
1943 self.message = savemessage;
1947 if(ear.netname != "")
1953 if(ear.spawnflags & 256) // ENOTUBA
1958 if(ear.spawnflags & 4)
1964 if(ear.spawnflags & 1)
1967 if(ear.spawnflags & 2)
1970 if(ear.spawnflags & 8)
1975 l = strlen(ear.message);
1977 if(ear.spawnflags & 128)
1980 msg = strdecolorize(msgin);
1982 if(substring(ear.message, 0, 1) == "*")
1984 if(substring(ear.message, -1, 1) == "*")
1987 // as we need multi-replacement here...
1988 s = substring(ear.message, 1, -2);
1990 if(strstrofs(msg, s, 0) >= 0)
1991 matchstart = -2; // we use strreplace on s
1996 s = substring(ear.message, 1, -1);
1998 if(substring(msg, -l, l) == s)
1999 matchstart = strlen(msg) - l;
2004 if(substring(ear.message, -1, 1) == "*")
2007 s = substring(ear.message, 0, -2);
2009 if(substring(msg, 0, l) == s)
2016 if(msg == ear.message)
2021 if(matchstart == -1) // no match
2024 magicear_matched = TRUE;
2031 savemessage = self.message;
2032 self.message = string_null;
2034 self.message = savemessage;
2038 if(ear.spawnflags & 16)
2042 else if(ear.netname != "")
2045 return strreplace(s, ear.netname, msg);
2048 substring(msg, 0, matchstart),
2050 substring(msg, matchstart + l, -1)
2058 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2062 for(ear = magicears; ear; ear = ear.enemy)
2064 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2065 if not(ear.spawnflags & 64)
2066 if(magicear_matched)
2073 void spawnfunc_trigger_magicear()
2075 self.enemy = magicears;
2078 // actually handled in "say" processing
2081 // 2 = ignore teamsay
2083 // 8 = ignore tell to unknown player
2084 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2085 // 32 = perform the replacement even if outside the radius or dead
2086 // 64 = continue replacing/triggering even if this one matched
2087 // 128 = don't decolorize message before matching
2088 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2089 // 512 = tuba notes must be exact right pitch, no transposing
2099 // if set, replacement for the matched text
2101 // "hearing distance"
2105 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2107 self.movedir_x -= 1; // map to tuba instrument numbers
2110 void relay_activators_use()
2116 for(trg = world; (trg = find(trg, targetname, os.target)); )
2120 trg.setactive(os.cnt);
2123 //bprint("Not using setactive\n");
2124 if(os.cnt == ACTIVE_TOGGLE)
2125 if(trg.active == ACTIVE_ACTIVE)
2126 trg.active = ACTIVE_NOT;
2128 trg.active = ACTIVE_ACTIVE;
2130 trg.active = os.cnt;
2136 void spawnfunc_relay_activate()
2138 self.cnt = ACTIVE_ACTIVE;
2139 self.use = relay_activators_use;
2142 void spawnfunc_relay_deactivate()
2144 self.cnt = ACTIVE_NOT;
2145 self.use = relay_activators_use;
2148 void spawnfunc_relay_activatetoggle()
2150 self.cnt = ACTIVE_TOGGLE;
2151 self.use = relay_activators_use;
2154 .string chmap, gametype;
2155 void spawnfunc_target_changelevel_use()
2157 if(self.gametype != "")
2158 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2160 if (self.chmap == "")
2161 localcmd("endmatch\n");
2163 localcmd(strcat("changelevel ", self.chmap, "\n"));
2166 void spawnfunc_target_changelevel()
2168 self.use = spawnfunc_target_changelevel_use;