1 void SUB_DontUseTargets()
10 activator = self.enemy;
16 ==============================
19 the global "activator" should be set to the entity that initiated the firing.
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
24 Centerprints any self.message to the activator.
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
32 ==============================
36 entity t, stemp, otemp, act;
45 // create a temp object to fire at a later time
47 t.classname = "DelayedUse";
48 t.nextthink = time + self.delay;
51 t.message = self.message;
52 t.killtarget = self.killtarget;
53 t.target = self.target;
54 t.target2 = self.target2;
55 t.target3 = self.target3;
56 t.target4 = self.target4;
64 if (IS_PLAYER(activator) && self.message != "")
66 if(IS_REAL_CLIENT(activator))
68 centerprint (activator, self.message);
70 play2(activator, "misc/talk.wav");
75 // kill the killtagets
80 for(t = world; (t = find(t, targetname, s)); )
91 if(stemp.target_random)
92 RandomSelection_Init();
94 for(i = 0; i < 4; ++i)
99 case 0: s = stemp.target; break;
100 case 1: s = stemp.target2; break;
101 case 2: s = stemp.target3; break;
102 case 3: s = stemp.target4; break;
106 for(t = world; (t = find(t, targetname, s)); )
109 if(stemp.target_random)
111 RandomSelection_Add(t, 0, string_null, 1, 0);
124 if(stemp.target_random && RandomSelection_chosen_ent)
126 self = RandomSelection_chosen_ent;
138 //=============================================================================
140 float SPAWNFLAG_NOMESSAGE = 1;
141 float SPAWNFLAG_NOTOUCH = 1;
143 // the wait time has passed, so set back up for another activation
148 self.health = self.max_health;
149 self.takedamage = DAMAGE_YES;
150 self.solid = SOLID_BBOX;
155 // the trigger was just touched/killed/used
156 // self.enemy should be set to the activator so it can be held through a delay
157 // so wait for the delay time before firing
160 if (self.nextthink > time)
162 return; // allready been triggered
165 if (self.classname == "trigger_secret")
167 if not(IS_PLAYER(self.enemy))
169 found_secrets = found_secrets + 1;
170 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
174 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
176 // don't trigger again until reset
177 self.takedamage = DAMAGE_NO;
179 activator = self.enemy;
180 other = self.goalentity;
185 self.think = multi_wait;
186 self.nextthink = time + self.wait;
188 else if (self.wait == 0)
190 multi_wait(); // waiting finished
193 { // we can't just remove (self) here, because this is a touch function
194 // called wheil C code is looping through area links...
195 self.touch = func_null;
201 self.goalentity = other;
202 self.enemy = activator;
208 if not(self.spawnflags & 2)
209 if not(other.iscreature)
213 if((self.spawnflags & 4 == 0) == (self.team != other.team))
216 // if the trigger has an angles field, check player's facing direction
217 if (self.movedir != '0 0 0')
219 makevectors (other.angles);
220 if (v_forward * self.movedir < 0)
221 return; // not facing the right way
227 self.goalentity = other;
231 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
233 if (!self.takedamage)
235 if(self.spawnflags & DOOR_NOSPLASH)
236 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
238 self.health = self.health - damage;
239 if (self.health <= 0)
241 self.enemy = attacker;
242 self.goalentity = inflictor;
249 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
250 self.touch = multi_touch;
253 self.health = self.max_health;
254 self.takedamage = DAMAGE_YES;
255 self.solid = SOLID_BBOX;
257 self.think = func_null;
259 self.team = self.team_saved;
262 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
263 Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
264 If "delay" is set, the trigger waits some time after activating before firing.
265 "wait" : Seconds between triggerings. (.2 default)
266 If notouch is set, the trigger is only fired by other entities, not by touching.
267 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
273 set "message" to text string
275 void spawnfunc_trigger_multiple()
277 self.reset = multi_reset;
278 if (self.sounds == 1)
280 precache_sound ("misc/secret.wav");
281 self.noise = "misc/secret.wav";
283 else if (self.sounds == 2)
285 precache_sound ("misc/talk.wav");
286 self.noise = "misc/talk.wav";
288 else if (self.sounds == 3)
290 precache_sound ("misc/trigger1.wav");
291 self.noise = "misc/trigger1.wav";
296 else if(self.wait < -1)
298 self.use = multi_use;
302 self.team_saved = self.team;
306 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
307 objerror ("health and notouch don't make sense\n");
308 self.max_health = self.health;
309 self.event_damage = multi_eventdamage;
310 self.takedamage = DAMAGE_YES;
311 self.solid = SOLID_BBOX;
312 setorigin (self, self.origin); // make sure it links into the world
316 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
318 self.touch = multi_touch;
319 setorigin (self, self.origin); // make sure it links into the world
325 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
326 Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
327 "targetname". If "health" is set, the trigger must be killed to activate.
328 If notouch is set, the trigger is only fired by other entities, not by touching.
329 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
330 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
336 set "message" to text string
338 void spawnfunc_trigger_once()
341 spawnfunc_trigger_multiple();
344 //=============================================================================
346 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
347 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
349 void spawnfunc_trigger_relay()
351 self.use = SUB_UseTargets;
352 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
357 self.think = SUB_UseTargets;
358 self.nextthink = self.wait;
363 self.think = func_null;
367 void spawnfunc_trigger_delay()
372 self.use = delay_use;
373 self.reset = delay_reset;
376 //=============================================================================
381 self.count = self.count - 1;
387 if (IS_PLAYER(activator)
388 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
391 centerprint (activator, "There are more to go...");
392 else if (self.count == 3)
393 centerprint (activator, "Only 3 more to go...");
394 else if (self.count == 2)
395 centerprint (activator, "Only 2 more to go...");
397 centerprint (activator, "Only 1 more to go...");
402 if (IS_PLAYER(activator)
403 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
404 centerprint(activator, "Sequence completed!");
405 self.enemy = activator;
411 self.count = self.cnt;
415 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
416 Acts as an intermediary for an action that takes multiple inputs.
418 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
420 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
422 void spawnfunc_trigger_counter()
427 self.cnt = self.count;
429 self.use = counter_use;
430 self.reset = counter_reset;
433 void trigger_hurt_use()
435 if(IS_PLAYER(activator))
436 self.enemy = activator;
438 self.enemy = world; // let's just destroy it, if taking over is too much work
441 .float triggerhurttime;
442 void trigger_hurt_touch()
444 if (self.active != ACTIVE_ACTIVE)
448 if((self.spawnflags & 4 == 0) == (self.team != other.team))
451 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
452 if (other.iscreature)
454 if (other.takedamage)
455 if (other.triggerhurttime < time)
458 other.triggerhurttime = time + 1;
462 if not(IS_PLAYER(own))
465 self.enemy = world; // I still hate you all
468 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
471 else if(other.damagedbytriggers)
476 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
483 if (other.classname == "rune") // reset runes
486 other.nextthink = min(other.nextthink, time + 1);
494 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
495 Any object touching this will be hurt
496 set dmg to damage amount
499 .entity trigger_hurt_next;
500 entity trigger_hurt_last;
501 entity trigger_hurt_first;
502 void spawnfunc_trigger_hurt()
505 self.active = ACTIVE_ACTIVE;
506 self.touch = trigger_hurt_touch;
507 self.use = trigger_hurt_use;
508 self.enemy = world; // I hate you all
511 if (self.message == "")
512 self.message = "was in the wrong place";
513 if (self.message2 == "")
514 self.message2 = "was thrown into a world of hurt by";
515 // self.message = "someone like %s always gets wrongplaced";
517 if(!trigger_hurt_first)
518 trigger_hurt_first = self;
519 if(trigger_hurt_last)
520 trigger_hurt_last.trigger_hurt_next = self;
521 trigger_hurt_last = self;
524 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
528 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
529 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
535 //////////////////////////////////////////////////////////////
539 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
541 //////////////////////////////////////////////////////////////
543 .float triggerhealtime;
544 void trigger_heal_touch()
546 if (self.active != ACTIVE_ACTIVE)
549 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
550 if (other.iscreature)
552 if (other.takedamage)
554 if (other.triggerhealtime < time)
557 other.triggerhealtime = time + 1;
559 if (other.health < self.max_health)
561 other.health = min(other.health + self.health, self.max_health);
562 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
563 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
569 void spawnfunc_trigger_heal()
571 self.active = ACTIVE_ACTIVE;
574 self.touch = trigger_heal_touch;
577 if (!self.max_health)
578 self.max_health = 200; //Max health topoff for field
580 self.noise = "misc/mediumhealth.wav";
581 precache_sound(self.noise);
585 //////////////////////////////////////////////////////////////
591 //////////////////////////////////////////////////////////////
593 .entity trigger_gravity_check;
594 void trigger_gravity_remove(entity own)
596 if(own.trigger_gravity_check.owner == own)
598 UpdateCSQCProjectile(own);
599 own.gravity = own.trigger_gravity_check.gravity;
600 remove(own.trigger_gravity_check);
603 backtrace("Removing a trigger_gravity_check with no valid owner");
604 own.trigger_gravity_check = world;
606 void trigger_gravity_check_think()
608 // This spawns when a player enters the gravity zone and checks if he left.
609 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
610 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
613 if(self.owner.trigger_gravity_check == self)
614 trigger_gravity_remove(self.owner);
622 self.nextthink = time;
626 void trigger_gravity_use()
628 self.state = !self.state;
631 void trigger_gravity_touch()
635 if(self.state != TRUE)
642 if not(self.spawnflags & 1)
644 if(other.trigger_gravity_check)
646 if(self == other.trigger_gravity_check.enemy)
649 other.trigger_gravity_check.count = 2; // gravity one more frame...
654 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
655 trigger_gravity_remove(other);
659 other.trigger_gravity_check = spawn();
660 other.trigger_gravity_check.enemy = self;
661 other.trigger_gravity_check.owner = other;
662 other.trigger_gravity_check.gravity = other.gravity;
663 other.trigger_gravity_check.think = trigger_gravity_check_think;
664 other.trigger_gravity_check.nextthink = time;
665 other.trigger_gravity_check.count = 2;
670 if (other.gravity != g)
674 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
675 UpdateCSQCProjectile(self.owner);
679 void spawnfunc_trigger_gravity()
681 if(self.gravity == 1)
685 self.touch = trigger_gravity_touch;
687 precache_sound(self.noise);
692 self.use = trigger_gravity_use;
693 if(self.spawnflags & 2)
698 //=============================================================================
700 // TODO add a way to do looped sounds with sound(); then complete this entity
701 .float volume, atten;
702 void target_speaker_use_off();
703 void target_speaker_use_activator()
705 if not(IS_REAL_CLIENT(activator))
708 if(substring(self.noise, 0, 1) == "*")
711 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
712 if(GetPlayerSoundSampleField_notFound)
713 snd = "misc/null.wav";
714 else if(activator.sample == "")
715 snd = "misc/null.wav";
718 tokenize_console(activator.sample);
722 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
724 snd = strcat(argv(0), ".wav"); // randomization
729 msg_entity = activator;
730 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
732 void target_speaker_use_on()
735 if(substring(self.noise, 0, 1) == "*")
738 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
739 if(GetPlayerSoundSampleField_notFound)
740 snd = "misc/null.wav";
741 else if(activator.sample == "")
742 snd = "misc/null.wav";
745 tokenize_console(activator.sample);
749 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
751 snd = strcat(argv(0), ".wav"); // randomization
756 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
757 if(self.spawnflags & 3)
758 self.use = target_speaker_use_off;
760 void target_speaker_use_off()
762 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
763 self.use = target_speaker_use_on;
765 void target_speaker_reset()
767 if(self.spawnflags & 1) // LOOPED_ON
769 if(self.use == target_speaker_use_on)
770 target_speaker_use_on();
772 else if(self.spawnflags & 2)
774 if(self.use == target_speaker_use_off)
775 target_speaker_use_off();
779 void spawnfunc_target_speaker()
781 // TODO: "*" prefix to sound file name
782 // TODO: wait and random (just, HOW? random is not a field)
784 precache_sound (self.noise);
786 if(!self.atten && !(self.spawnflags & 4))
789 self.atten = ATTN_NORM;
791 self.atten = ATTN_STATIC;
793 else if(self.atten < 0)
801 if(self.spawnflags & 8) // ACTIVATOR
802 self.use = target_speaker_use_activator;
803 else if(self.spawnflags & 1) // LOOPED_ON
805 target_speaker_use_on();
806 self.reset = target_speaker_reset;
808 else if(self.spawnflags & 2) // LOOPED_OFF
810 self.use = target_speaker_use_on;
811 self.reset = target_speaker_reset;
814 self.use = target_speaker_use_on;
816 else if(self.spawnflags & 1) // LOOPED_ON
818 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
821 else if(self.spawnflags & 2) // LOOPED_OFF
823 objerror("This sound entity can never be activated");
827 // Quake/Nexuiz fallback
828 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
834 void spawnfunc_func_stardust() {
835 self.effects = EF_STARDUST;
839 .float bgmscriptattack;
840 .float bgmscriptdecay;
841 .float bgmscriptsustain;
842 .float bgmscriptrelease;
843 float pointparticles_SendEntity(entity to, float fl)
845 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
847 // optional features to save space
849 if(self.spawnflags & 2)
850 fl |= 0x10; // absolute count on toggle-on
851 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
852 fl |= 0x20; // 4 bytes - saves CPU
853 if(self.waterlevel || self.count != 1)
854 fl |= 0x40; // 4 bytes - obscure features almost never used
855 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
856 fl |= 0x80; // 14 bytes - saves lots of space
858 WriteByte(MSG_ENTITY, fl);
862 WriteCoord(MSG_ENTITY, self.impulse);
864 WriteCoord(MSG_ENTITY, 0); // off
868 WriteCoord(MSG_ENTITY, self.origin_x);
869 WriteCoord(MSG_ENTITY, self.origin_y);
870 WriteCoord(MSG_ENTITY, self.origin_z);
874 if(self.model != "null")
876 WriteShort(MSG_ENTITY, self.modelindex);
879 WriteCoord(MSG_ENTITY, self.mins_x);
880 WriteCoord(MSG_ENTITY, self.mins_y);
881 WriteCoord(MSG_ENTITY, self.mins_z);
882 WriteCoord(MSG_ENTITY, self.maxs_x);
883 WriteCoord(MSG_ENTITY, self.maxs_y);
884 WriteCoord(MSG_ENTITY, self.maxs_z);
889 WriteShort(MSG_ENTITY, 0);
892 WriteCoord(MSG_ENTITY, self.maxs_x);
893 WriteCoord(MSG_ENTITY, self.maxs_y);
894 WriteCoord(MSG_ENTITY, self.maxs_z);
897 WriteShort(MSG_ENTITY, self.cnt);
900 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
901 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
905 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
906 WriteByte(MSG_ENTITY, self.count * 16.0);
908 WriteString(MSG_ENTITY, self.noise);
911 WriteByte(MSG_ENTITY, floor(self.atten * 64));
912 WriteByte(MSG_ENTITY, floor(self.volume * 255));
914 WriteString(MSG_ENTITY, self.bgmscript);
915 if(self.bgmscript != "")
917 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
918 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
919 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
920 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
926 void pointparticles_use()
928 self.state = !self.state;
932 void pointparticles_think()
934 if(self.origin != self.oldorigin)
937 self.oldorigin = self.origin;
939 self.nextthink = time;
942 void pointparticles_reset()
944 if(self.spawnflags & 1)
950 void spawnfunc_func_pointparticles()
953 setmodel(self, self.model);
955 precache_sound (self.noise);
957 if(!self.bgmscriptsustain)
958 self.bgmscriptsustain = 1;
959 else if(self.bgmscriptsustain < 0)
960 self.bgmscriptsustain = 0;
963 self.atten = ATTN_NORM;
964 else if(self.atten < 0)
975 setorigin(self, self.origin + self.mins);
976 setsize(self, '0 0 0', self.maxs - self.mins);
979 self.cnt = particleeffectnum(self.mdl);
981 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
985 self.use = pointparticles_use;
986 self.reset = pointparticles_reset;
991 self.think = pointparticles_think;
992 self.nextthink = time;
995 void spawnfunc_func_sparks()
997 // self.cnt is the amount of sparks that one burst will spawn
999 self.cnt = 25.0; // nice default value
1002 // self.wait is the probability that a sparkthink will spawn a spark shower
1003 // range: 0 - 1, but 0 makes little sense, so...
1004 if(self.wait < 0.05) {
1005 self.wait = 0.25; // nice default value
1008 self.count = self.cnt;
1009 self.mins = '0 0 0';
1010 self.maxs = '0 0 0';
1011 self.velocity = '0 0 -1';
1012 self.mdl = "TE_SPARK";
1013 self.impulse = 10 * self.wait; // by default 2.5/sec
1015 self.cnt = 0; // use mdl
1017 spawnfunc_func_pointparticles();
1020 float rainsnow_SendEntity(entity to, float sf)
1022 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1023 WriteByte(MSG_ENTITY, self.state);
1024 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1025 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1026 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1027 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1028 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1029 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1030 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1031 WriteShort(MSG_ENTITY, self.count);
1032 WriteByte(MSG_ENTITY, self.cnt);
1036 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1037 This is an invisible area like a trigger, which rain falls inside of.
1041 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1043 sets color of rain (default 12 - white)
1045 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1047 void spawnfunc_func_rain()
1049 self.dest = self.velocity;
1050 self.velocity = '0 0 0';
1052 self.dest = '0 0 -700';
1053 self.angles = '0 0 0';
1054 self.movetype = MOVETYPE_NONE;
1055 self.solid = SOLID_NOT;
1056 SetBrushEntityModel();
1061 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1064 if(self.count > 65535)
1067 self.state = 1; // 1 is rain, 0 is snow
1070 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1074 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1075 This is an invisible area like a trigger, which snow falls inside of.
1079 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1081 sets color of rain (default 12 - white)
1083 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1085 void spawnfunc_func_snow()
1087 self.dest = self.velocity;
1088 self.velocity = '0 0 0';
1090 self.dest = '0 0 -300';
1091 self.angles = '0 0 0';
1092 self.movetype = MOVETYPE_NONE;
1093 self.solid = SOLID_NOT;
1094 SetBrushEntityModel();
1099 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1102 if(self.count > 65535)
1105 self.state = 0; // 1 is rain, 0 is snow
1108 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1112 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1115 void misc_laser_aim()
1120 if(self.spawnflags & 2)
1122 if(self.enemy.origin != self.mangle)
1124 self.mangle = self.enemy.origin;
1125 self.SendFlags |= 2;
1130 a = vectoangles(self.enemy.origin - self.origin);
1132 if(a != self.mangle)
1135 self.SendFlags |= 2;
1141 if(self.angles != self.mangle)
1143 self.mangle = self.angles;
1144 self.SendFlags |= 2;
1147 if(self.origin != self.oldorigin)
1149 self.SendFlags |= 1;
1150 self.oldorigin = self.origin;
1154 void misc_laser_init()
1156 if(self.target != "")
1157 self.enemy = find(world, targetname, self.target);
1161 void misc_laser_think()
1168 self.nextthink = time;
1177 o = self.enemy.origin;
1178 if not(self.spawnflags & 2)
1179 o = self.origin + normalize(o - self.origin) * 32768;
1183 makevectors(self.mangle);
1184 o = self.origin + v_forward * 32768;
1187 if(self.dmg || self.enemy.target != "")
1189 traceline(self.origin, o, MOVE_NORMAL, self);
1192 hitloc = trace_endpos;
1194 if(self.enemy.target != "") // DETECTOR laser
1196 if(trace_ent.iscreature)
1198 self.pusher = hitent;
1205 activator = self.pusher;
1218 activator = self.pusher;
1228 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1230 if(hitent.takedamage)
1231 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1235 float laser_SendEntity(entity to, float fl)
1237 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1238 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1239 if(self.spawnflags & 2)
1243 if(self.scale != 1 || self.modelscale != 1)
1245 if(self.spawnflags & 4)
1247 WriteByte(MSG_ENTITY, fl);
1250 WriteCoord(MSG_ENTITY, self.origin_x);
1251 WriteCoord(MSG_ENTITY, self.origin_y);
1252 WriteCoord(MSG_ENTITY, self.origin_z);
1256 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1257 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1258 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1260 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1263 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1264 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1266 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1267 WriteShort(MSG_ENTITY, self.cnt + 1);
1273 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1274 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1275 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1279 WriteAngle(MSG_ENTITY, self.mangle_x);
1280 WriteAngle(MSG_ENTITY, self.mangle_y);
1284 WriteByte(MSG_ENTITY, self.state);
1288 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1289 Any object touching the beam will be hurt
1292 spawnfunc_target_position where the laser ends
1294 name of beam end effect to use
1296 color of the beam (default: red)
1298 damage per second (-1 for a laser that kills immediately)
1302 self.state = !self.state;
1303 self.SendFlags |= 4;
1309 if(self.spawnflags & 1)
1315 void spawnfunc_misc_laser()
1319 if(self.mdl == "none")
1323 self.cnt = particleeffectnum(self.mdl);
1326 self.cnt = particleeffectnum("laser_deadly");
1332 self.cnt = particleeffectnum("laser_deadly");
1339 if(self.colormod == '0 0 0')
1341 self.colormod = '1 0 0';
1342 if(self.message == "")
1343 self.message = "saw the light";
1344 if (self.message2 == "")
1345 self.message2 = "was pushed into a laser by";
1348 if(!self.modelscale)
1349 self.modelscale = 1;
1350 else if(self.modelscale < 0)
1351 self.modelscale = 0;
1352 self.think = misc_laser_think;
1353 self.nextthink = time;
1354 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1356 self.mangle = self.angles;
1358 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1362 self.reset = laser_reset;
1364 self.use = laser_use;
1370 // tZorks trigger impulse / gravity
1374 .float lastpushtime;
1376 // targeted (directional) mode
1377 void trigger_impulse_touch1()
1380 float pushdeltatime;
1383 if (self.active != ACTIVE_ACTIVE)
1386 if (!isPushable(other))
1391 targ = find(world, targetname, self.target);
1394 objerror("trigger_force without a (valid) .target!\n");
1399 str = min(self.radius, vlen(self.origin - other.origin));
1401 if(self.falloff == 1)
1402 str = (str / self.radius) * self.strength;
1403 else if(self.falloff == 2)
1404 str = (1 - (str / self.radius)) * self.strength;
1406 str = self.strength;
1408 pushdeltatime = time - other.lastpushtime;
1409 if (pushdeltatime > 0.15) pushdeltatime = 0;
1410 other.lastpushtime = time;
1411 if(!pushdeltatime) return;
1413 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1414 other.flags &~= FL_ONGROUND;
1415 UpdateCSQCProjectile(other);
1418 // Directionless (accelerator/decelerator) mode
1419 void trigger_impulse_touch2()
1421 float pushdeltatime;
1423 if (self.active != ACTIVE_ACTIVE)
1426 if (!isPushable(other))
1431 pushdeltatime = time - other.lastpushtime;
1432 if (pushdeltatime > 0.15) pushdeltatime = 0;
1433 other.lastpushtime = time;
1434 if(!pushdeltatime) return;
1436 // div0: ticrate independent, 1 = identity (not 20)
1437 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1438 UpdateCSQCProjectile(other);
1441 // Spherical (gravity/repulsor) mode
1442 void trigger_impulse_touch3()
1444 float pushdeltatime;
1447 if (self.active != ACTIVE_ACTIVE)
1450 if (!isPushable(other))
1455 pushdeltatime = time - other.lastpushtime;
1456 if (pushdeltatime > 0.15) pushdeltatime = 0;
1457 other.lastpushtime = time;
1458 if(!pushdeltatime) return;
1460 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1462 str = min(self.radius, vlen(self.origin - other.origin));
1464 if(self.falloff == 1)
1465 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1466 else if(self.falloff == 2)
1467 str = (str / self.radius) * self.strength; // 0 in the inside
1469 str = self.strength;
1471 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1472 UpdateCSQCProjectile(other);
1475 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1476 -------- KEYS --------
1477 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1478 If not, this trigger acts like a damper/accelerator field.
1480 strength : This is how mutch force to add in the direction of .target each second
1481 when .target is set. If not, this is hoe mutch to slow down/accelerate
1482 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1484 radius : If set, act as a spherical device rather then a liniar one.
1486 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1488 -------- NOTES --------
1489 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1490 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1493 void spawnfunc_trigger_impulse()
1495 self.active = ACTIVE_ACTIVE;
1500 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1501 setorigin(self, self.origin);
1502 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1503 self.touch = trigger_impulse_touch3;
1509 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1510 self.touch = trigger_impulse_touch1;
1514 if(!self.strength) self.strength = 0.9;
1515 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1516 self.touch = trigger_impulse_touch2;
1521 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1522 "Flip-flop" trigger gate... lets only every second trigger event through
1526 self.state = !self.state;
1531 void spawnfunc_trigger_flipflop()
1533 if(self.spawnflags & 1)
1535 self.use = flipflop_use;
1536 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1539 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1540 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1544 self.nextthink = time + self.wait;
1545 self.enemy = activator;
1551 void monoflop_fixed_use()
1555 self.nextthink = time + self.wait;
1557 self.enemy = activator;
1561 void monoflop_think()
1564 activator = self.enemy;
1568 void monoflop_reset()
1574 void spawnfunc_trigger_monoflop()
1578 if(self.spawnflags & 1)
1579 self.use = monoflop_fixed_use;
1581 self.use = monoflop_use;
1582 self.think = monoflop_think;
1584 self.reset = monoflop_reset;
1587 void multivibrator_send()
1592 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1594 newstate = (time < cyclestart + self.wait);
1597 if(self.state != newstate)
1599 self.state = newstate;
1602 self.nextthink = cyclestart + self.wait + 0.01;
1604 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1607 void multivibrator_toggle()
1609 if(self.nextthink == 0)
1611 multivibrator_send();
1624 void multivibrator_reset()
1626 if(!(self.spawnflags & 1))
1627 self.nextthink = 0; // wait for a trigger event
1629 self.nextthink = max(1, time);
1632 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1633 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1634 -------- KEYS --------
1635 target: trigger all entities with this targetname when it goes off
1636 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1637 phase: offset of the timing
1638 wait: "on" cycle time (default: 1)
1639 respawntime: "off" cycle time (default: same as wait)
1640 -------- SPAWNFLAGS --------
1641 START_ON: assume it is already turned on (when targeted)
1643 void spawnfunc_trigger_multivibrator()
1647 if(!self.respawntime)
1648 self.respawntime = self.wait;
1651 self.use = multivibrator_toggle;
1652 self.think = multivibrator_send;
1653 self.nextthink = max(1, time);
1656 multivibrator_reset();
1665 if(self.killtarget != "")
1666 src = find(world, targetname, self.killtarget);
1667 if(self.target != "")
1668 dst = find(world, targetname, self.target);
1672 objerror("follow: could not find target/killtarget");
1678 // already done :P entity must stay
1682 else if(!src || !dst)
1684 objerror("follow: could not find target/killtarget");
1687 else if(self.spawnflags & 1)
1690 if(self.spawnflags & 2)
1692 setattachment(dst, src, self.message);
1696 attach_sameorigin(dst, src, self.message);
1699 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1704 if(self.spawnflags & 2)
1706 dst.movetype = MOVETYPE_FOLLOW;
1708 // dst.punchangle = '0 0 0'; // keep unchanged
1709 dst.view_ofs = dst.origin;
1710 dst.v_angle = dst.angles;
1714 follow_sameorigin(dst, src);
1721 void spawnfunc_misc_follow()
1723 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1728 void gamestart_use() {
1734 void spawnfunc_trigger_gamestart() {
1735 self.use = gamestart_use;
1736 self.reset2 = spawnfunc_trigger_gamestart;
1740 self.think = self.use;
1741 self.nextthink = game_starttime + self.wait;
1744 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1750 .entity voicescript; // attached voice script
1751 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1752 .float voicescript_nextthink; // time to play next voice
1753 .float voicescript_voiceend; // time when this voice ends
1755 void target_voicescript_clear(entity pl)
1757 pl.voicescript = world;
1760 void target_voicescript_use()
1762 if(activator.voicescript != self)
1764 activator.voicescript = self;
1765 activator.voicescript_index = 0;
1766 activator.voicescript_nextthink = time + self.delay;
1770 void target_voicescript_next(entity pl)
1775 vs = pl.voicescript;
1778 if(vs.message == "")
1780 if not(IS_PLAYER(pl))
1785 if(time >= pl.voicescript_voiceend)
1787 if(time >= pl.voicescript_nextthink)
1789 // get the next voice...
1790 n = tokenize_console(vs.message);
1792 if(pl.voicescript_index < vs.cnt)
1793 i = pl.voicescript_index * 2;
1794 else if(n > vs.cnt * 2)
1795 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1801 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1802 dt = stof(argv(i + 1));
1805 pl.voicescript_voiceend = time + dt;
1806 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1810 pl.voicescript_voiceend = time - dt;
1811 pl.voicescript_nextthink = pl.voicescript_voiceend;
1814 pl.voicescript_index += 1;
1818 pl.voicescript = world; // stop trying then
1824 void spawnfunc_target_voicescript()
1826 // netname: directory of the sound files
1827 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1828 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1829 // Here, a - in front of the duration means that no delay is to be
1830 // added after this message
1831 // wait: average time between messages
1832 // delay: initial delay before the first message
1835 self.use = target_voicescript_use;
1837 n = tokenize_console(self.message);
1839 for(i = 0; i+1 < n; i += 2)
1846 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1852 void trigger_relay_teamcheck_use()
1856 if(self.spawnflags & 2)
1858 if(activator.team != self.team)
1863 if(activator.team == self.team)
1869 if(self.spawnflags & 1)
1874 void trigger_relay_teamcheck_reset()
1876 self.team = self.team_saved;
1879 void spawnfunc_trigger_relay_teamcheck()
1881 self.team_saved = self.team;
1882 self.use = trigger_relay_teamcheck_use;
1883 self.reset = trigger_relay_teamcheck_reset;
1888 void trigger_disablerelay_use()
1895 for(e = world; (e = find(e, targetname, self.target)); )
1897 if(e.use == SUB_UseTargets)
1899 e.use = SUB_DontUseTargets;
1902 else if(e.use == SUB_DontUseTargets)
1904 e.use = SUB_UseTargets;
1910 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1913 void spawnfunc_trigger_disablerelay()
1915 self.use = trigger_disablerelay_use;
1918 float magicear_matched;
1919 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1920 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1922 float domatch, dotrigger, matchstart, l;
1927 magicear_matched = FALSE;
1929 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1930 domatch = ((ear.spawnflags & 32) || dotrigger);
1937 // we are in TUBA mode!
1938 if not(ear.spawnflags & 256)
1941 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1944 magicear_matched = TRUE;
1951 savemessage = self.message;
1952 self.message = string_null;
1954 self.message = savemessage;
1958 if(ear.netname != "")
1964 if(ear.spawnflags & 256) // ENOTUBA
1969 if(ear.spawnflags & 4)
1975 if(ear.spawnflags & 1)
1978 if(ear.spawnflags & 2)
1981 if(ear.spawnflags & 8)
1986 l = strlen(ear.message);
1988 if(ear.spawnflags & 128)
1991 msg = strdecolorize(msgin);
1993 if(substring(ear.message, 0, 1) == "*")
1995 if(substring(ear.message, -1, 1) == "*")
1998 // as we need multi-replacement here...
1999 s = substring(ear.message, 1, -2);
2001 if(strstrofs(msg, s, 0) >= 0)
2002 matchstart = -2; // we use strreplace on s
2007 s = substring(ear.message, 1, -1);
2009 if(substring(msg, -l, l) == s)
2010 matchstart = strlen(msg) - l;
2015 if(substring(ear.message, -1, 1) == "*")
2018 s = substring(ear.message, 0, -2);
2020 if(substring(msg, 0, l) == s)
2027 if(msg == ear.message)
2032 if(matchstart == -1) // no match
2035 magicear_matched = TRUE;
2042 savemessage = self.message;
2043 self.message = string_null;
2045 self.message = savemessage;
2049 if(ear.spawnflags & 16)
2053 else if(ear.netname != "")
2056 return strreplace(s, ear.netname, msg);
2059 substring(msg, 0, matchstart),
2061 substring(msg, matchstart + l, -1)
2069 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2073 for(ear = magicears; ear; ear = ear.enemy)
2075 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2076 if not(ear.spawnflags & 64)
2077 if(magicear_matched)
2084 void spawnfunc_trigger_magicear()
2086 self.enemy = magicears;
2089 // actually handled in "say" processing
2092 // 2 = ignore teamsay
2094 // 8 = ignore tell to unknown player
2095 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2096 // 32 = perform the replacement even if outside the radius or dead
2097 // 64 = continue replacing/triggering even if this one matched
2098 // 128 = don't decolorize message before matching
2099 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2100 // 512 = tuba notes must be exact right pitch, no transposing
2110 // if set, replacement for the matched text
2112 // "hearing distance"
2116 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2118 self.movedir_x -= 1; // map to tuba instrument numbers
2121 void relay_activators_use()
2127 for(trg = world; (trg = find(trg, targetname, os.target)); )
2131 trg.setactive(os.cnt);
2134 //bprint("Not using setactive\n");
2135 if(os.cnt == ACTIVE_TOGGLE)
2136 if(trg.active == ACTIVE_ACTIVE)
2137 trg.active = ACTIVE_NOT;
2139 trg.active = ACTIVE_ACTIVE;
2141 trg.active = os.cnt;
2147 void spawnfunc_relay_activate()
2149 self.cnt = ACTIVE_ACTIVE;
2150 self.use = relay_activators_use;
2153 void spawnfunc_relay_deactivate()
2155 self.cnt = ACTIVE_NOT;
2156 self.use = relay_activators_use;
2159 void spawnfunc_relay_activatetoggle()
2161 self.cnt = ACTIVE_TOGGLE;
2162 self.use = relay_activators_use;
2165 .string chmap, gametype;
2166 void spawnfunc_target_changelevel_use()
2168 if(self.gametype != "")
2169 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2171 if (self.chmap == "")
2172 localcmd("endmatch\n");
2174 localcmd(strcat("changelevel ", self.chmap, "\n"));
2177 void spawnfunc_target_changelevel()
2179 self.use = spawnfunc_target_changelevel_use;