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;
65 if(IS_PLAYER(activator) && self.message != "")
66 if(IS_REAL_CLIENT(activator))
68 centerprint(activator, self.message);
70 play2(activator, "misc/talk.wav");
74 // kill the killtagets
79 for(t = world; (t = find(t, targetname, s)); )
90 if(stemp.target_random)
91 RandomSelection_Init();
93 for(i = 0; i < 4; ++i)
98 case 0: s = stemp.target; break;
99 case 1: s = stemp.target2; break;
100 case 2: s = stemp.target3; break;
101 case 3: s = stemp.target4; break;
105 for(t = world; (t = find(t, targetname, s)); )
108 if(stemp.target_random)
110 RandomSelection_Add(t, 0, string_null, 1, 0);
123 if(stemp.target_random && RandomSelection_chosen_ent)
125 self = RandomSelection_chosen_ent;
137 //=============================================================================
139 const float SPAWNFLAG_NOMESSAGE = 1;
140 const float SPAWNFLAG_NOTOUCH = 1;
142 // the wait time has passed, so set back up for another activation
147 self.health = self.max_health;
148 self.takedamage = DAMAGE_YES;
149 self.solid = SOLID_BBOX;
154 // the trigger was just touched/killed/used
155 // self.enemy should be set to the activator so it can be held through a delay
156 // so wait for the delay time before firing
159 if (self.nextthink > time)
161 return; // allready been triggered
164 if (self.classname == "trigger_secret")
166 if (!IS_PLAYER(self.enemy))
168 found_secrets = found_secrets + 1;
169 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
173 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
175 // don't trigger again until reset
176 self.takedamage = DAMAGE_NO;
178 activator = self.enemy;
179 other = self.goalentity;
184 self.think = multi_wait;
185 self.nextthink = time + self.wait;
187 else if (self.wait == 0)
189 multi_wait(); // waiting finished
192 { // we can't just remove (self) here, because this is a touch function
193 // called wheil C code is looping through area links...
194 self.touch = func_null;
200 self.goalentity = other;
201 self.enemy = activator;
207 if(!(self.spawnflags & 2))
208 if(!other.iscreature)
212 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
215 // if the trigger has an angles field, check player's facing direction
216 if (self.movedir != '0 0 0')
218 makevectors (other.angles);
219 if (v_forward * self.movedir < 0)
220 return; // not facing the right way
226 self.goalentity = other;
230 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
232 if (!self.takedamage)
234 if(self.spawnflags & DOOR_NOSPLASH)
235 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
237 self.health = self.health - damage;
238 if (self.health <= 0)
240 self.enemy = attacker;
241 self.goalentity = inflictor;
248 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
249 self.touch = multi_touch;
252 self.health = self.max_health;
253 self.takedamage = DAMAGE_YES;
254 self.solid = SOLID_BBOX;
256 self.think = func_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 = func_null;
366 void spawnfunc_trigger_delay()
371 self.use = delay_use;
372 self.reset = delay_reset;
375 //=============================================================================
386 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
387 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
389 self.enemy = activator;
394 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
396 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
398 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
404 self.count = self.cnt;
408 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
409 Acts as an intermediary for an action that takes multiple inputs.
411 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
413 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
415 void spawnfunc_trigger_counter()
420 self.cnt = self.count;
422 self.use = counter_use;
423 self.reset = counter_reset;
426 void trigger_hurt_use()
428 if(IS_PLAYER(activator))
429 self.enemy = activator;
431 self.enemy = world; // let's just destroy it, if taking over is too much work
434 .float triggerhurttime;
435 void trigger_hurt_touch()
437 if (self.active != ACTIVE_ACTIVE)
441 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
444 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
445 if (other.iscreature)
447 if (other.takedamage)
448 if (other.triggerhurttime < time)
451 other.triggerhurttime = time + 1;
458 self.enemy = world; // I still hate you all
461 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
464 else if(other.damagedbytriggers)
469 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
476 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
477 Any object touching this will be hurt
478 set dmg to damage amount
481 .entity trigger_hurt_next;
482 entity trigger_hurt_last;
483 entity trigger_hurt_first;
484 void spawnfunc_trigger_hurt()
487 self.active = ACTIVE_ACTIVE;
488 self.touch = trigger_hurt_touch;
489 self.use = trigger_hurt_use;
490 self.enemy = world; // I hate you all
493 if (self.message == "")
494 self.message = "was in the wrong place";
495 if (self.message2 == "")
496 self.message2 = "was thrown into a world of hurt by";
497 // self.message = "someone like %s always gets wrongplaced";
499 if(!trigger_hurt_first)
500 trigger_hurt_first = self;
501 if(trigger_hurt_last)
502 trigger_hurt_last.trigger_hurt_next = self;
503 trigger_hurt_last = self;
506 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
510 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
511 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
517 //////////////////////////////////////////////////////////////
521 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
523 //////////////////////////////////////////////////////////////
525 .float triggerhealtime;
526 void trigger_heal_touch()
528 if (self.active != ACTIVE_ACTIVE)
531 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
532 if (other.iscreature)
534 if (other.takedamage)
536 if (other.triggerhealtime < time)
539 other.triggerhealtime = time + 1;
541 if (other.health < self.max_health)
543 other.health = min(other.health + self.health, self.max_health);
544 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
545 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
551 void spawnfunc_trigger_heal()
553 self.active = ACTIVE_ACTIVE;
556 self.touch = trigger_heal_touch;
559 if (!self.max_health)
560 self.max_health = 200; //Max health topoff for field
562 self.noise = "misc/mediumhealth.wav";
563 precache_sound(self.noise);
567 //////////////////////////////////////////////////////////////
573 //////////////////////////////////////////////////////////////
575 .entity trigger_gravity_check;
576 void trigger_gravity_remove(entity own)
578 if(own.trigger_gravity_check.owner == own)
580 UpdateCSQCProjectile(own);
581 own.gravity = own.trigger_gravity_check.gravity;
582 remove(own.trigger_gravity_check);
585 backtrace("Removing a trigger_gravity_check with no valid owner");
586 own.trigger_gravity_check = world;
588 void trigger_gravity_check_think()
590 // This spawns when a player enters the gravity zone and checks if he left.
591 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
592 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
595 if(self.owner.trigger_gravity_check == self)
596 trigger_gravity_remove(self.owner);
604 self.nextthink = time;
608 void trigger_gravity_use()
610 self.state = !self.state;
613 void trigger_gravity_touch()
617 if(self.state != TRUE)
624 if (!(self.spawnflags & 1))
626 if(other.trigger_gravity_check)
628 if(self == other.trigger_gravity_check.enemy)
631 other.trigger_gravity_check.count = 2; // gravity one more frame...
636 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
637 trigger_gravity_remove(other);
641 other.trigger_gravity_check = spawn();
642 other.trigger_gravity_check.enemy = self;
643 other.trigger_gravity_check.owner = other;
644 other.trigger_gravity_check.gravity = other.gravity;
645 other.trigger_gravity_check.think = trigger_gravity_check_think;
646 other.trigger_gravity_check.nextthink = time;
647 other.trigger_gravity_check.count = 2;
652 if (other.gravity != g)
656 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
657 UpdateCSQCProjectile(self.owner);
661 void spawnfunc_trigger_gravity()
663 if(self.gravity == 1)
667 self.touch = trigger_gravity_touch;
669 precache_sound(self.noise);
674 self.use = trigger_gravity_use;
675 if(self.spawnflags & 2)
680 //=============================================================================
682 // TODO add a way to do looped sounds with sound(); then complete this entity
683 .float volume, atten;
684 void target_speaker_use_off();
685 void target_speaker_use_activator()
687 if (!IS_REAL_CLIENT(activator))
690 if(substring(self.noise, 0, 1) == "*")
693 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
694 if(GetPlayerSoundSampleField_notFound)
695 snd = "misc/null.wav";
696 else if(activator.sample == "")
697 snd = "misc/null.wav";
700 tokenize_console(activator.sample);
704 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
706 snd = strcat(argv(0), ".wav"); // randomization
711 msg_entity = activator;
712 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
714 void target_speaker_use_on()
717 if(substring(self.noise, 0, 1) == "*")
720 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
721 if(GetPlayerSoundSampleField_notFound)
722 snd = "misc/null.wav";
723 else if(activator.sample == "")
724 snd = "misc/null.wav";
727 tokenize_console(activator.sample);
731 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
733 snd = strcat(argv(0), ".wav"); // randomization
738 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
739 if(self.spawnflags & 3)
740 self.use = target_speaker_use_off;
742 void target_speaker_use_off()
744 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
745 self.use = target_speaker_use_on;
747 void target_speaker_reset()
749 if(self.spawnflags & 1) // LOOPED_ON
751 if(self.use == target_speaker_use_on)
752 target_speaker_use_on();
754 else if(self.spawnflags & 2)
756 if(self.use == target_speaker_use_off)
757 target_speaker_use_off();
761 void spawnfunc_target_speaker()
763 // TODO: "*" prefix to sound file name
764 // TODO: wait and random (just, HOW? random is not a field)
766 precache_sound (self.noise);
768 if(!self.atten && !(self.spawnflags & 4))
771 self.atten = ATTEN_NORM;
773 self.atten = ATTEN_STATIC;
775 else if(self.atten < 0)
783 if(self.spawnflags & 8) // ACTIVATOR
784 self.use = target_speaker_use_activator;
785 else if(self.spawnflags & 1) // LOOPED_ON
787 target_speaker_use_on();
788 self.reset = target_speaker_reset;
790 else if(self.spawnflags & 2) // LOOPED_OFF
792 self.use = target_speaker_use_on;
793 self.reset = target_speaker_reset;
796 self.use = target_speaker_use_on;
798 else if(self.spawnflags & 1) // LOOPED_ON
800 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
803 else if(self.spawnflags & 2) // LOOPED_OFF
805 objerror("This sound entity can never be activated");
809 // Quake/Nexuiz fallback
810 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
816 void spawnfunc_func_stardust() {
817 self.effects = EF_STARDUST;
821 .float bgmscriptattack;
822 .float bgmscriptdecay;
823 .float bgmscriptsustain;
824 .float bgmscriptrelease;
825 float pointparticles_SendEntity(entity to, float fl)
827 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
829 // optional features to save space
831 if(self.spawnflags & 2)
832 fl |= 0x10; // absolute count on toggle-on
833 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
834 fl |= 0x20; // 4 bytes - saves CPU
835 if(self.waterlevel || self.count != 1)
836 fl |= 0x40; // 4 bytes - obscure features almost never used
837 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
838 fl |= 0x80; // 14 bytes - saves lots of space
840 WriteByte(MSG_ENTITY, fl);
844 WriteCoord(MSG_ENTITY, self.impulse);
846 WriteCoord(MSG_ENTITY, 0); // off
850 WriteCoord(MSG_ENTITY, self.origin_x);
851 WriteCoord(MSG_ENTITY, self.origin_y);
852 WriteCoord(MSG_ENTITY, self.origin_z);
856 if(self.model != "null")
858 WriteShort(MSG_ENTITY, self.modelindex);
861 WriteCoord(MSG_ENTITY, self.mins_x);
862 WriteCoord(MSG_ENTITY, self.mins_y);
863 WriteCoord(MSG_ENTITY, self.mins_z);
864 WriteCoord(MSG_ENTITY, self.maxs_x);
865 WriteCoord(MSG_ENTITY, self.maxs_y);
866 WriteCoord(MSG_ENTITY, self.maxs_z);
871 WriteShort(MSG_ENTITY, 0);
874 WriteCoord(MSG_ENTITY, self.maxs_x);
875 WriteCoord(MSG_ENTITY, self.maxs_y);
876 WriteCoord(MSG_ENTITY, self.maxs_z);
879 WriteShort(MSG_ENTITY, self.cnt);
882 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
883 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
887 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
888 WriteByte(MSG_ENTITY, self.count * 16.0);
890 WriteString(MSG_ENTITY, self.noise);
893 WriteByte(MSG_ENTITY, floor(self.atten * 64));
894 WriteByte(MSG_ENTITY, floor(self.volume * 255));
896 WriteString(MSG_ENTITY, self.bgmscript);
897 if(self.bgmscript != "")
899 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
900 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
901 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
902 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
908 void pointparticles_use()
910 self.state = !self.state;
914 void pointparticles_think()
916 if(self.origin != self.oldorigin)
919 self.oldorigin = self.origin;
921 self.nextthink = time;
924 void pointparticles_reset()
926 if(self.spawnflags & 1)
932 void spawnfunc_func_pointparticles()
935 setmodel(self, self.model);
937 precache_sound (self.noise);
939 if(!self.bgmscriptsustain)
940 self.bgmscriptsustain = 1;
941 else if(self.bgmscriptsustain < 0)
942 self.bgmscriptsustain = 0;
945 self.atten = ATTEN_NORM;
946 else if(self.atten < 0)
957 setorigin(self, self.origin + self.mins);
958 setsize(self, '0 0 0', self.maxs - self.mins);
961 self.cnt = particleeffectnum(self.mdl);
963 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
967 self.use = pointparticles_use;
968 self.reset = pointparticles_reset;
973 self.think = pointparticles_think;
974 self.nextthink = time;
977 void spawnfunc_func_sparks()
979 // self.cnt is the amount of sparks that one burst will spawn
981 self.cnt = 25.0; // nice default value
984 // self.wait is the probability that a sparkthink will spawn a spark shower
985 // range: 0 - 1, but 0 makes little sense, so...
986 if(self.wait < 0.05) {
987 self.wait = 0.25; // nice default value
990 self.count = self.cnt;
993 self.velocity = '0 0 -1';
994 self.mdl = "TE_SPARK";
995 self.impulse = 10 * self.wait; // by default 2.5/sec
997 self.cnt = 0; // use mdl
999 spawnfunc_func_pointparticles();
1002 float rainsnow_SendEntity(entity to, float sf)
1004 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1005 WriteByte(MSG_ENTITY, self.state);
1006 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1007 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1008 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1009 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1010 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1011 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1012 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1013 WriteShort(MSG_ENTITY, self.count);
1014 WriteByte(MSG_ENTITY, self.cnt);
1018 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1019 This is an invisible area like a trigger, which rain falls inside of.
1023 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1025 sets color of rain (default 12 - white)
1027 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1029 void spawnfunc_func_rain()
1031 self.dest = self.velocity;
1032 self.velocity = '0 0 0';
1034 self.dest = '0 0 -700';
1035 self.angles = '0 0 0';
1036 self.movetype = MOVETYPE_NONE;
1037 self.solid = SOLID_NOT;
1038 SetBrushEntityModel();
1043 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1046 if(self.count > 65535)
1049 self.state = 1; // 1 is rain, 0 is snow
1052 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1056 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1057 This is an invisible area like a trigger, which snow falls inside of.
1061 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1063 sets color of rain (default 12 - white)
1065 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1067 void spawnfunc_func_snow()
1069 self.dest = self.velocity;
1070 self.velocity = '0 0 0';
1072 self.dest = '0 0 -300';
1073 self.angles = '0 0 0';
1074 self.movetype = MOVETYPE_NONE;
1075 self.solid = SOLID_NOT;
1076 SetBrushEntityModel();
1081 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1084 if(self.count > 65535)
1087 self.state = 0; // 1 is rain, 0 is snow
1090 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1094 void misc_laser_aim()
1099 if(self.spawnflags & 2)
1101 if(self.enemy.origin != self.mangle)
1103 self.mangle = self.enemy.origin;
1104 self.SendFlags |= 2;
1109 a = vectoangles(self.enemy.origin - self.origin);
1111 if(a != self.mangle)
1114 self.SendFlags |= 2;
1120 if(self.angles != self.mangle)
1122 self.mangle = self.angles;
1123 self.SendFlags |= 2;
1126 if(self.origin != self.oldorigin)
1128 self.SendFlags |= 1;
1129 self.oldorigin = self.origin;
1133 void misc_laser_init()
1135 if(self.target != "")
1136 self.enemy = find(world, targetname, self.target);
1140 void misc_laser_think()
1147 self.nextthink = time;
1156 o = self.enemy.origin;
1157 if (!(self.spawnflags & 2))
1158 o = self.origin + normalize(o - self.origin) * 32768;
1162 makevectors(self.mangle);
1163 o = self.origin + v_forward * 32768;
1166 if(self.dmg || self.enemy.target != "")
1168 traceline(self.origin, o, MOVE_NORMAL, self);
1171 hitloc = trace_endpos;
1173 if(self.enemy.target != "") // DETECTOR laser
1175 if(trace_ent.iscreature)
1177 self.pusher = hitent;
1184 activator = self.pusher;
1197 activator = self.pusher;
1207 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1209 if(hitent.takedamage)
1210 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1214 float laser_SendEntity(entity to, float fl)
1216 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1217 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1218 if(self.spawnflags & 2)
1222 if(self.scale != 1 || self.modelscale != 1)
1224 if(self.spawnflags & 4)
1226 WriteByte(MSG_ENTITY, fl);
1229 WriteCoord(MSG_ENTITY, self.origin_x);
1230 WriteCoord(MSG_ENTITY, self.origin_y);
1231 WriteCoord(MSG_ENTITY, self.origin_z);
1235 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1236 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1237 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1239 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1242 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1243 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1245 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1246 WriteShort(MSG_ENTITY, self.cnt + 1);
1252 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1253 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1254 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1258 WriteAngle(MSG_ENTITY, self.mangle_x);
1259 WriteAngle(MSG_ENTITY, self.mangle_y);
1263 WriteByte(MSG_ENTITY, self.state);
1267 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1268 Any object touching the beam will be hurt
1271 spawnfunc_target_position where the laser ends
1273 name of beam end effect to use
1275 color of the beam (default: red)
1277 damage per second (-1 for a laser that kills immediately)
1281 self.state = !self.state;
1282 self.SendFlags |= 4;
1288 if(self.spawnflags & 1)
1294 void spawnfunc_misc_laser()
1298 if(self.mdl == "none")
1302 self.cnt = particleeffectnum(self.mdl);
1305 self.cnt = particleeffectnum("laser_deadly");
1311 self.cnt = particleeffectnum("laser_deadly");
1318 if(self.colormod == '0 0 0')
1320 self.colormod = '1 0 0';
1321 if(self.message == "")
1322 self.message = "saw the light";
1323 if (self.message2 == "")
1324 self.message2 = "was pushed into a laser by";
1327 if(!self.modelscale)
1328 self.modelscale = 1;
1329 else if(self.modelscale < 0)
1330 self.modelscale = 0;
1331 self.think = misc_laser_think;
1332 self.nextthink = time;
1333 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1335 self.mangle = self.angles;
1337 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1341 self.reset = laser_reset;
1343 self.use = laser_use;
1349 // tZorks trigger impulse / gravity
1353 .float lastpushtime;
1355 // targeted (directional) mode
1356 void trigger_impulse_touch1()
1359 float pushdeltatime;
1362 if (self.active != ACTIVE_ACTIVE)
1365 if (!isPushable(other))
1370 targ = find(world, targetname, self.target);
1373 objerror("trigger_force without a (valid) .target!\n");
1378 str = min(self.radius, vlen(self.origin - other.origin));
1380 if(self.falloff == 1)
1381 str = (str / self.radius) * self.strength;
1382 else if(self.falloff == 2)
1383 str = (1 - (str / self.radius)) * self.strength;
1385 str = self.strength;
1387 pushdeltatime = time - other.lastpushtime;
1388 if (pushdeltatime > 0.15) pushdeltatime = 0;
1389 other.lastpushtime = time;
1390 if(!pushdeltatime) return;
1392 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1393 other.flags &= ~FL_ONGROUND;
1394 UpdateCSQCProjectile(other);
1397 // Directionless (accelerator/decelerator) mode
1398 void trigger_impulse_touch2()
1400 float pushdeltatime;
1402 if (self.active != ACTIVE_ACTIVE)
1405 if (!isPushable(other))
1410 pushdeltatime = time - other.lastpushtime;
1411 if (pushdeltatime > 0.15) pushdeltatime = 0;
1412 other.lastpushtime = time;
1413 if(!pushdeltatime) return;
1415 // div0: ticrate independent, 1 = identity (not 20)
1416 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1417 UpdateCSQCProjectile(other);
1420 // Spherical (gravity/repulsor) mode
1421 void trigger_impulse_touch3()
1423 float pushdeltatime;
1426 if (self.active != ACTIVE_ACTIVE)
1429 if (!isPushable(other))
1434 pushdeltatime = time - other.lastpushtime;
1435 if (pushdeltatime > 0.15) pushdeltatime = 0;
1436 other.lastpushtime = time;
1437 if(!pushdeltatime) return;
1439 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1441 str = min(self.radius, vlen(self.origin - other.origin));
1443 if(self.falloff == 1)
1444 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1445 else if(self.falloff == 2)
1446 str = (str / self.radius) * self.strength; // 0 in the inside
1448 str = self.strength;
1450 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1451 UpdateCSQCProjectile(other);
1454 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1455 -------- KEYS --------
1456 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1457 If not, this trigger acts like a damper/accelerator field.
1459 strength : This is how mutch force to add in the direction of .target each second
1460 when .target is set. If not, this is hoe mutch to slow down/accelerate
1461 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1463 radius : If set, act as a spherical device rather then a liniar one.
1465 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1467 -------- NOTES --------
1468 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1469 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1472 void spawnfunc_trigger_impulse()
1474 self.active = ACTIVE_ACTIVE;
1479 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1480 setorigin(self, self.origin);
1481 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1482 self.touch = trigger_impulse_touch3;
1488 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1489 self.touch = trigger_impulse_touch1;
1493 if(!self.strength) self.strength = 0.9;
1494 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1495 self.touch = trigger_impulse_touch2;
1500 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1501 "Flip-flop" trigger gate... lets only every second trigger event through
1505 self.state = !self.state;
1510 void spawnfunc_trigger_flipflop()
1512 if(self.spawnflags & 1)
1514 self.use = flipflop_use;
1515 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1518 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1519 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1523 self.nextthink = time + self.wait;
1524 self.enemy = activator;
1530 void monoflop_fixed_use()
1534 self.nextthink = time + self.wait;
1536 self.enemy = activator;
1540 void monoflop_think()
1543 activator = self.enemy;
1547 void monoflop_reset()
1553 void spawnfunc_trigger_monoflop()
1557 if(self.spawnflags & 1)
1558 self.use = monoflop_fixed_use;
1560 self.use = monoflop_use;
1561 self.think = monoflop_think;
1563 self.reset = monoflop_reset;
1566 void multivibrator_send()
1571 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1573 newstate = (time < cyclestart + self.wait);
1576 if(self.state != newstate)
1578 self.state = newstate;
1581 self.nextthink = cyclestart + self.wait + 0.01;
1583 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1586 void multivibrator_toggle()
1588 if(self.nextthink == 0)
1590 multivibrator_send();
1603 void multivibrator_reset()
1605 if(!(self.spawnflags & 1))
1606 self.nextthink = 0; // wait for a trigger event
1608 self.nextthink = max(1, time);
1611 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1612 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1613 -------- KEYS --------
1614 target: trigger all entities with this targetname when it goes off
1615 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1616 phase: offset of the timing
1617 wait: "on" cycle time (default: 1)
1618 respawntime: "off" cycle time (default: same as wait)
1619 -------- SPAWNFLAGS --------
1620 START_ON: assume it is already turned on (when targeted)
1622 void spawnfunc_trigger_multivibrator()
1626 if(!self.respawntime)
1627 self.respawntime = self.wait;
1630 self.use = multivibrator_toggle;
1631 self.think = multivibrator_send;
1632 self.nextthink = max(1, time);
1635 multivibrator_reset();
1644 if(self.killtarget != "")
1645 src = find(world, targetname, self.killtarget);
1646 if(self.target != "")
1647 dst = find(world, targetname, self.target);
1651 objerror("follow: could not find target/killtarget");
1657 // already done :P entity must stay
1661 else if(!src || !dst)
1663 objerror("follow: could not find target/killtarget");
1666 else if(self.spawnflags & 1)
1669 if(self.spawnflags & 2)
1671 setattachment(dst, src, self.message);
1675 attach_sameorigin(dst, src, self.message);
1678 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1683 if(self.spawnflags & 2)
1685 dst.movetype = MOVETYPE_FOLLOW;
1687 // dst.punchangle = '0 0 0'; // keep unchanged
1688 dst.view_ofs = dst.origin;
1689 dst.v_angle = dst.angles;
1693 follow_sameorigin(dst, src);
1700 void spawnfunc_misc_follow()
1702 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1707 void gamestart_use() {
1713 void spawnfunc_trigger_gamestart() {
1714 self.use = gamestart_use;
1715 self.reset2 = spawnfunc_trigger_gamestart;
1719 self.think = self.use;
1720 self.nextthink = game_starttime + self.wait;
1723 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1729 .entity voicescript; // attached voice script
1730 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1731 .float voicescript_nextthink; // time to play next voice
1732 .float voicescript_voiceend; // time when this voice ends
1734 void target_voicescript_clear(entity pl)
1736 pl.voicescript = world;
1739 void target_voicescript_use()
1741 if(activator.voicescript != self)
1743 activator.voicescript = self;
1744 activator.voicescript_index = 0;
1745 activator.voicescript_nextthink = time + self.delay;
1749 void target_voicescript_next(entity pl)
1754 vs = pl.voicescript;
1757 if(vs.message == "")
1764 if(time >= pl.voicescript_voiceend)
1766 if(time >= pl.voicescript_nextthink)
1768 // get the next voice...
1769 n = tokenize_console(vs.message);
1771 if(pl.voicescript_index < vs.cnt)
1772 i = pl.voicescript_index * 2;
1773 else if(n > vs.cnt * 2)
1774 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1780 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1781 dt = stof(argv(i + 1));
1784 pl.voicescript_voiceend = time + dt;
1785 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1789 pl.voicescript_voiceend = time - dt;
1790 pl.voicescript_nextthink = pl.voicescript_voiceend;
1793 pl.voicescript_index += 1;
1797 pl.voicescript = world; // stop trying then
1803 void spawnfunc_target_voicescript()
1805 // netname: directory of the sound files
1806 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1807 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1808 // Here, a - in front of the duration means that no delay is to be
1809 // added after this message
1810 // wait: average time between messages
1811 // delay: initial delay before the first message
1814 self.use = target_voicescript_use;
1816 n = tokenize_console(self.message);
1818 for(i = 0; i+1 < n; i += 2)
1825 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1831 void trigger_relay_teamcheck_use()
1835 if(self.spawnflags & 2)
1837 if(activator.team != self.team)
1842 if(activator.team == self.team)
1848 if(self.spawnflags & 1)
1853 void trigger_relay_teamcheck_reset()
1855 self.team = self.team_saved;
1858 void spawnfunc_trigger_relay_teamcheck()
1860 self.team_saved = self.team;
1861 self.use = trigger_relay_teamcheck_use;
1862 self.reset = trigger_relay_teamcheck_reset;
1867 void trigger_disablerelay_use()
1874 for(e = world; (e = find(e, targetname, self.target)); )
1876 if(e.use == SUB_UseTargets)
1878 e.use = SUB_DontUseTargets;
1881 else if(e.use == SUB_DontUseTargets)
1883 e.use = SUB_UseTargets;
1889 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1892 void spawnfunc_trigger_disablerelay()
1894 self.use = trigger_disablerelay_use;
1897 float magicear_matched;
1898 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1899 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1901 float domatch, dotrigger, matchstart, l;
1906 magicear_matched = FALSE;
1908 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1909 domatch = ((ear.spawnflags & 32) || dotrigger);
1916 // we are in TUBA mode!
1917 if (!(ear.spawnflags & 256))
1920 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1923 magicear_matched = TRUE;
1930 savemessage = self.message;
1931 self.message = string_null;
1933 self.message = savemessage;
1937 if(ear.netname != "")
1943 if(ear.spawnflags & 256) // ENOTUBA
1948 if(ear.spawnflags & 4)
1954 if(ear.spawnflags & 1)
1957 if(ear.spawnflags & 2)
1960 if(ear.spawnflags & 8)
1965 l = strlen(ear.message);
1967 if(ear.spawnflags & 128)
1970 msg = strdecolorize(msgin);
1972 if(substring(ear.message, 0, 1) == "*")
1974 if(substring(ear.message, -1, 1) == "*")
1977 // as we need multi-replacement here...
1978 s = substring(ear.message, 1, -2);
1980 if(strstrofs(msg, s, 0) >= 0)
1981 matchstart = -2; // we use strreplace on s
1986 s = substring(ear.message, 1, -1);
1988 if(substring(msg, -l, l) == s)
1989 matchstart = strlen(msg) - l;
1994 if(substring(ear.message, -1, 1) == "*")
1997 s = substring(ear.message, 0, -2);
1999 if(substring(msg, 0, l) == s)
2006 if(msg == ear.message)
2011 if(matchstart == -1) // no match
2014 magicear_matched = TRUE;
2021 savemessage = self.message;
2022 self.message = string_null;
2024 self.message = savemessage;
2028 if(ear.spawnflags & 16)
2032 else if(ear.netname != "")
2035 return strreplace(s, ear.netname, msg);
2038 substring(msg, 0, matchstart),
2040 substring(msg, matchstart + l, -1)
2048 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2052 for(ear = magicears; ear; ear = ear.enemy)
2054 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2055 if(!(ear.spawnflags & 64))
2056 if(magicear_matched)
2063 void spawnfunc_trigger_magicear()
2065 self.enemy = magicears;
2068 // actually handled in "say" processing
2071 // 2 = ignore teamsay
2073 // 8 = ignore tell to unknown player
2074 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2075 // 32 = perform the replacement even if outside the radius or dead
2076 // 64 = continue replacing/triggering even if this one matched
2077 // 128 = don't decolorize message before matching
2078 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2079 // 512 = tuba notes must be exact right pitch, no transposing
2089 // if set, replacement for the matched text
2091 // "hearing distance"
2095 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2097 self.movedir_x -= 1; // map to tuba instrument numbers
2100 void relay_activators_use()
2106 for(trg = world; (trg = find(trg, targetname, os.target)); )
2110 trg.setactive(os.cnt);
2113 //bprint("Not using setactive\n");
2114 if(os.cnt == ACTIVE_TOGGLE)
2115 if(trg.active == ACTIVE_ACTIVE)
2116 trg.active = ACTIVE_NOT;
2118 trg.active = ACTIVE_ACTIVE;
2120 trg.active = os.cnt;
2126 void spawnfunc_relay_activate()
2128 self.cnt = ACTIVE_ACTIVE;
2129 self.use = relay_activators_use;
2132 void spawnfunc_relay_deactivate()
2134 self.cnt = ACTIVE_NOT;
2135 self.use = relay_activators_use;
2138 void spawnfunc_relay_activatetoggle()
2140 self.cnt = ACTIVE_TOGGLE;
2141 self.use = relay_activators_use;
2144 .string chmap, gametype;
2145 void spawnfunc_target_changelevel_use()
2147 if(self.gametype != "")
2148 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2150 if (self.chmap == "")
2151 localcmd("endmatch\n");
2153 localcmd(strcat("changelevel ", self.chmap, "\n"));
2156 void spawnfunc_target_changelevel()
2158 self.use = spawnfunc_target_changelevel_use;