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 != "")
65 if(IS_REAL_CLIENT(activator))
67 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_TRIGGER, self.message);
69 play2(activator, "misc/talk.wav");
73 // kill the killtagets
78 for(t = world; (t = find(t, targetname, s)); )
89 if(stemp.target_random)
90 RandomSelection_Init();
92 for(i = 0; i < 4; ++i)
97 case 0: s = stemp.target; break;
98 case 1: s = stemp.target2; break;
99 case 2: s = stemp.target3; break;
100 case 3: s = stemp.target4; break;
104 for(t = world; (t = find(t, targetname, s)); )
107 if(stemp.target_random)
109 RandomSelection_Add(t, 0, string_null, 1, 0);
122 if(stemp.target_random && RandomSelection_chosen_ent)
124 self = RandomSelection_chosen_ent;
136 //=============================================================================
138 const float SPAWNFLAG_NOMESSAGE = 1;
139 const float SPAWNFLAG_NOTOUCH = 1;
141 // the wait time has passed, so set back up for another activation
146 self.health = self.max_health;
147 self.takedamage = DAMAGE_YES;
148 self.solid = SOLID_BBOX;
153 // the trigger was just touched/killed/used
154 // self.enemy should be set to the activator so it can be held through a delay
155 // so wait for the delay time before firing
158 if (self.nextthink > time)
160 return; // allready been triggered
163 if (self.classname == "trigger_secret")
165 if not(IS_PLAYER(self.enemy))
167 found_secrets = found_secrets + 1;
168 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
172 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
174 // don't trigger again until reset
175 self.takedamage = DAMAGE_NO;
177 activator = self.enemy;
178 other = self.goalentity;
183 self.think = multi_wait;
184 self.nextthink = time + self.wait;
186 else if (self.wait == 0)
188 multi_wait(); // waiting finished
191 { // we can't just remove (self) here, because this is a touch function
192 // called wheil C code is looping through area links...
193 self.touch = func_null;
199 self.goalentity = other;
200 self.enemy = activator;
206 if not(self.spawnflags & 2)
207 if not(other.iscreature)
211 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
214 // if the trigger has an angles field, check player's facing direction
215 if (self.movedir != '0 0 0')
217 makevectors (other.angles);
218 if (v_forward * self.movedir < 0)
219 return; // not facing the right way
225 self.goalentity = other;
229 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
231 if (!self.takedamage)
233 if(self.spawnflags & DOOR_NOSPLASH)
234 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
236 self.health = self.health - damage;
237 if (self.health <= 0)
239 self.enemy = attacker;
240 self.goalentity = inflictor;
247 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
248 self.touch = multi_touch;
251 self.health = self.max_health;
252 self.takedamage = DAMAGE_YES;
253 self.solid = SOLID_BBOX;
255 self.think = func_null;
257 self.team = self.team_saved;
260 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
261 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.
262 If "delay" is set, the trigger waits some time after activating before firing.
263 "wait" : Seconds between triggerings. (.2 default)
264 If notouch is set, the trigger is only fired by other entities, not by touching.
265 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
271 set "message" to text string
273 void spawnfunc_trigger_multiple()
275 self.reset = multi_reset;
276 if (self.sounds == 1)
278 precache_sound ("misc/secret.wav");
279 self.noise = "misc/secret.wav";
281 else if (self.sounds == 2)
283 precache_sound ("misc/talk.wav");
284 self.noise = "misc/talk.wav";
286 else if (self.sounds == 3)
288 precache_sound ("misc/trigger1.wav");
289 self.noise = "misc/trigger1.wav";
294 else if(self.wait < -1)
296 self.use = multi_use;
300 self.team_saved = self.team;
304 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
305 objerror ("health and notouch don't make sense\n");
306 self.max_health = self.health;
307 self.event_damage = multi_eventdamage;
308 self.takedamage = DAMAGE_YES;
309 self.solid = SOLID_BBOX;
310 setorigin (self, self.origin); // make sure it links into the world
314 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
316 self.touch = multi_touch;
317 setorigin (self, self.origin); // make sure it links into the world
323 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
324 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
325 "targetname". If "health" is set, the trigger must be killed to activate.
326 If notouch is set, the trigger is only fired by other entities, not by touching.
327 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
328 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.
334 set "message" to text string
336 void spawnfunc_trigger_once()
339 spawnfunc_trigger_multiple();
342 //=============================================================================
344 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
345 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
347 void spawnfunc_trigger_relay()
349 self.use = SUB_UseTargets;
350 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
355 self.think = SUB_UseTargets;
356 self.nextthink = self.wait;
361 self.think = func_null;
365 void spawnfunc_trigger_delay()
370 self.use = delay_use;
371 self.reset = delay_reset;
374 //=============================================================================
385 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
386 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
388 self.enemy = activator;
393 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
395 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
397 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
403 self.count = self.cnt;
407 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
408 Acts as an intermediary for an action that takes multiple inputs.
410 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
412 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
414 void spawnfunc_trigger_counter()
419 self.cnt = self.count;
421 self.use = counter_use;
422 self.reset = counter_reset;
425 void trigger_hurt_use()
427 if(IS_PLAYER(activator))
428 self.enemy = activator;
430 self.enemy = world; // let's just destroy it, if taking over is too much work
433 .float triggerhurttime;
434 void trigger_hurt_touch()
436 if (self.active != ACTIVE_ACTIVE)
440 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
443 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
444 if (other.iscreature)
446 if (other.takedamage)
447 if (other.triggerhurttime < time)
450 other.triggerhurttime = time + 1;
454 if not(IS_PLAYER(own))
457 self.enemy = world; // I still hate you all
460 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
463 else if(other.damagedbytriggers)
468 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
475 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
476 Any object touching this will be hurt
477 set dmg to damage amount
480 .entity trigger_hurt_next;
481 entity trigger_hurt_last;
482 entity trigger_hurt_first;
483 void spawnfunc_trigger_hurt()
486 self.active = ACTIVE_ACTIVE;
487 self.touch = trigger_hurt_touch;
488 self.use = trigger_hurt_use;
489 self.enemy = world; // I hate you all
492 if (self.message == "")
493 self.message = "was in the wrong place";
494 if (self.message2 == "")
495 self.message2 = "was thrown into a world of hurt by";
496 // self.message = "someone like %s always gets wrongplaced";
498 if(!trigger_hurt_first)
499 trigger_hurt_first = self;
500 if(trigger_hurt_last)
501 trigger_hurt_last.trigger_hurt_next = self;
502 trigger_hurt_last = self;
505 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
509 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
510 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
516 //////////////////////////////////////////////////////////////
520 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
522 //////////////////////////////////////////////////////////////
524 .float triggerhealtime;
525 void trigger_heal_touch()
527 if (self.active != ACTIVE_ACTIVE)
530 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
531 if (other.iscreature)
533 if (other.takedamage)
535 if (other.triggerhealtime < time)
538 other.triggerhealtime = time + 1;
540 if (other.health < self.max_health)
542 other.health = min(other.health + self.health, self.max_health);
543 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
544 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
550 void spawnfunc_trigger_heal()
552 self.active = ACTIVE_ACTIVE;
555 self.touch = trigger_heal_touch;
558 if (!self.max_health)
559 self.max_health = 200; //Max health topoff for field
561 self.noise = "misc/mediumhealth.wav";
562 precache_sound(self.noise);
566 //////////////////////////////////////////////////////////////
572 //////////////////////////////////////////////////////////////
574 .entity trigger_gravity_check;
575 void trigger_gravity_remove(entity own)
577 if(own.trigger_gravity_check.owner == own)
579 UpdateCSQCProjectile(own);
580 own.gravity = own.trigger_gravity_check.gravity;
581 remove(own.trigger_gravity_check);
584 backtrace("Removing a trigger_gravity_check with no valid owner");
585 own.trigger_gravity_check = world;
587 void trigger_gravity_check_think()
589 // This spawns when a player enters the gravity zone and checks if he left.
590 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
591 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
594 if(self.owner.trigger_gravity_check == self)
595 trigger_gravity_remove(self.owner);
603 self.nextthink = time;
607 void trigger_gravity_use()
609 self.state = !self.state;
612 void trigger_gravity_touch()
616 if(self.state != TRUE)
623 if not(self.spawnflags & 1)
625 if(other.trigger_gravity_check)
627 if(self == other.trigger_gravity_check.enemy)
630 other.trigger_gravity_check.count = 2; // gravity one more frame...
635 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
636 trigger_gravity_remove(other);
640 other.trigger_gravity_check = spawn();
641 other.trigger_gravity_check.enemy = self;
642 other.trigger_gravity_check.owner = other;
643 other.trigger_gravity_check.gravity = other.gravity;
644 other.trigger_gravity_check.think = trigger_gravity_check_think;
645 other.trigger_gravity_check.nextthink = time;
646 other.trigger_gravity_check.count = 2;
651 if (other.gravity != g)
655 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
656 UpdateCSQCProjectile(self.owner);
660 void spawnfunc_trigger_gravity()
662 if(self.gravity == 1)
666 self.touch = trigger_gravity_touch;
668 precache_sound(self.noise);
673 self.use = trigger_gravity_use;
674 if(self.spawnflags & 2)
679 //=============================================================================
681 // TODO add a way to do looped sounds with sound(); then complete this entity
682 .float volume, atten;
683 void target_speaker_use_off();
684 void target_speaker_use_activator()
686 if not(IS_REAL_CLIENT(activator))
689 if(substring(self.noise, 0, 1) == "*")
692 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
693 if(GetPlayerSoundSampleField_notFound)
694 snd = "misc/null.wav";
695 else if(activator.sample == "")
696 snd = "misc/null.wav";
699 tokenize_console(activator.sample);
703 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
705 snd = strcat(argv(0), ".wav"); // randomization
710 msg_entity = activator;
711 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
713 void target_speaker_use_on()
716 if(substring(self.noise, 0, 1) == "*")
719 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
720 if(GetPlayerSoundSampleField_notFound)
721 snd = "misc/null.wav";
722 else if(activator.sample == "")
723 snd = "misc/null.wav";
726 tokenize_console(activator.sample);
730 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
732 snd = strcat(argv(0), ".wav"); // randomization
737 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
738 if(self.spawnflags & 3)
739 self.use = target_speaker_use_off;
741 void target_speaker_use_off()
743 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
744 self.use = target_speaker_use_on;
746 void target_speaker_reset()
748 if(self.spawnflags & 1) // LOOPED_ON
750 if(self.use == target_speaker_use_on)
751 target_speaker_use_on();
753 else if(self.spawnflags & 2)
755 if(self.use == target_speaker_use_off)
756 target_speaker_use_off();
760 void spawnfunc_target_speaker()
762 // TODO: "*" prefix to sound file name
763 // TODO: wait and random (just, HOW? random is not a field)
765 precache_sound (self.noise);
767 if(!self.atten && !(self.spawnflags & 4))
770 self.atten = ATTEN_NORM;
772 self.atten = ATTEN_STATIC;
774 else if(self.atten < 0)
782 if(self.spawnflags & 8) // ACTIVATOR
783 self.use = target_speaker_use_activator;
784 else if(self.spawnflags & 1) // LOOPED_ON
786 target_speaker_use_on();
787 self.reset = target_speaker_reset;
789 else if(self.spawnflags & 2) // LOOPED_OFF
791 self.use = target_speaker_use_on;
792 self.reset = target_speaker_reset;
795 self.use = target_speaker_use_on;
797 else if(self.spawnflags & 1) // LOOPED_ON
799 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
802 else if(self.spawnflags & 2) // LOOPED_OFF
804 objerror("This sound entity can never be activated");
808 // Quake/Nexuiz fallback
809 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
815 void spawnfunc_func_stardust() {
816 self.effects = EF_STARDUST;
820 .float bgmscriptattack;
821 .float bgmscriptdecay;
822 .float bgmscriptsustain;
823 .float bgmscriptrelease;
824 float pointparticles_SendEntity(entity to, float fl)
826 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
828 // optional features to save space
830 if(self.spawnflags & 2)
831 fl |= 0x10; // absolute count on toggle-on
832 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
833 fl |= 0x20; // 4 bytes - saves CPU
834 if(self.waterlevel || self.count != 1)
835 fl |= 0x40; // 4 bytes - obscure features almost never used
836 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
837 fl |= 0x80; // 14 bytes - saves lots of space
839 WriteByte(MSG_ENTITY, fl);
843 WriteCoord(MSG_ENTITY, self.impulse);
845 WriteCoord(MSG_ENTITY, 0); // off
849 WriteCoord(MSG_ENTITY, self.origin_x);
850 WriteCoord(MSG_ENTITY, self.origin_y);
851 WriteCoord(MSG_ENTITY, self.origin_z);
855 if(self.model != "null")
857 WriteShort(MSG_ENTITY, self.modelindex);
860 WriteCoord(MSG_ENTITY, self.mins_x);
861 WriteCoord(MSG_ENTITY, self.mins_y);
862 WriteCoord(MSG_ENTITY, self.mins_z);
863 WriteCoord(MSG_ENTITY, self.maxs_x);
864 WriteCoord(MSG_ENTITY, self.maxs_y);
865 WriteCoord(MSG_ENTITY, self.maxs_z);
870 WriteShort(MSG_ENTITY, 0);
873 WriteCoord(MSG_ENTITY, self.maxs_x);
874 WriteCoord(MSG_ENTITY, self.maxs_y);
875 WriteCoord(MSG_ENTITY, self.maxs_z);
878 WriteShort(MSG_ENTITY, self.cnt);
881 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
882 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
886 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
887 WriteByte(MSG_ENTITY, self.count * 16.0);
889 WriteString(MSG_ENTITY, self.noise);
892 WriteByte(MSG_ENTITY, floor(self.atten * 64));
893 WriteByte(MSG_ENTITY, floor(self.volume * 255));
895 WriteString(MSG_ENTITY, self.bgmscript);
896 if(self.bgmscript != "")
898 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
899 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
900 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
901 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
907 void pointparticles_use()
909 self.state = !self.state;
913 void pointparticles_think()
915 if(self.origin != self.oldorigin)
918 self.oldorigin = self.origin;
920 self.nextthink = time;
923 void pointparticles_reset()
925 if(self.spawnflags & 1)
931 void spawnfunc_func_pointparticles()
934 setmodel(self, self.model);
936 precache_sound (self.noise);
938 if(!self.bgmscriptsustain)
939 self.bgmscriptsustain = 1;
940 else if(self.bgmscriptsustain < 0)
941 self.bgmscriptsustain = 0;
944 self.atten = ATTEN_NORM;
945 else if(self.atten < 0)
956 setorigin(self, self.origin + self.mins);
957 setsize(self, '0 0 0', self.maxs - self.mins);
960 self.cnt = particleeffectnum(self.mdl);
962 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
966 self.use = pointparticles_use;
967 self.reset = pointparticles_reset;
972 self.think = pointparticles_think;
973 self.nextthink = time;
976 void spawnfunc_func_sparks()
978 // self.cnt is the amount of sparks that one burst will spawn
980 self.cnt = 25.0; // nice default value
983 // self.wait is the probability that a sparkthink will spawn a spark shower
984 // range: 0 - 1, but 0 makes little sense, so...
985 if(self.wait < 0.05) {
986 self.wait = 0.25; // nice default value
989 self.count = self.cnt;
992 self.velocity = '0 0 -1';
993 self.mdl = "TE_SPARK";
994 self.impulse = 10 * self.wait; // by default 2.5/sec
996 self.cnt = 0; // use mdl
998 spawnfunc_func_pointparticles();
1001 float rainsnow_SendEntity(entity to, float sf)
1003 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1004 WriteByte(MSG_ENTITY, self.state);
1005 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1006 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1007 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1008 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1009 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1010 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1011 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1012 WriteShort(MSG_ENTITY, self.count);
1013 WriteByte(MSG_ENTITY, self.cnt);
1017 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1018 This is an invisible area like a trigger, which rain falls inside of.
1022 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1024 sets color of rain (default 12 - white)
1026 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1028 void spawnfunc_func_rain()
1030 self.dest = self.velocity;
1031 self.velocity = '0 0 0';
1033 self.dest = '0 0 -700';
1034 self.angles = '0 0 0';
1035 self.movetype = MOVETYPE_NONE;
1036 self.solid = SOLID_NOT;
1037 SetBrushEntityModel();
1042 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1045 if(self.count > 65535)
1048 self.state = 1; // 1 is rain, 0 is snow
1051 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1055 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1056 This is an invisible area like a trigger, which snow falls inside of.
1060 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1062 sets color of rain (default 12 - white)
1064 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1066 void spawnfunc_func_snow()
1068 self.dest = self.velocity;
1069 self.velocity = '0 0 0';
1071 self.dest = '0 0 -300';
1072 self.angles = '0 0 0';
1073 self.movetype = MOVETYPE_NONE;
1074 self.solid = SOLID_NOT;
1075 SetBrushEntityModel();
1080 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1083 if(self.count > 65535)
1086 self.state = 0; // 1 is rain, 0 is snow
1089 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1093 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1096 void misc_laser_aim()
1101 if(self.spawnflags & 2)
1103 if(self.enemy.origin != self.mangle)
1105 self.mangle = self.enemy.origin;
1106 self.SendFlags |= 2;
1111 a = vectoangles(self.enemy.origin - self.origin);
1113 if(a != self.mangle)
1116 self.SendFlags |= 2;
1122 if(self.angles != self.mangle)
1124 self.mangle = self.angles;
1125 self.SendFlags |= 2;
1128 if(self.origin != self.oldorigin)
1130 self.SendFlags |= 1;
1131 self.oldorigin = self.origin;
1135 void misc_laser_init()
1137 if(self.target != "")
1138 self.enemy = find(world, targetname, self.target);
1142 void misc_laser_think()
1149 self.nextthink = time;
1158 o = self.enemy.origin;
1159 if not(self.spawnflags & 2)
1160 o = self.origin + normalize(o - self.origin) * 32768;
1164 makevectors(self.mangle);
1165 o = self.origin + v_forward * 32768;
1168 if(self.dmg || self.enemy.target != "")
1170 traceline(self.origin, o, MOVE_NORMAL, self);
1173 hitloc = trace_endpos;
1175 if(self.enemy.target != "") // DETECTOR laser
1177 if(trace_ent.iscreature)
1179 self.pusher = hitent;
1186 activator = self.pusher;
1199 activator = self.pusher;
1209 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1211 if(hitent.takedamage)
1212 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1216 float laser_SendEntity(entity to, float fl)
1218 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1219 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1220 if(self.spawnflags & 2)
1224 if(self.scale != 1 || self.modelscale != 1)
1226 if(self.spawnflags & 4)
1228 WriteByte(MSG_ENTITY, fl);
1231 WriteCoord(MSG_ENTITY, self.origin_x);
1232 WriteCoord(MSG_ENTITY, self.origin_y);
1233 WriteCoord(MSG_ENTITY, self.origin_z);
1237 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1238 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1239 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1241 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1244 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1245 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1247 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1248 WriteShort(MSG_ENTITY, self.cnt + 1);
1254 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1255 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1256 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1260 WriteAngle(MSG_ENTITY, self.mangle_x);
1261 WriteAngle(MSG_ENTITY, self.mangle_y);
1265 WriteByte(MSG_ENTITY, self.state);
1269 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1270 Any object touching the beam will be hurt
1273 spawnfunc_target_position where the laser ends
1275 name of beam end effect to use
1277 color of the beam (default: red)
1279 damage per second (-1 for a laser that kills immediately)
1283 self.state = !self.state;
1284 self.SendFlags |= 4;
1290 if(self.spawnflags & 1)
1296 void spawnfunc_misc_laser()
1300 if(self.mdl == "none")
1304 self.cnt = particleeffectnum(self.mdl);
1307 self.cnt = particleeffectnum("laser_deadly");
1313 self.cnt = particleeffectnum("laser_deadly");
1320 if(self.colormod == '0 0 0')
1322 self.colormod = '1 0 0';
1323 if(self.message == "")
1324 self.message = "saw the light";
1325 if (self.message2 == "")
1326 self.message2 = "was pushed into a laser by";
1329 if(!self.modelscale)
1330 self.modelscale = 1;
1331 else if(self.modelscale < 0)
1332 self.modelscale = 0;
1333 self.think = misc_laser_think;
1334 self.nextthink = time;
1335 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1337 self.mangle = self.angles;
1339 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1343 self.reset = laser_reset;
1345 self.use = laser_use;
1351 // tZorks trigger impulse / gravity
1355 .float lastpushtime;
1357 // targeted (directional) mode
1358 void trigger_impulse_touch1()
1361 float pushdeltatime;
1364 if (self.active != ACTIVE_ACTIVE)
1367 if (!isPushable(other))
1372 targ = find(world, targetname, self.target);
1375 objerror("trigger_force without a (valid) .target!\n");
1380 str = min(self.radius, vlen(self.origin - other.origin));
1382 if(self.falloff == 1)
1383 str = (str / self.radius) * self.strength;
1384 else if(self.falloff == 2)
1385 str = (1 - (str / self.radius)) * self.strength;
1387 str = self.strength;
1389 pushdeltatime = time - other.lastpushtime;
1390 if (pushdeltatime > 0.15) pushdeltatime = 0;
1391 other.lastpushtime = time;
1392 if(!pushdeltatime) return;
1394 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1395 other.flags &= ~FL_ONGROUND;
1396 UpdateCSQCProjectile(other);
1399 // Directionless (accelerator/decelerator) mode
1400 void trigger_impulse_touch2()
1402 float pushdeltatime;
1404 if (self.active != ACTIVE_ACTIVE)
1407 if (!isPushable(other))
1412 pushdeltatime = time - other.lastpushtime;
1413 if (pushdeltatime > 0.15) pushdeltatime = 0;
1414 other.lastpushtime = time;
1415 if(!pushdeltatime) return;
1417 // div0: ticrate independent, 1 = identity (not 20)
1418 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1419 UpdateCSQCProjectile(other);
1422 // Spherical (gravity/repulsor) mode
1423 void trigger_impulse_touch3()
1425 float pushdeltatime;
1428 if (self.active != ACTIVE_ACTIVE)
1431 if (!isPushable(other))
1436 pushdeltatime = time - other.lastpushtime;
1437 if (pushdeltatime > 0.15) pushdeltatime = 0;
1438 other.lastpushtime = time;
1439 if(!pushdeltatime) return;
1441 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1443 str = min(self.radius, vlen(self.origin - other.origin));
1445 if(self.falloff == 1)
1446 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1447 else if(self.falloff == 2)
1448 str = (str / self.radius) * self.strength; // 0 in the inside
1450 str = self.strength;
1452 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1453 UpdateCSQCProjectile(other);
1456 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1457 -------- KEYS --------
1458 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1459 If not, this trigger acts like a damper/accelerator field.
1461 strength : This is how mutch force to add in the direction of .target each second
1462 when .target is set. If not, this is hoe mutch to slow down/accelerate
1463 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1465 radius : If set, act as a spherical device rather then a liniar one.
1467 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1469 -------- NOTES --------
1470 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1471 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1474 void spawnfunc_trigger_impulse()
1476 self.active = ACTIVE_ACTIVE;
1481 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1482 setorigin(self, self.origin);
1483 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1484 self.touch = trigger_impulse_touch3;
1490 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1491 self.touch = trigger_impulse_touch1;
1495 if(!self.strength) self.strength = 0.9;
1496 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1497 self.touch = trigger_impulse_touch2;
1502 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1503 "Flip-flop" trigger gate... lets only every second trigger event through
1507 self.state = !self.state;
1512 void spawnfunc_trigger_flipflop()
1514 if(self.spawnflags & 1)
1516 self.use = flipflop_use;
1517 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1520 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1521 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1525 self.nextthink = time + self.wait;
1526 self.enemy = activator;
1532 void monoflop_fixed_use()
1536 self.nextthink = time + self.wait;
1538 self.enemy = activator;
1542 void monoflop_think()
1545 activator = self.enemy;
1549 void monoflop_reset()
1555 void spawnfunc_trigger_monoflop()
1559 if(self.spawnflags & 1)
1560 self.use = monoflop_fixed_use;
1562 self.use = monoflop_use;
1563 self.think = monoflop_think;
1565 self.reset = monoflop_reset;
1568 void multivibrator_send()
1573 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1575 newstate = (time < cyclestart + self.wait);
1578 if(self.state != newstate)
1580 self.state = newstate;
1583 self.nextthink = cyclestart + self.wait + 0.01;
1585 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1588 void multivibrator_toggle()
1590 if(self.nextthink == 0)
1592 multivibrator_send();
1605 void multivibrator_reset()
1607 if(!(self.spawnflags & 1))
1608 self.nextthink = 0; // wait for a trigger event
1610 self.nextthink = max(1, time);
1613 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1614 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1615 -------- KEYS --------
1616 target: trigger all entities with this targetname when it goes off
1617 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1618 phase: offset of the timing
1619 wait: "on" cycle time (default: 1)
1620 respawntime: "off" cycle time (default: same as wait)
1621 -------- SPAWNFLAGS --------
1622 START_ON: assume it is already turned on (when targeted)
1624 void spawnfunc_trigger_multivibrator()
1628 if(!self.respawntime)
1629 self.respawntime = self.wait;
1632 self.use = multivibrator_toggle;
1633 self.think = multivibrator_send;
1634 self.nextthink = max(1, time);
1637 multivibrator_reset();
1646 if(self.killtarget != "")
1647 src = find(world, targetname, self.killtarget);
1648 if(self.target != "")
1649 dst = find(world, targetname, self.target);
1653 objerror("follow: could not find target/killtarget");
1659 // already done :P entity must stay
1663 else if(!src || !dst)
1665 objerror("follow: could not find target/killtarget");
1668 else if(self.spawnflags & 1)
1671 if(self.spawnflags & 2)
1673 setattachment(dst, src, self.message);
1677 attach_sameorigin(dst, src, self.message);
1680 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1685 if(self.spawnflags & 2)
1687 dst.movetype = MOVETYPE_FOLLOW;
1689 // dst.punchangle = '0 0 0'; // keep unchanged
1690 dst.view_ofs = dst.origin;
1691 dst.v_angle = dst.angles;
1695 follow_sameorigin(dst, src);
1702 void spawnfunc_misc_follow()
1704 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1709 void gamestart_use() {
1715 void spawnfunc_trigger_gamestart() {
1716 self.use = gamestart_use;
1717 self.reset2 = spawnfunc_trigger_gamestart;
1721 self.think = self.use;
1722 self.nextthink = game_starttime + self.wait;
1725 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1731 .entity voicescript; // attached voice script
1732 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1733 .float voicescript_nextthink; // time to play next voice
1734 .float voicescript_voiceend; // time when this voice ends
1736 void target_voicescript_clear(entity pl)
1738 pl.voicescript = world;
1741 void target_voicescript_use()
1743 if(activator.voicescript != self)
1745 activator.voicescript = self;
1746 activator.voicescript_index = 0;
1747 activator.voicescript_nextthink = time + self.delay;
1751 void target_voicescript_next(entity pl)
1756 vs = pl.voicescript;
1759 if(vs.message == "")
1761 if not(IS_PLAYER(pl))
1766 if(time >= pl.voicescript_voiceend)
1768 if(time >= pl.voicescript_nextthink)
1770 // get the next voice...
1771 n = tokenize_console(vs.message);
1773 if(pl.voicescript_index < vs.cnt)
1774 i = pl.voicescript_index * 2;
1775 else if(n > vs.cnt * 2)
1776 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1782 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1783 dt = stof(argv(i + 1));
1786 pl.voicescript_voiceend = time + dt;
1787 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1791 pl.voicescript_voiceend = time - dt;
1792 pl.voicescript_nextthink = pl.voicescript_voiceend;
1795 pl.voicescript_index += 1;
1799 pl.voicescript = world; // stop trying then
1805 void spawnfunc_target_voicescript()
1807 // netname: directory of the sound files
1808 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1809 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1810 // Here, a - in front of the duration means that no delay is to be
1811 // added after this message
1812 // wait: average time between messages
1813 // delay: initial delay before the first message
1816 self.use = target_voicescript_use;
1818 n = tokenize_console(self.message);
1820 for(i = 0; i+1 < n; i += 2)
1827 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1833 void trigger_relay_teamcheck_use()
1837 if(self.spawnflags & 2)
1839 if(activator.team != self.team)
1844 if(activator.team == self.team)
1850 if(self.spawnflags & 1)
1855 void trigger_relay_teamcheck_reset()
1857 self.team = self.team_saved;
1860 void spawnfunc_trigger_relay_teamcheck()
1862 self.team_saved = self.team;
1863 self.use = trigger_relay_teamcheck_use;
1864 self.reset = trigger_relay_teamcheck_reset;
1869 void trigger_disablerelay_use()
1876 for(e = world; (e = find(e, targetname, self.target)); )
1878 if(e.use == SUB_UseTargets)
1880 e.use = SUB_DontUseTargets;
1883 else if(e.use == SUB_DontUseTargets)
1885 e.use = SUB_UseTargets;
1891 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1894 void spawnfunc_trigger_disablerelay()
1896 self.use = trigger_disablerelay_use;
1899 float magicear_matched;
1900 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1901 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1903 float domatch, dotrigger, matchstart, l;
1908 magicear_matched = FALSE;
1910 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1911 domatch = ((ear.spawnflags & 32) || dotrigger);
1918 // we are in TUBA mode!
1919 if not(ear.spawnflags & 256)
1922 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1925 magicear_matched = TRUE;
1932 savemessage = self.message;
1933 self.message = string_null;
1935 self.message = savemessage;
1939 if(ear.netname != "")
1945 if(ear.spawnflags & 256) // ENOTUBA
1950 if(ear.spawnflags & 4)
1956 if(ear.spawnflags & 1)
1959 if(ear.spawnflags & 2)
1962 if(ear.spawnflags & 8)
1967 l = strlen(ear.message);
1969 if(ear.spawnflags & 128)
1972 msg = strdecolorize(msgin);
1974 if(substring(ear.message, 0, 1) == "*")
1976 if(substring(ear.message, -1, 1) == "*")
1979 // as we need multi-replacement here...
1980 s = substring(ear.message, 1, -2);
1982 if(strstrofs(msg, s, 0) >= 0)
1983 matchstart = -2; // we use strreplace on s
1988 s = substring(ear.message, 1, -1);
1990 if(substring(msg, -l, l) == s)
1991 matchstart = strlen(msg) - l;
1996 if(substring(ear.message, -1, 1) == "*")
1999 s = substring(ear.message, 0, -2);
2001 if(substring(msg, 0, l) == s)
2008 if(msg == ear.message)
2013 if(matchstart == -1) // no match
2016 magicear_matched = TRUE;
2023 savemessage = self.message;
2024 self.message = string_null;
2026 self.message = savemessage;
2030 if(ear.spawnflags & 16)
2034 else if(ear.netname != "")
2037 return strreplace(s, ear.netname, msg);
2040 substring(msg, 0, matchstart),
2042 substring(msg, matchstart + l, -1)
2050 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2054 for(ear = magicears; ear; ear = ear.enemy)
2056 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2057 if not(ear.spawnflags & 64)
2058 if(magicear_matched)
2065 void spawnfunc_trigger_magicear()
2067 self.enemy = magicears;
2070 // actually handled in "say" processing
2073 // 2 = ignore teamsay
2075 // 8 = ignore tell to unknown player
2076 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2077 // 32 = perform the replacement even if outside the radius or dead
2078 // 64 = continue replacing/triggering even if this one matched
2079 // 128 = don't decolorize message before matching
2080 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2081 // 512 = tuba notes must be exact right pitch, no transposing
2091 // if set, replacement for the matched text
2093 // "hearing distance"
2097 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2099 self.movedir_x -= 1; // map to tuba instrument numbers
2102 void relay_activators_use()
2108 for(trg = world; (trg = find(trg, targetname, os.target)); )
2112 trg.setactive(os.cnt);
2115 //bprint("Not using setactive\n");
2116 if(os.cnt == ACTIVE_TOGGLE)
2117 if(trg.active == ACTIVE_ACTIVE)
2118 trg.active = ACTIVE_NOT;
2120 trg.active = ACTIVE_ACTIVE;
2122 trg.active = os.cnt;
2128 void spawnfunc_relay_activate()
2130 self.cnt = ACTIVE_ACTIVE;
2131 self.use = relay_activators_use;
2134 void spawnfunc_relay_deactivate()
2136 self.cnt = ACTIVE_NOT;
2137 self.use = relay_activators_use;
2140 void spawnfunc_relay_activatetoggle()
2142 self.cnt = ACTIVE_TOGGLE;
2143 self.use = relay_activators_use;
2146 .string chmap, gametype;
2147 void spawnfunc_target_changelevel_use()
2149 if(self.gametype != "")
2150 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2152 if (self.chmap == "")
2153 localcmd("endmatch\n");
2155 localcmd(strcat("changelevel ", self.chmap, "\n"));
2158 void spawnfunc_target_changelevel()
2160 self.use = spawnfunc_target_changelevel_use;