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 (activator.classname == "player" && self.message != "")
66 if(clienttype(activator) == CLIENTTYPE_REAL)
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 float SPAWNFLAG_NOMESSAGE = 1;
141 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 (self.enemy.classname != "player")
169 found_secrets = found_secrets + 1;
170 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
174 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTN_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 = SUB_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 = SUB_Null;
258 self.team = self.team_saved;
261 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
262 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.
263 If "delay" is set, the trigger waits some time after activating before firing.
264 "wait" : Seconds between triggerings. (.2 default)
265 If notouch is set, the trigger is only fired by other entities, not by touching.
266 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
272 set "message" to text string
274 void spawnfunc_trigger_multiple()
276 self.reset = multi_reset;
277 if (self.sounds == 1)
279 precache_sound ("misc/secret.wav");
280 self.noise = "misc/secret.wav";
282 else if (self.sounds == 2)
284 precache_sound ("misc/talk.wav");
285 self.noise = "misc/talk.wav";
287 else if (self.sounds == 3)
289 precache_sound ("misc/trigger1.wav");
290 self.noise = "misc/trigger1.wav";
295 else if(self.wait < -1)
297 self.use = multi_use;
301 self.team_saved = self.team;
305 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
306 objerror ("health and notouch don't make sense\n");
307 self.max_health = self.health;
308 self.event_damage = multi_eventdamage;
309 self.takedamage = DAMAGE_YES;
310 self.solid = SOLID_BBOX;
311 setorigin (self, self.origin); // make sure it links into the world
315 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
317 self.touch = multi_touch;
318 setorigin (self, self.origin); // make sure it links into the world
324 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
325 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
326 "targetname". If "health" is set, the trigger must be killed to activate.
327 If notouch is set, the trigger is only fired by other entities, not by touching.
328 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
329 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.
335 set "message" to text string
337 void spawnfunc_trigger_once()
340 spawnfunc_trigger_multiple();
343 //=============================================================================
345 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
346 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
348 void spawnfunc_trigger_relay()
350 self.use = SUB_UseTargets;
351 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
356 self.think = SUB_UseTargets;
357 self.nextthink = self.wait;
362 self.think = SUB_Null;
365 void spawnfunc_trigger_delay()
370 self.use = delay_use;
371 self.reset = delay_reset;
374 //=============================================================================
379 self.count = self.count - 1;
385 if (activator.classname == "player"
386 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
389 centerprint (activator, "There are more to go...");
390 else if (self.count == 3)
391 centerprint (activator, "Only 3 more to go...");
392 else if (self.count == 2)
393 centerprint (activator, "Only 2 more to go...");
395 centerprint (activator, "Only 1 more to go...");
400 if (activator.classname == "player"
401 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
402 centerprint(activator, "Sequence completed!");
403 self.enemy = activator;
409 self.count = self.cnt;
413 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
414 Acts as an intermediary for an action that takes multiple inputs.
416 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
418 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
420 void spawnfunc_trigger_counter()
425 self.cnt = self.count;
427 self.use = counter_use;
428 self.reset = counter_reset;
431 void trigger_hurt_use()
433 if(activator.classname == "player")
434 self.enemy = activator;
436 self.enemy = world; // let's just destroy it, if taking over is too much work
439 .float triggerhurttime;
440 void trigger_hurt_touch()
442 if (self.active != ACTIVE_ACTIVE)
446 if((self.spawnflags & 4 == 0) == (self.team != other.team))
449 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
450 if (other.iscreature)
452 if (other.takedamage)
453 if (other.triggerhurttime < time)
456 other.triggerhurttime = time + 1;
460 if(own.classname != "player")
463 self.enemy = world; // I still hate you all
466 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
473 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
476 other.pain_finished = min(other.pain_finished, time + 2);
478 else if (other.classname == "rune") // reset runes
481 other.nextthink = min(other.nextthink, time + 1);
489 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
490 Any object touching this will be hurt
491 set dmg to damage amount
494 .entity trigger_hurt_next;
495 entity trigger_hurt_last;
496 entity trigger_hurt_first;
497 void spawnfunc_trigger_hurt()
500 self.active = ACTIVE_ACTIVE;
501 self.touch = trigger_hurt_touch;
502 self.use = trigger_hurt_use;
503 self.enemy = world; // I hate you all
507 self.message = "was in the wrong place";
509 self.message2 = "was thrown into a world of hurt by";
510 // self.message = "someone like %s always gets wrongplaced";
512 if(!trigger_hurt_first)
513 trigger_hurt_first = self;
514 if(trigger_hurt_last)
515 trigger_hurt_last.trigger_hurt_next = self;
516 trigger_hurt_last = self;
519 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
523 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
524 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
530 //////////////////////////////////////////////////////////////
534 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
536 //////////////////////////////////////////////////////////////
538 .float triggerhealtime;
539 void trigger_heal_touch()
541 if (self.active != ACTIVE_ACTIVE)
544 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
545 if (other.iscreature)
547 if (other.takedamage)
549 if (other.triggerhealtime < time)
552 other.triggerhealtime = time + 1;
554 if (other.health < self.max_health)
556 other.health = min(other.health + self.health, self.max_health);
557 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
558 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
564 void spawnfunc_trigger_heal()
566 self.active = ACTIVE_ACTIVE;
569 self.touch = trigger_heal_touch;
572 if (!self.max_health)
573 self.max_health = 200; //Max health topoff for field
575 self.noise = "misc/mediumhealth.wav";
576 precache_sound(self.noise);
580 //////////////////////////////////////////////////////////////
586 //////////////////////////////////////////////////////////////
588 .entity trigger_gravity_check;
589 void trigger_gravity_remove(entity own)
591 if(own.trigger_gravity_check.owner == own)
593 UpdateCSQCProjectile(own);
594 own.gravity = own.trigger_gravity_check.gravity;
595 remove(own.trigger_gravity_check);
598 backtrace("Removing a trigger_gravity_check with no valid owner");
599 own.trigger_gravity_check = world;
601 void trigger_gravity_check_think()
603 // This spawns when a player enters the gravity zone and checks if he left.
604 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
605 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
608 if(self.owner.trigger_gravity_check == self)
609 trigger_gravity_remove(self.owner);
617 self.nextthink = time;
621 void trigger_gravity_use()
623 self.state = !self.state;
626 void trigger_gravity_touch()
630 if(self.state != TRUE)
637 if not(self.spawnflags & 1)
639 if(other.trigger_gravity_check)
641 if(self == other.trigger_gravity_check.enemy)
644 other.trigger_gravity_check.count = 2; // gravity one more frame...
649 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
650 trigger_gravity_remove(other);
654 other.trigger_gravity_check = spawn();
655 other.trigger_gravity_check.enemy = self;
656 other.trigger_gravity_check.owner = other;
657 other.trigger_gravity_check.gravity = other.gravity;
658 other.trigger_gravity_check.think = trigger_gravity_check_think;
659 other.trigger_gravity_check.nextthink = time;
660 other.trigger_gravity_check.count = 2;
665 if (other.gravity != g)
669 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
670 UpdateCSQCProjectile(self.owner);
674 void spawnfunc_trigger_gravity()
676 if(self.gravity == 1)
680 self.touch = trigger_gravity_touch;
682 precache_sound(self.noise);
687 self.use = trigger_gravity_use;
688 if(self.spawnflags & 2)
693 //=============================================================================
695 // TODO add a way to do looped sounds with sound(); then complete this entity
696 .float volume, atten;
697 void target_speaker_use_off();
698 void target_speaker_use_activator()
700 if(clienttype(activator) != CLIENTTYPE_REAL)
703 if(substring(self.noise, 0, 1) == "*")
706 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
707 if(GetPlayerSoundSampleField_notFound)
708 snd = "misc/null.wav";
709 else if(activator.sample == "")
710 snd = "misc/null.wav";
713 tokenize_console(activator.sample);
717 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
719 snd = strcat(argv(0), ".wav"); // randomization
724 msg_entity = activator;
725 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
727 void target_speaker_use_on()
730 if(substring(self.noise, 0, 1) == "*")
733 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
734 if(GetPlayerSoundSampleField_notFound)
735 snd = "misc/null.wav";
736 else if(activator.sample == "")
737 snd = "misc/null.wav";
740 tokenize_console(activator.sample);
744 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
746 snd = strcat(argv(0), ".wav"); // randomization
751 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
752 if(self.spawnflags & 3)
753 self.use = target_speaker_use_off;
755 void target_speaker_use_off()
757 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
758 self.use = target_speaker_use_on;
760 void target_speaker_reset()
762 if(self.spawnflags & 1) // LOOPED_ON
764 if(self.use == target_speaker_use_on)
765 target_speaker_use_on();
767 else if(self.spawnflags & 2)
769 if(self.use == target_speaker_use_off)
770 target_speaker_use_off();
774 void spawnfunc_target_speaker()
776 // TODO: "*" prefix to sound file name
777 // TODO: wait and random (just, HOW? random is not a field)
779 precache_sound (self.noise);
781 if(!self.atten && !(self.spawnflags & 4))
784 self.atten = ATTN_NORM;
786 self.atten = ATTN_STATIC;
788 else if(self.atten < 0)
796 if(self.spawnflags & 8) // ACTIVATOR
797 self.use = target_speaker_use_activator;
798 else if(self.spawnflags & 1) // LOOPED_ON
800 target_speaker_use_on();
801 self.reset = target_speaker_reset;
803 else if(self.spawnflags & 2) // LOOPED_OFF
805 self.use = target_speaker_use_on;
806 self.reset = target_speaker_reset;
809 self.use = target_speaker_use_on;
811 else if(self.spawnflags & 1) // LOOPED_ON
813 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
816 else if(self.spawnflags & 2) // LOOPED_OFF
818 objerror("This sound entity can never be activated");
822 // Quake/Nexuiz fallback
823 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
829 void spawnfunc_func_stardust() {
830 self.effects = EF_STARDUST;
834 .float bgmscriptattack;
835 .float bgmscriptdecay;
836 .float bgmscriptsustain;
837 .float bgmscriptrelease;
838 float pointparticles_SendEntity(entity to, float fl)
840 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
842 // optional features to save space
844 if(self.spawnflags & 2)
845 fl |= 0x10; // absolute count on toggle-on
846 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
847 fl |= 0x20; // 4 bytes - saves CPU
848 if(self.waterlevel || self.count != 1)
849 fl |= 0x40; // 4 bytes - obscure features almost never used
850 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
851 fl |= 0x80; // 14 bytes - saves lots of space
853 WriteByte(MSG_ENTITY, fl);
857 WriteCoord(MSG_ENTITY, self.impulse);
859 WriteCoord(MSG_ENTITY, 0); // off
863 WriteCoord(MSG_ENTITY, self.origin_x);
864 WriteCoord(MSG_ENTITY, self.origin_y);
865 WriteCoord(MSG_ENTITY, self.origin_z);
869 if(self.model != "null")
871 WriteShort(MSG_ENTITY, self.modelindex);
874 WriteCoord(MSG_ENTITY, self.mins_x);
875 WriteCoord(MSG_ENTITY, self.mins_y);
876 WriteCoord(MSG_ENTITY, self.mins_z);
877 WriteCoord(MSG_ENTITY, self.maxs_x);
878 WriteCoord(MSG_ENTITY, self.maxs_y);
879 WriteCoord(MSG_ENTITY, self.maxs_z);
884 WriteShort(MSG_ENTITY, 0);
887 WriteCoord(MSG_ENTITY, self.maxs_x);
888 WriteCoord(MSG_ENTITY, self.maxs_y);
889 WriteCoord(MSG_ENTITY, self.maxs_z);
892 WriteShort(MSG_ENTITY, self.cnt);
895 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
896 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
900 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
901 WriteByte(MSG_ENTITY, self.count * 16.0);
903 WriteString(MSG_ENTITY, self.noise);
906 WriteByte(MSG_ENTITY, floor(self.atten * 64));
907 WriteByte(MSG_ENTITY, floor(self.volume * 255));
909 WriteString(MSG_ENTITY, self.bgmscript);
910 if(self.bgmscript != "")
912 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
913 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
914 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
915 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
921 void pointparticles_use()
923 self.state = !self.state;
927 void pointparticles_think()
929 if(self.origin != self.oldorigin)
932 self.oldorigin = self.origin;
934 self.nextthink = time;
937 void pointparticles_reset()
939 if(self.spawnflags & 1)
945 void spawnfunc_func_pointparticles()
948 setmodel(self, self.model);
950 precache_sound (self.noise);
952 if(!self.bgmscriptsustain)
953 self.bgmscriptsustain = 1;
954 else if(self.bgmscriptsustain < 0)
955 self.bgmscriptsustain = 0;
958 self.atten = ATTN_NORM;
959 else if(self.atten < 0)
970 setorigin(self, self.origin + self.mins);
971 setsize(self, '0 0 0', self.maxs - self.mins);
974 self.cnt = particleeffectnum(self.mdl);
976 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
980 self.use = pointparticles_use;
981 self.reset = pointparticles_reset;
986 self.think = pointparticles_think;
987 self.nextthink = time;
990 void spawnfunc_func_sparks()
992 // self.cnt is the amount of sparks that one burst will spawn
994 self.cnt = 25.0; // nice default value
997 // self.wait is the probability that a sparkthink will spawn a spark shower
998 // range: 0 - 1, but 0 makes little sense, so...
999 if(self.wait < 0.05) {
1000 self.wait = 0.25; // nice default value
1003 self.count = self.cnt;
1004 self.mins = '0 0 0';
1005 self.maxs = '0 0 0';
1006 self.velocity = '0 0 -1';
1007 self.mdl = "TE_SPARK";
1008 self.impulse = 10 * self.wait; // by default 2.5/sec
1010 self.cnt = 0; // use mdl
1012 spawnfunc_func_pointparticles();
1015 float rainsnow_SendEntity(entity to, float sf)
1017 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1018 WriteByte(MSG_ENTITY, self.state);
1019 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1020 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1021 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1022 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1023 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1024 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1025 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1026 WriteShort(MSG_ENTITY, self.count);
1027 WriteByte(MSG_ENTITY, self.cnt);
1031 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1032 This is an invisible area like a trigger, which rain falls inside of.
1036 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1038 sets color of rain (default 12 - white)
1040 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1042 void spawnfunc_func_rain()
1044 self.dest = self.velocity;
1045 self.velocity = '0 0 0';
1047 self.dest = '0 0 -700';
1048 self.angles = '0 0 0';
1049 self.movetype = MOVETYPE_NONE;
1050 self.solid = SOLID_NOT;
1051 SetBrushEntityModel();
1056 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1059 if(self.count > 65535)
1062 self.state = 1; // 1 is rain, 0 is snow
1065 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1069 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1070 This is an invisible area like a trigger, which snow falls inside of.
1074 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1076 sets color of rain (default 12 - white)
1078 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1080 void spawnfunc_func_snow()
1082 self.dest = self.velocity;
1083 self.velocity = '0 0 0';
1085 self.dest = '0 0 -300';
1086 self.angles = '0 0 0';
1087 self.movetype = MOVETYPE_NONE;
1088 self.solid = SOLID_NOT;
1089 SetBrushEntityModel();
1094 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1097 if(self.count > 65535)
1100 self.state = 0; // 1 is rain, 0 is snow
1103 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1107 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1110 void misc_laser_aim()
1115 if(self.spawnflags & 2)
1117 if(self.enemy.origin != self.mangle)
1119 self.mangle = self.enemy.origin;
1120 self.SendFlags |= 2;
1125 a = vectoangles(self.enemy.origin - self.origin);
1127 if(a != self.mangle)
1130 self.SendFlags |= 2;
1136 if(self.angles != self.mangle)
1138 self.mangle = self.angles;
1139 self.SendFlags |= 2;
1142 if(self.origin != self.oldorigin)
1144 self.SendFlags |= 1;
1145 self.oldorigin = self.origin;
1149 void misc_laser_init()
1151 if(self.target != "")
1152 self.enemy = find(world, targetname, self.target);
1156 void misc_laser_think()
1163 self.nextthink = time;
1172 o = self.enemy.origin;
1173 if not(self.spawnflags & 2)
1174 o = self.origin + normalize(o - self.origin) * 32768;
1178 makevectors(self.mangle);
1179 o = self.origin + v_forward * 32768;
1182 if(self.dmg || self.enemy.target != "")
1184 traceline(self.origin, o, MOVE_NORMAL, self);
1187 hitloc = trace_endpos;
1189 if(self.enemy.target != "") // DETECTOR laser
1191 if(trace_ent.iscreature)
1193 self.pusher = hitent;
1200 activator = self.pusher;
1213 activator = self.pusher;
1223 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1225 if(hitent.takedamage)
1226 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1230 float laser_SendEntity(entity to, float fl)
1232 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1233 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1234 if(self.spawnflags & 2)
1238 if(self.scale != 1 || self.modelscale != 1)
1240 if(self.spawnflags & 4)
1242 WriteByte(MSG_ENTITY, fl);
1245 WriteCoord(MSG_ENTITY, self.origin_x);
1246 WriteCoord(MSG_ENTITY, self.origin_y);
1247 WriteCoord(MSG_ENTITY, self.origin_z);
1251 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1252 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1253 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1255 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1258 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1259 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1261 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1262 WriteShort(MSG_ENTITY, self.cnt + 1);
1268 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1269 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1270 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1274 WriteAngle(MSG_ENTITY, self.mangle_x);
1275 WriteAngle(MSG_ENTITY, self.mangle_y);
1279 WriteByte(MSG_ENTITY, self.state);
1283 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1284 Any object touching the beam will be hurt
1287 spawnfunc_target_position where the laser ends
1289 name of beam end effect to use
1291 color of the beam (default: red)
1293 damage per second (-1 for a laser that kills immediately)
1297 self.state = !self.state;
1298 self.SendFlags |= 4;
1304 if(self.spawnflags & 1)
1310 void spawnfunc_misc_laser()
1314 if(self.mdl == "none")
1318 self.cnt = particleeffectnum(self.mdl);
1321 self.cnt = particleeffectnum("laser_deadly");
1327 self.cnt = particleeffectnum("laser_deadly");
1334 if(self.colormod == '0 0 0')
1336 self.colormod = '1 0 0';
1338 self.message = "saw the light";
1340 self.message2 = "was pushed into a laser by";
1343 if(!self.modelscale)
1344 self.modelscale = 1;
1345 else if(self.modelscale < 0)
1346 self.modelscale = 0;
1347 self.think = misc_laser_think;
1348 self.nextthink = time;
1349 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1351 self.mangle = self.angles;
1353 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1357 self.reset = laser_reset;
1359 self.use = laser_use;
1365 // tZorks trigger impulse / gravity
1369 .float lastpushtime;
1371 // targeted (directional) mode
1372 void trigger_impulse_touch1()
1375 float pushdeltatime;
1378 if (self.active != ACTIVE_ACTIVE)
1381 if (!isPushable(other))
1386 targ = find(world, targetname, self.target);
1389 objerror("trigger_force without a (valid) .target!\n");
1394 str = min(self.radius, vlen(self.origin - other.origin));
1396 if(self.falloff == 1)
1397 str = (str / self.radius) * self.strength;
1398 else if(self.falloff == 2)
1399 str = (1 - (str / self.radius)) * self.strength;
1401 str = self.strength;
1403 pushdeltatime = time - other.lastpushtime;
1404 if (pushdeltatime > 0.15) pushdeltatime = 0;
1405 other.lastpushtime = time;
1406 if(!pushdeltatime) return;
1408 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1409 other.flags &~= FL_ONGROUND;
1410 UpdateCSQCProjectile(other);
1413 // Directionless (accelerator/decelerator) mode
1414 void trigger_impulse_touch2()
1416 float pushdeltatime;
1418 if (self.active != ACTIVE_ACTIVE)
1421 if (!isPushable(other))
1426 pushdeltatime = time - other.lastpushtime;
1427 if (pushdeltatime > 0.15) pushdeltatime = 0;
1428 other.lastpushtime = time;
1429 if(!pushdeltatime) return;
1431 // div0: ticrate independent, 1 = identity (not 20)
1432 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1433 UpdateCSQCProjectile(other);
1436 // Spherical (gravity/repulsor) mode
1437 void trigger_impulse_touch3()
1439 float pushdeltatime;
1442 if (self.active != ACTIVE_ACTIVE)
1445 if (!isPushable(other))
1450 pushdeltatime = time - other.lastpushtime;
1451 if (pushdeltatime > 0.15) pushdeltatime = 0;
1452 other.lastpushtime = time;
1453 if(!pushdeltatime) return;
1455 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1457 str = min(self.radius, vlen(self.origin - other.origin));
1459 if(self.falloff == 1)
1460 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1461 else if(self.falloff == 2)
1462 str = (str / self.radius) * self.strength; // 0 in the inside
1464 str = self.strength;
1466 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1467 UpdateCSQCProjectile(other);
1470 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1471 -------- KEYS --------
1472 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1473 If not, this trigger acts like a damper/accelerator field.
1475 strength : This is how mutch force to add in the direction of .target each second
1476 when .target is set. If not, this is hoe mutch to slow down/accelerate
1477 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1479 radius : If set, act as a spherical device rather then a liniar one.
1481 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1483 -------- NOTES --------
1484 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1485 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1488 void spawnfunc_trigger_impulse()
1490 self.active = ACTIVE_ACTIVE;
1495 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1496 setorigin(self, self.origin);
1497 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1498 self.touch = trigger_impulse_touch3;
1504 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1505 self.touch = trigger_impulse_touch1;
1509 if(!self.strength) self.strength = 0.9;
1510 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1511 self.touch = trigger_impulse_touch2;
1516 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1517 "Flip-flop" trigger gate... lets only every second trigger event through
1521 self.state = !self.state;
1526 void spawnfunc_trigger_flipflop()
1528 if(self.spawnflags & 1)
1530 self.use = flipflop_use;
1531 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1534 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1535 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1539 self.nextthink = time + self.wait;
1540 self.enemy = activator;
1546 void monoflop_fixed_use()
1550 self.nextthink = time + self.wait;
1552 self.enemy = activator;
1556 void monoflop_think()
1559 activator = self.enemy;
1563 void monoflop_reset()
1569 void spawnfunc_trigger_monoflop()
1573 if(self.spawnflags & 1)
1574 self.use = monoflop_fixed_use;
1576 self.use = monoflop_use;
1577 self.think = monoflop_think;
1579 self.reset = monoflop_reset;
1582 void multivibrator_send()
1587 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1589 newstate = (time < cyclestart + self.wait);
1592 if(self.state != newstate)
1594 self.state = newstate;
1597 self.nextthink = cyclestart + self.wait + 0.01;
1599 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1602 void multivibrator_toggle()
1604 if(self.nextthink == 0)
1606 multivibrator_send();
1619 void multivibrator_reset()
1621 if(!(self.spawnflags & 1))
1622 self.nextthink = 0; // wait for a trigger event
1624 self.nextthink = max(1, time);
1627 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1628 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1629 -------- KEYS --------
1630 target: trigger all entities with this targetname when it goes off
1631 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1632 phase: offset of the timing
1633 wait: "on" cycle time (default: 1)
1634 respawntime: "off" cycle time (default: same as wait)
1635 -------- SPAWNFLAGS --------
1636 START_ON: assume it is already turned on (when targeted)
1638 void spawnfunc_trigger_multivibrator()
1642 if(!self.respawntime)
1643 self.respawntime = self.wait;
1646 self.use = multivibrator_toggle;
1647 self.think = multivibrator_send;
1648 self.nextthink = max(1, time);
1651 multivibrator_reset();
1660 if(self.killtarget != "")
1661 src = find(world, targetname, self.killtarget);
1662 if(self.target != "")
1663 dst = find(world, targetname, self.target);
1667 objerror("follow: could not find target/killtarget");
1673 // already done :P entity must stay
1677 else if(!src || !dst)
1679 objerror("follow: could not find target/killtarget");
1682 else if(self.spawnflags & 1)
1685 if(self.spawnflags & 2)
1687 setattachment(dst, src, self.message);
1691 attach_sameorigin(dst, src, self.message);
1694 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1699 if(self.spawnflags & 2)
1701 dst.movetype = MOVETYPE_FOLLOW;
1703 // dst.punchangle = '0 0 0'; // keep unchanged
1704 dst.view_ofs = dst.origin;
1705 dst.v_angle = dst.angles;
1709 follow_sameorigin(dst, src);
1716 void spawnfunc_misc_follow()
1718 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1723 void gamestart_use() {
1729 void spawnfunc_trigger_gamestart() {
1730 self.use = gamestart_use;
1731 self.reset2 = spawnfunc_trigger_gamestart;
1735 self.think = self.use;
1736 self.nextthink = game_starttime + self.wait;
1739 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1745 .entity voicescript; // attached voice script
1746 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1747 .float voicescript_nextthink; // time to play next voice
1748 .float voicescript_voiceend; // time when this voice ends
1750 void target_voicescript_clear(entity pl)
1752 pl.voicescript = world;
1755 void target_voicescript_use()
1757 if(activator.voicescript != self)
1759 activator.voicescript = self;
1760 activator.voicescript_index = 0;
1761 activator.voicescript_nextthink = time + self.delay;
1765 void target_voicescript_next(entity pl)
1770 vs = pl.voicescript;
1773 if(vs.message == "")
1775 if(pl.classname != "player")
1780 if(time >= pl.voicescript_voiceend)
1782 if(time >= pl.voicescript_nextthink)
1784 // get the next voice...
1785 n = tokenize_console(vs.message);
1787 if(pl.voicescript_index < vs.cnt)
1788 i = pl.voicescript_index * 2;
1789 else if(n > vs.cnt * 2)
1790 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1796 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1797 dt = stof(argv(i + 1));
1800 pl.voicescript_voiceend = time + dt;
1801 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1805 pl.voicescript_voiceend = time - dt;
1806 pl.voicescript_nextthink = pl.voicescript_voiceend;
1809 pl.voicescript_index += 1;
1813 pl.voicescript = world; // stop trying then
1819 void spawnfunc_target_voicescript()
1821 // netname: directory of the sound files
1822 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1823 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1824 // Here, a - in front of the duration means that no delay is to be
1825 // added after this message
1826 // wait: average time between messages
1827 // delay: initial delay before the first message
1830 self.use = target_voicescript_use;
1832 n = tokenize_console(self.message);
1834 for(i = 0; i+1 < n; i += 2)
1841 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1847 void trigger_relay_teamcheck_use()
1851 if(self.spawnflags & 2)
1853 if(activator.team != self.team)
1858 if(activator.team == self.team)
1864 if(self.spawnflags & 1)
1869 void trigger_relay_teamcheck_reset()
1871 self.team = self.team_saved;
1874 void spawnfunc_trigger_relay_teamcheck()
1876 self.team_saved = self.team;
1877 self.use = trigger_relay_teamcheck_use;
1878 self.reset = trigger_relay_teamcheck_reset;
1883 void trigger_disablerelay_use()
1890 for(e = world; (e = find(e, targetname, self.target)); )
1892 if(e.use == SUB_UseTargets)
1894 e.use = SUB_DontUseTargets;
1897 else if(e.use == SUB_DontUseTargets)
1899 e.use = SUB_UseTargets;
1905 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1908 void spawnfunc_trigger_disablerelay()
1910 self.use = trigger_disablerelay_use;
1913 float magicear_matched;
1914 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1915 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1917 float domatch, dotrigger, matchstart, l;
1922 magicear_matched = FALSE;
1924 dotrigger = ((source.classname == "player") && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1925 domatch = ((ear.spawnflags & 32) || dotrigger);
1932 // we are in TUBA mode!
1933 if not(ear.spawnflags & 256)
1936 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1939 magicear_matched = TRUE;
1946 savemessage = self.message;
1947 self.message = string_null;
1949 self.message = savemessage;
1953 if(ear.netname != "")
1959 if(ear.spawnflags & 256) // ENOTUBA
1964 if(ear.spawnflags & 4)
1970 if(ear.spawnflags & 1)
1973 if(ear.spawnflags & 2)
1976 if(ear.spawnflags & 8)
1981 l = strlen(ear.message);
1983 if(ear.spawnflags & 128)
1986 msg = strdecolorize(msgin);
1988 if(substring(ear.message, 0, 1) == "*")
1990 if(substring(ear.message, -1, 1) == "*")
1993 // as we need multi-replacement here...
1994 s = substring(ear.message, 1, -2);
1996 if(strstrofs(msg, s, 0) >= 0)
1997 matchstart = -2; // we use strreplace on s
2002 s = substring(ear.message, 1, -1);
2004 if(substring(msg, -l, l) == s)
2005 matchstart = strlen(msg) - l;
2010 if(substring(ear.message, -1, 1) == "*")
2013 s = substring(ear.message, 0, -2);
2015 if(substring(msg, 0, l) == s)
2022 if(msg == ear.message)
2027 if(matchstart == -1) // no match
2030 magicear_matched = TRUE;
2037 savemessage = self.message;
2038 self.message = string_null;
2040 self.message = savemessage;
2044 if(ear.spawnflags & 16)
2048 else if(ear.netname != "")
2051 return strreplace(s, ear.netname, msg);
2054 substring(msg, 0, matchstart),
2056 substring(msg, matchstart + l, -1)
2064 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2068 for(ear = magicears; ear; ear = ear.enemy)
2070 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2071 if not(ear.spawnflags & 64)
2072 if(magicear_matched)
2079 void spawnfunc_trigger_magicear()
2081 self.enemy = magicears;
2084 // actually handled in "say" processing
2087 // 2 = ignore teamsay
2089 // 8 = ignore tell to unknown player
2090 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2091 // 32 = perform the replacement even if outside the radius or dead
2092 // 64 = continue replacing/triggering even if this one matched
2093 // 128 = don't decolorize message before matching
2094 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2095 // 512 = tuba notes must be exact right pitch, no transposing
2105 // if set, replacement for the matched text
2107 // "hearing distance"
2111 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2113 self.movedir_x -= 1; // map to tuba instrument numbers
2116 void relay_activators_use()
2122 for(trg = world; (trg = find(trg, targetname, os.target)); )
2126 trg.setactive(os.cnt);
2129 //bprint("Not using setactive\n");
2130 if(os.cnt == ACTIVE_TOGGLE)
2131 if(trg.active == ACTIVE_ACTIVE)
2132 trg.active = ACTIVE_NOT;
2134 trg.active = ACTIVE_ACTIVE;
2136 trg.active = os.cnt;
2142 void spawnfunc_relay_activate()
2144 self.cnt = ACTIVE_ACTIVE;
2145 self.use = relay_activators_use;
2148 void spawnfunc_relay_deactivate()
2150 self.cnt = ACTIVE_NOT;
2151 self.use = relay_activators_use;
2154 void spawnfunc_relay_activatetoggle()
2156 self.cnt = ACTIVE_TOGGLE;
2157 self.use = relay_activators_use;
2160 .string chmap, gametype;
2161 void spawnfunc_target_changelevel_use()
2163 if(self.gametype != "")
2164 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2166 if (self.chmap == "")
2167 localcmd("endmatch\n");
2169 localcmd(strcat("changelevel ", self.chmap, "\n"));
2172 void spawnfunc_target_changelevel()
2174 self.use = spawnfunc_target_changelevel_use;