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 centerprint(activator, 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 (!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 (!(self.spawnflags & 2))
207 if (!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;
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 (!(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 (!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 misc_laser_aim()
1098 if(self.spawnflags & 2)
1100 if(self.enemy.origin != self.mangle)
1102 self.mangle = self.enemy.origin;
1103 self.SendFlags |= 2;
1108 a = vectoangles(self.enemy.origin - self.origin);
1110 if(a != self.mangle)
1113 self.SendFlags |= 2;
1119 if(self.angles != self.mangle)
1121 self.mangle = self.angles;
1122 self.SendFlags |= 2;
1125 if(self.origin != self.oldorigin)
1127 self.SendFlags |= 1;
1128 self.oldorigin = self.origin;
1132 void misc_laser_init()
1134 if(self.target != "")
1135 self.enemy = find(world, targetname, self.target);
1139 void misc_laser_think()
1146 self.nextthink = time;
1155 o = self.enemy.origin;
1156 if (!(self.spawnflags & 2))
1157 o = self.origin + normalize(o - self.origin) * 32768;
1161 makevectors(self.mangle);
1162 o = self.origin + v_forward * 32768;
1165 if(self.dmg || self.enemy.target != "")
1167 traceline(self.origin, o, MOVE_NORMAL, self);
1170 hitloc = trace_endpos;
1172 if(self.enemy.target != "") // DETECTOR laser
1174 if(trace_ent.iscreature)
1176 self.pusher = hitent;
1183 activator = self.pusher;
1196 activator = self.pusher;
1206 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1208 if(hitent.takedamage)
1209 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1213 float laser_SendEntity(entity to, float fl)
1215 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1216 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1217 if(self.spawnflags & 2)
1221 if(self.scale != 1 || self.modelscale != 1)
1223 if(self.spawnflags & 4)
1225 WriteByte(MSG_ENTITY, fl);
1228 WriteCoord(MSG_ENTITY, self.origin_x);
1229 WriteCoord(MSG_ENTITY, self.origin_y);
1230 WriteCoord(MSG_ENTITY, self.origin_z);
1234 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1235 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1236 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1238 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1241 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1242 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1244 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1245 WriteShort(MSG_ENTITY, self.cnt + 1);
1251 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1252 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1253 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1257 WriteAngle(MSG_ENTITY, self.mangle_x);
1258 WriteAngle(MSG_ENTITY, self.mangle_y);
1262 WriteByte(MSG_ENTITY, self.state);
1266 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1267 Any object touching the beam will be hurt
1270 spawnfunc_target_position where the laser ends
1272 name of beam end effect to use
1274 color of the beam (default: red)
1276 damage per second (-1 for a laser that kills immediately)
1280 self.state = !self.state;
1281 self.SendFlags |= 4;
1287 if(self.spawnflags & 1)
1293 void spawnfunc_misc_laser()
1297 if(self.mdl == "none")
1301 self.cnt = particleeffectnum(self.mdl);
1304 self.cnt = particleeffectnum("laser_deadly");
1310 self.cnt = particleeffectnum("laser_deadly");
1317 if(self.colormod == '0 0 0')
1319 self.colormod = '1 0 0';
1320 if(self.message == "")
1321 self.message = "saw the light";
1322 if (self.message2 == "")
1323 self.message2 = "was pushed into a laser by";
1326 if(!self.modelscale)
1327 self.modelscale = 1;
1328 else if(self.modelscale < 0)
1329 self.modelscale = 0;
1330 self.think = misc_laser_think;
1331 self.nextthink = time;
1332 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1334 self.mangle = self.angles;
1336 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1340 self.reset = laser_reset;
1342 self.use = laser_use;
1348 // tZorks trigger impulse / gravity
1352 .float lastpushtime;
1354 // targeted (directional) mode
1355 void trigger_impulse_touch1()
1358 float pushdeltatime;
1361 if (self.active != ACTIVE_ACTIVE)
1364 if (!isPushable(other))
1369 targ = find(world, targetname, self.target);
1372 objerror("trigger_force without a (valid) .target!\n");
1377 str = min(self.radius, vlen(self.origin - other.origin));
1379 if(self.falloff == 1)
1380 str = (str / self.radius) * self.strength;
1381 else if(self.falloff == 2)
1382 str = (1 - (str / self.radius)) * self.strength;
1384 str = self.strength;
1386 pushdeltatime = time - other.lastpushtime;
1387 if (pushdeltatime > 0.15) pushdeltatime = 0;
1388 other.lastpushtime = time;
1389 if(!pushdeltatime) return;
1391 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1392 other.flags &= ~FL_ONGROUND;
1393 UpdateCSQCProjectile(other);
1396 // Directionless (accelerator/decelerator) mode
1397 void trigger_impulse_touch2()
1399 float pushdeltatime;
1401 if (self.active != ACTIVE_ACTIVE)
1404 if (!isPushable(other))
1409 pushdeltatime = time - other.lastpushtime;
1410 if (pushdeltatime > 0.15) pushdeltatime = 0;
1411 other.lastpushtime = time;
1412 if(!pushdeltatime) return;
1414 // div0: ticrate independent, 1 = identity (not 20)
1415 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1416 UpdateCSQCProjectile(other);
1419 // Spherical (gravity/repulsor) mode
1420 void trigger_impulse_touch3()
1422 float pushdeltatime;
1425 if (self.active != ACTIVE_ACTIVE)
1428 if (!isPushable(other))
1433 pushdeltatime = time - other.lastpushtime;
1434 if (pushdeltatime > 0.15) pushdeltatime = 0;
1435 other.lastpushtime = time;
1436 if(!pushdeltatime) return;
1438 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1440 str = min(self.radius, vlen(self.origin - other.origin));
1442 if(self.falloff == 1)
1443 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1444 else if(self.falloff == 2)
1445 str = (str / self.radius) * self.strength; // 0 in the inside
1447 str = self.strength;
1449 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1450 UpdateCSQCProjectile(other);
1453 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1454 -------- KEYS --------
1455 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1456 If not, this trigger acts like a damper/accelerator field.
1458 strength : This is how mutch force to add in the direction of .target each second
1459 when .target is set. If not, this is hoe mutch to slow down/accelerate
1460 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1462 radius : If set, act as a spherical device rather then a liniar one.
1464 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1466 -------- NOTES --------
1467 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1468 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1471 void spawnfunc_trigger_impulse()
1473 self.active = ACTIVE_ACTIVE;
1478 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1479 setorigin(self, self.origin);
1480 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1481 self.touch = trigger_impulse_touch3;
1487 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1488 self.touch = trigger_impulse_touch1;
1492 if(!self.strength) self.strength = 0.9;
1493 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1494 self.touch = trigger_impulse_touch2;
1499 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1500 "Flip-flop" trigger gate... lets only every second trigger event through
1504 self.state = !self.state;
1509 void spawnfunc_trigger_flipflop()
1511 if(self.spawnflags & 1)
1513 self.use = flipflop_use;
1514 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1517 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1518 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1522 self.nextthink = time + self.wait;
1523 self.enemy = activator;
1529 void monoflop_fixed_use()
1533 self.nextthink = time + self.wait;
1535 self.enemy = activator;
1539 void monoflop_think()
1542 activator = self.enemy;
1546 void monoflop_reset()
1552 void spawnfunc_trigger_monoflop()
1556 if(self.spawnflags & 1)
1557 self.use = monoflop_fixed_use;
1559 self.use = monoflop_use;
1560 self.think = monoflop_think;
1562 self.reset = monoflop_reset;
1565 void multivibrator_send()
1570 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1572 newstate = (time < cyclestart + self.wait);
1575 if(self.state != newstate)
1577 self.state = newstate;
1580 self.nextthink = cyclestart + self.wait + 0.01;
1582 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1585 void multivibrator_toggle()
1587 if(self.nextthink == 0)
1589 multivibrator_send();
1602 void multivibrator_reset()
1604 if(!(self.spawnflags & 1))
1605 self.nextthink = 0; // wait for a trigger event
1607 self.nextthink = max(1, time);
1610 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1611 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1612 -------- KEYS --------
1613 target: trigger all entities with this targetname when it goes off
1614 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1615 phase: offset of the timing
1616 wait: "on" cycle time (default: 1)
1617 respawntime: "off" cycle time (default: same as wait)
1618 -------- SPAWNFLAGS --------
1619 START_ON: assume it is already turned on (when targeted)
1621 void spawnfunc_trigger_multivibrator()
1625 if(!self.respawntime)
1626 self.respawntime = self.wait;
1629 self.use = multivibrator_toggle;
1630 self.think = multivibrator_send;
1631 self.nextthink = max(1, time);
1634 multivibrator_reset();
1643 if(self.killtarget != "")
1644 src = find(world, targetname, self.killtarget);
1645 if(self.target != "")
1646 dst = find(world, targetname, self.target);
1650 objerror("follow: could not find target/killtarget");
1656 // already done :P entity must stay
1660 else if(!src || !dst)
1662 objerror("follow: could not find target/killtarget");
1665 else if(self.spawnflags & 1)
1668 if(self.spawnflags & 2)
1670 setattachment(dst, src, self.message);
1674 attach_sameorigin(dst, src, self.message);
1677 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1682 if(self.spawnflags & 2)
1684 dst.movetype = MOVETYPE_FOLLOW;
1686 // dst.punchangle = '0 0 0'; // keep unchanged
1687 dst.view_ofs = dst.origin;
1688 dst.v_angle = dst.angles;
1692 follow_sameorigin(dst, src);
1699 void spawnfunc_misc_follow()
1701 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1706 void gamestart_use() {
1712 void spawnfunc_trigger_gamestart() {
1713 self.use = gamestart_use;
1714 self.reset2 = spawnfunc_trigger_gamestart;
1718 self.think = self.use;
1719 self.nextthink = game_starttime + self.wait;
1722 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1728 .entity voicescript; // attached voice script
1729 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1730 .float voicescript_nextthink; // time to play next voice
1731 .float voicescript_voiceend; // time when this voice ends
1733 void target_voicescript_clear(entity pl)
1735 pl.voicescript = world;
1738 void target_voicescript_use()
1740 if(activator.voicescript != self)
1742 activator.voicescript = self;
1743 activator.voicescript_index = 0;
1744 activator.voicescript_nextthink = time + self.delay;
1748 void target_voicescript_next(entity pl)
1753 vs = pl.voicescript;
1756 if(vs.message == "")
1763 if(time >= pl.voicescript_voiceend)
1765 if(time >= pl.voicescript_nextthink)
1767 // get the next voice...
1768 n = tokenize_console(vs.message);
1770 if(pl.voicescript_index < vs.cnt)
1771 i = pl.voicescript_index * 2;
1772 else if(n > vs.cnt * 2)
1773 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1779 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1780 dt = stof(argv(i + 1));
1783 pl.voicescript_voiceend = time + dt;
1784 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1788 pl.voicescript_voiceend = time - dt;
1789 pl.voicescript_nextthink = pl.voicescript_voiceend;
1792 pl.voicescript_index += 1;
1796 pl.voicescript = world; // stop trying then
1802 void spawnfunc_target_voicescript()
1804 // netname: directory of the sound files
1805 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1806 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1807 // Here, a - in front of the duration means that no delay is to be
1808 // added after this message
1809 // wait: average time between messages
1810 // delay: initial delay before the first message
1813 self.use = target_voicescript_use;
1815 n = tokenize_console(self.message);
1817 for(i = 0; i+1 < n; i += 2)
1824 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1830 void trigger_relay_teamcheck_use()
1834 if(self.spawnflags & 2)
1836 if(activator.team != self.team)
1841 if(activator.team == self.team)
1847 if(self.spawnflags & 1)
1852 void trigger_relay_teamcheck_reset()
1854 self.team = self.team_saved;
1857 void spawnfunc_trigger_relay_teamcheck()
1859 self.team_saved = self.team;
1860 self.use = trigger_relay_teamcheck_use;
1861 self.reset = trigger_relay_teamcheck_reset;
1866 void trigger_disablerelay_use()
1873 for(e = world; (e = find(e, targetname, self.target)); )
1875 if(e.use == SUB_UseTargets)
1877 e.use = SUB_DontUseTargets;
1880 else if(e.use == SUB_DontUseTargets)
1882 e.use = SUB_UseTargets;
1888 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1891 void spawnfunc_trigger_disablerelay()
1893 self.use = trigger_disablerelay_use;
1896 float magicear_matched;
1897 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1898 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1900 float domatch, dotrigger, matchstart, l;
1905 magicear_matched = FALSE;
1907 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1908 domatch = ((ear.spawnflags & 32) || dotrigger);
1915 // we are in TUBA mode!
1916 if (!(ear.spawnflags & 256))
1919 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1922 magicear_matched = TRUE;
1929 savemessage = self.message;
1930 self.message = string_null;
1932 self.message = savemessage;
1936 if(ear.netname != "")
1942 if(ear.spawnflags & 256) // ENOTUBA
1947 if(ear.spawnflags & 4)
1953 if(ear.spawnflags & 1)
1956 if(ear.spawnflags & 2)
1959 if(ear.spawnflags & 8)
1964 l = strlen(ear.message);
1966 if(ear.spawnflags & 128)
1969 msg = strdecolorize(msgin);
1971 if(substring(ear.message, 0, 1) == "*")
1973 if(substring(ear.message, -1, 1) == "*")
1976 // as we need multi-replacement here...
1977 s = substring(ear.message, 1, -2);
1979 if(strstrofs(msg, s, 0) >= 0)
1980 matchstart = -2; // we use strreplace on s
1985 s = substring(ear.message, 1, -1);
1987 if(substring(msg, -l, l) == s)
1988 matchstart = strlen(msg) - l;
1993 if(substring(ear.message, -1, 1) == "*")
1996 s = substring(ear.message, 0, -2);
1998 if(substring(msg, 0, l) == s)
2005 if(msg == ear.message)
2010 if(matchstart == -1) // no match
2013 magicear_matched = TRUE;
2020 savemessage = self.message;
2021 self.message = string_null;
2023 self.message = savemessage;
2027 if(ear.spawnflags & 16)
2031 else if(ear.netname != "")
2034 return strreplace(s, ear.netname, msg);
2037 substring(msg, 0, matchstart),
2039 substring(msg, matchstart + l, -1)
2047 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2051 for(ear = magicears; ear; ear = ear.enemy)
2053 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2054 if (!(ear.spawnflags & 64))
2055 if(magicear_matched)
2062 void spawnfunc_trigger_magicear()
2064 self.enemy = magicears;
2067 // actually handled in "say" processing
2070 // 2 = ignore teamsay
2072 // 8 = ignore tell to unknown player
2073 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2074 // 32 = perform the replacement even if outside the radius or dead
2075 // 64 = continue replacing/triggering even if this one matched
2076 // 128 = don't decolorize message before matching
2077 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2078 // 512 = tuba notes must be exact right pitch, no transposing
2088 // if set, replacement for the matched text
2090 // "hearing distance"
2094 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2096 self.movedir_x -= 1; // map to tuba instrument numbers
2099 void relay_activators_use()
2105 for(trg = world; (trg = find(trg, targetname, os.target)); )
2109 trg.setactive(os.cnt);
2112 //bprint("Not using setactive\n");
2113 if(os.cnt == ACTIVE_TOGGLE)
2114 if(trg.active == ACTIVE_ACTIVE)
2115 trg.active = ACTIVE_NOT;
2117 trg.active = ACTIVE_ACTIVE;
2119 trg.active = os.cnt;
2125 void spawnfunc_relay_activate()
2127 self.cnt = ACTIVE_ACTIVE;
2128 self.use = relay_activators_use;
2131 void spawnfunc_relay_deactivate()
2133 self.cnt = ACTIVE_NOT;
2134 self.use = relay_activators_use;
2137 void spawnfunc_relay_activatetoggle()
2139 self.cnt = ACTIVE_TOGGLE;
2140 self.use = relay_activators_use;
2143 .string chmap, gametype;
2144 void spawnfunc_target_changelevel_use()
2146 if(self.gametype != "")
2147 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2149 if (self.chmap == "")
2150 localcmd("endmatch\n");
2152 localcmd(strcat("changelevel ", self.chmap, "\n"));
2155 void spawnfunc_target_changelevel()
2157 self.use = spawnfunc_target_changelevel_use;