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 local entity t, stemp, otemp, act;
45 // create a temp object to fire at a later time
47 t.classname = "DelayedUse";
48 t.nextthink = time + self.delay;
51 t.message = self.message;
52 t.killtarget = self.killtarget;
53 t.target = self.target;
54 t.target2 = self.target2;
55 t.target3 = self.target3;
56 t.target4 = self.target4;
64 if (activator.classname == "player" && self.message != "")
66 if(clienttype(activator) == CLIENTTYPE_REAL)
68 centerprint (activator, self.message);
70 play2(activator, "misc/talk.wav");
75 // kill the killtagets
80 for(t = world; (t = find(t, targetname, s)); )
91 for(i = 0; i < 4; ++i)
96 case 0: s = stemp.target; break;
97 case 1: s = stemp.target2; break;
98 case 2: s = stemp.target3; break;
99 case 3: s = stemp.target4; break;
103 for(t = world; (t = find(t, targetname, s)); )
106 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
121 //=============================================================================
123 float SPAWNFLAG_NOMESSAGE = 1;
124 float SPAWNFLAG_NOTOUCH = 1;
126 // the wait time has passed, so set back up for another activation
131 self.health = self.max_health;
132 self.takedamage = DAMAGE_YES;
133 self.solid = SOLID_BBOX;
138 // the trigger was just touched/killed/used
139 // self.enemy should be set to the activator so it can be held through a delay
140 // so wait for the delay time before firing
143 if (self.nextthink > time)
145 return; // allready been triggered
148 if (self.classname == "trigger_secret")
150 if (self.enemy.classname != "player")
152 found_secrets = found_secrets + 1;
153 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
157 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
159 // don't trigger again until reset
160 self.takedamage = DAMAGE_NO;
162 activator = self.enemy;
163 other = self.goalentity;
168 self.think = multi_wait;
169 self.nextthink = time + self.wait;
171 else if (self.wait == 0)
173 multi_wait(); // waiting finished
176 { // we can't just remove (self) here, because this is a touch function
177 // called wheil C code is looping through area links...
178 self.touch = SUB_Null;
184 self.goalentity = other;
185 self.enemy = activator;
191 if not(self.spawnflags & 2)
192 if not(other.iscreature)
196 if((self.spawnflags & 4 == 0) == (self.team != other.team))
199 // if the trigger has an angles field, check player's facing direction
200 if (self.movedir != '0 0 0')
202 makevectors (other.angles);
203 if (v_forward * self.movedir < 0)
204 return; // not facing the right way
210 self.goalentity = other;
214 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
216 if (!self.takedamage)
218 if(self.spawnflags & DOOR_NOSPLASH)
219 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
221 self.health = self.health - damage;
222 if (self.health <= 0)
224 self.enemy = attacker;
225 self.goalentity = inflictor;
232 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
233 self.touch = multi_touch;
236 self.health = self.max_health;
237 self.takedamage = DAMAGE_YES;
238 self.solid = SOLID_BBOX;
240 self.think = SUB_Null;
241 self.team = self.team_saved;
244 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
245 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.
246 If "delay" is set, the trigger waits some time after activating before firing.
247 "wait" : Seconds between triggerings. (.2 default)
248 If notouch is set, the trigger is only fired by other entities, not by touching.
249 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
255 set "message" to text string
257 void spawnfunc_trigger_multiple()
259 self.reset = multi_reset;
260 if (self.sounds == 1)
262 precache_sound ("misc/secret.wav");
263 self.noise = "misc/secret.wav";
265 else if (self.sounds == 2)
267 precache_sound ("misc/talk.wav");
268 self.noise = "misc/talk.wav";
270 else if (self.sounds == 3)
272 precache_sound ("misc/trigger1.wav");
273 self.noise = "misc/trigger1.wav";
278 else if(self.wait < -1)
280 self.use = multi_use;
284 self.team_saved = self.team;
288 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
289 objerror ("health and notouch don't make sense\n");
290 self.max_health = self.health;
291 self.event_damage = multi_eventdamage;
292 self.takedamage = DAMAGE_YES;
293 self.solid = SOLID_BBOX;
294 setorigin (self, self.origin); // make sure it links into the world
298 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
300 self.touch = multi_touch;
301 setorigin (self, self.origin); // make sure it links into the world
307 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
308 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
309 "targetname". If "health" is set, the trigger must be killed to activate.
310 If notouch is set, the trigger is only fired by other entities, not by touching.
311 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
312 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.
318 set "message" to text string
320 void spawnfunc_trigger_once()
323 spawnfunc_trigger_multiple();
326 //=============================================================================
328 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
329 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
331 void spawnfunc_trigger_relay()
333 self.use = SUB_UseTargets;
334 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
339 self.think = SUB_UseTargets;
340 self.nextthink = self.wait;
345 self.think = SUB_Null;
348 void spawnfunc_trigger_delay()
353 self.use = delay_use;
354 self.reset = delay_reset;
357 //=============================================================================
362 self.count = self.count - 1;
368 if (activator.classname == "player"
369 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
372 centerprint (activator, "There are more to go...");
373 else if (self.count == 3)
374 centerprint (activator, "Only 3 more to go...");
375 else if (self.count == 2)
376 centerprint (activator, "Only 2 more to go...");
378 centerprint (activator, "Only 1 more to go...");
383 if (activator.classname == "player"
384 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
385 centerprint(activator, "Sequence completed!");
386 self.enemy = activator;
392 self.count = self.cnt;
396 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
397 Acts as an intermediary for an action that takes multiple inputs.
399 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
401 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
403 void spawnfunc_trigger_counter()
408 self.cnt = self.count;
410 self.use = counter_use;
411 self.reset = counter_reset;
414 void trigger_hurt_use()
416 if(activator.classname == "player")
417 self.enemy = activator;
419 self.enemy = world; // let's just destroy it, if taking over is too much work
422 .float triggerhurttime;
423 void trigger_hurt_touch()
425 if (self.active != ACTIVE_ACTIVE)
429 if((self.spawnflags & 4 == 0) == (self.team != other.team))
432 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
433 if (other.iscreature)
435 if (other.takedamage)
436 if (other.triggerhurttime < time)
439 other.triggerhurttime = time + 1;
443 if(own.classname != "player")
446 self.enemy = world; // I still hate you all
449 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
456 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
459 other.pain_finished = min(other.pain_finished, time + 2);
461 else if (other.classname == "rune") // reset runes
464 other.nextthink = min(other.nextthink, time + 1);
472 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
473 Any object touching this will be hurt
474 set dmg to damage amount
477 .entity trigger_hurt_next;
478 entity trigger_hurt_last;
479 entity trigger_hurt_first;
480 void spawnfunc_trigger_hurt()
483 self.active = ACTIVE_ACTIVE;
484 self.touch = trigger_hurt_touch;
485 self.use = trigger_hurt_use;
486 self.enemy = world; // I hate you all
490 self.message = "was in the wrong place";
492 self.message2 = "was thrown into a world of hurt by";
493 // self.message = "someone like %s always gets wrongplaced";
495 if(!trigger_hurt_first)
496 trigger_hurt_first = self;
497 if(trigger_hurt_last)
498 trigger_hurt_last.trigger_hurt_next = self;
499 trigger_hurt_last = self;
502 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
506 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
507 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
513 //////////////////////////////////////////////////////////////
517 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
519 //////////////////////////////////////////////////////////////
521 .float triggerhealtime;
522 void trigger_heal_touch()
524 if (self.active != ACTIVE_ACTIVE)
527 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
528 if (other.iscreature)
530 if (other.takedamage)
531 if (other.triggerhealtime < time)
534 other.triggerhealtime = time + 1;
536 if (other.health < self.max_health)
538 other.health = min(other.health + self.health, self.max_health);
539 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
540 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
546 void spawnfunc_trigger_heal()
548 self.active = ACTIVE_ACTIVE;
551 self.touch = trigger_heal_touch;
554 if (!self.max_health)
555 self.max_health = 200; //Max health topoff for field
557 self.noise = "misc/mediumhealth.wav";
558 precache_sound(self.noise);
562 //////////////////////////////////////////////////////////////
568 //////////////////////////////////////////////////////////////
570 .entity trigger_gravity_check;
571 void trigger_gravity_remove(entity own)
573 if(own.trigger_gravity_check.owner == own)
575 UpdateCSQCProjectile(own);
576 own.gravity = own.trigger_gravity_check.gravity;
577 remove(own.trigger_gravity_check);
580 backtrace("Removing a trigger_gravity_check with no valid owner");
581 own.trigger_gravity_check = world;
583 void trigger_gravity_check_think()
585 // This spawns when a player enters the gravity zone and checks if he left.
586 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
587 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
590 if(self.owner.trigger_gravity_check == self)
591 trigger_gravity_remove(self.owner);
599 self.nextthink = time;
603 void trigger_gravity_use()
605 self.state = !self.state;
608 void trigger_gravity_touch()
612 if(self.state != TRUE)
619 if not(self.spawnflags & 1)
621 if(other.trigger_gravity_check)
623 if(self == other.trigger_gravity_check.enemy)
626 other.trigger_gravity_check.count = 2; // gravity one more frame...
631 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
632 trigger_gravity_remove(other);
636 other.trigger_gravity_check = spawn();
637 other.trigger_gravity_check.enemy = self;
638 other.trigger_gravity_check.owner = other;
639 other.trigger_gravity_check.gravity = other.gravity;
640 other.trigger_gravity_check.think = trigger_gravity_check_think;
641 other.trigger_gravity_check.nextthink = time;
642 other.trigger_gravity_check.count = 2;
647 if (other.gravity != g)
651 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
652 UpdateCSQCProjectile(self.owner);
656 void spawnfunc_trigger_gravity()
658 if(self.gravity == 1)
662 self.touch = trigger_gravity_touch;
664 precache_sound(self.noise);
669 self.use = trigger_gravity_use;
670 if(self.spawnflags & 2)
675 //=============================================================================
677 // TODO add a way to do looped sounds with sound(); then complete this entity
678 .float volume, atten;
679 void target_speaker_use_off();
680 void target_speaker_use_activator()
682 if(clienttype(activator) != CLIENTTYPE_REAL)
685 if(substring(self.noise, 0, 1) == "*")
688 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
689 if(GetPlayerSoundSampleField_notFound)
690 snd = "misc/null.wav";
691 else if(activator.sample == "")
692 snd = "misc/null.wav";
695 tokenize_console(activator.sample);
699 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
701 snd = strcat(argv(0), ".wav"); // randomization
706 msg_entity = activator;
707 soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
709 void target_speaker_use_on()
712 if(substring(self.noise, 0, 1) == "*")
715 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
716 if(GetPlayerSoundSampleField_notFound)
717 snd = "misc/null.wav";
718 else if(activator.sample == "")
719 snd = "misc/null.wav";
722 tokenize_console(activator.sample);
726 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
728 snd = strcat(argv(0), ".wav"); // randomization
733 sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
734 if(self.spawnflags & 3)
735 self.use = target_speaker_use_off;
737 void target_speaker_use_off()
739 sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
740 self.use = target_speaker_use_on;
742 void target_speaker_reset()
744 if(self.spawnflags & 1) // LOOPED_ON
746 if(self.use == target_speaker_use_on)
747 target_speaker_use_on();
749 else if(self.spawnflags & 2)
751 if(self.use == target_speaker_use_off)
752 target_speaker_use_off();
756 void spawnfunc_target_speaker()
758 // TODO: "*" prefix to sound file name
759 // TODO: wait and random (just, HOW? random is not a field)
761 precache_sound (self.noise);
763 if(!self.atten && !(self.spawnflags & 4))
766 self.atten = ATTN_NORM;
768 self.atten = ATTN_STATIC;
770 else if(self.atten < 0)
778 if(self.spawnflags & 8) // ACTIVATOR
779 self.use = target_speaker_use_activator;
780 else if(self.spawnflags & 1) // LOOPED_ON
782 target_speaker_use_on();
783 self.reset = target_speaker_reset;
785 else if(self.spawnflags & 2) // LOOPED_OFF
787 self.use = target_speaker_use_on;
788 self.reset = target_speaker_reset;
791 self.use = target_speaker_use_on;
793 else if(self.spawnflags & 1) // LOOPED_ON
795 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
798 else if(self.spawnflags & 2) // LOOPED_OFF
800 objerror("This sound entity can never be activated");
804 // Quake/Nexuiz fallback
805 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
811 void spawnfunc_func_stardust() {
812 self.effects = EF_STARDUST;
816 .float bgmscriptattack;
817 .float bgmscriptdecay;
818 .float bgmscriptsustain;
819 .float bgmscriptrelease;
820 float pointparticles_SendEntity(entity to, float fl)
822 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
824 // optional features to save space
826 if(self.spawnflags & 2)
827 fl |= 0x10; // absolute count on toggle-on
828 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
829 fl |= 0x20; // 4 bytes - saves CPU
830 if(self.waterlevel || self.count != 1)
831 fl |= 0x40; // 4 bytes - obscure features almost never used
832 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
833 fl |= 0x80; // 14 bytes - saves lots of space
835 WriteByte(MSG_ENTITY, fl);
839 WriteCoord(MSG_ENTITY, self.impulse);
841 WriteCoord(MSG_ENTITY, 0); // off
845 WriteCoord(MSG_ENTITY, self.origin_x);
846 WriteCoord(MSG_ENTITY, self.origin_y);
847 WriteCoord(MSG_ENTITY, self.origin_z);
851 if(self.model != "null")
853 WriteShort(MSG_ENTITY, self.modelindex);
856 WriteCoord(MSG_ENTITY, self.mins_x);
857 WriteCoord(MSG_ENTITY, self.mins_y);
858 WriteCoord(MSG_ENTITY, self.mins_z);
859 WriteCoord(MSG_ENTITY, self.maxs_x);
860 WriteCoord(MSG_ENTITY, self.maxs_y);
861 WriteCoord(MSG_ENTITY, self.maxs_z);
866 WriteShort(MSG_ENTITY, 0);
869 WriteCoord(MSG_ENTITY, self.maxs_x);
870 WriteCoord(MSG_ENTITY, self.maxs_y);
871 WriteCoord(MSG_ENTITY, self.maxs_z);
874 WriteShort(MSG_ENTITY, self.cnt);
877 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
878 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
882 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
883 WriteByte(MSG_ENTITY, self.count * 16.0);
885 WriteString(MSG_ENTITY, self.noise);
888 WriteByte(MSG_ENTITY, floor(self.atten * 64));
889 WriteByte(MSG_ENTITY, floor(self.volume * 255));
891 WriteString(MSG_ENTITY, self.bgmscript);
892 if(self.bgmscript != "")
894 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
895 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
896 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
897 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
903 void pointparticles_use()
905 self.state = !self.state;
909 void pointparticles_think()
911 if(self.origin != self.oldorigin)
914 self.oldorigin = self.origin;
916 self.nextthink = time;
919 void pointparticles_reset()
921 if(self.spawnflags & 1)
927 void spawnfunc_func_pointparticles()
930 setmodel(self, self.model);
932 precache_sound (self.noise);
934 if(!self.bgmscriptsustain)
935 self.bgmscriptsustain = 1;
936 else if(self.bgmscriptsustain < 0)
937 self.bgmscriptsustain = 0;
940 self.atten = ATTN_NORM;
941 else if(self.atten < 0)
952 setorigin(self, self.origin + self.mins);
953 setsize(self, '0 0 0', self.maxs - self.mins);
956 self.cnt = particleeffectnum(self.mdl);
958 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
962 self.use = pointparticles_use;
963 self.reset = pointparticles_reset;
968 self.think = pointparticles_think;
969 self.nextthink = time;
972 void spawnfunc_func_sparks()
974 // self.cnt is the amount of sparks that one burst will spawn
976 self.cnt = 25.0; // nice default value
979 // self.wait is the probability that a sparkthink will spawn a spark shower
980 // range: 0 - 1, but 0 makes little sense, so...
981 if(self.wait < 0.05) {
982 self.wait = 0.25; // nice default value
985 self.count = self.cnt;
988 self.velocity = '0 0 -1';
989 self.mdl = "TE_SPARK";
990 self.impulse = 10 * self.wait; // by default 2.5/sec
992 self.cnt = 0; // use mdl
994 spawnfunc_func_pointparticles();
997 float rainsnow_SendEntity(entity to, float sf)
999 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1000 WriteByte(MSG_ENTITY, self.state);
1001 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1002 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1003 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1004 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1005 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1006 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1007 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1008 WriteShort(MSG_ENTITY, self.count);
1009 WriteByte(MSG_ENTITY, self.cnt);
1013 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1014 This is an invisible area like a trigger, which rain falls inside of.
1018 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1020 sets color of rain (default 12 - white)
1022 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1024 void spawnfunc_func_rain()
1026 self.dest = self.velocity;
1027 self.velocity = '0 0 0';
1029 self.dest = '0 0 -700';
1030 self.angles = '0 0 0';
1031 self.movetype = MOVETYPE_NONE;
1032 self.solid = SOLID_NOT;
1033 SetBrushEntityModel();
1038 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1041 if(self.count > 65535)
1044 self.state = 1; // 1 is rain, 0 is snow
1047 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1051 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1052 This is an invisible area like a trigger, which snow falls inside of.
1056 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1058 sets color of rain (default 12 - white)
1060 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1062 void spawnfunc_func_snow()
1064 self.dest = self.velocity;
1065 self.velocity = '0 0 0';
1067 self.dest = '0 0 -300';
1068 self.angles = '0 0 0';
1069 self.movetype = MOVETYPE_NONE;
1070 self.solid = SOLID_NOT;
1071 SetBrushEntityModel();
1076 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1079 if(self.count > 65535)
1082 self.state = 0; // 1 is rain, 0 is snow
1085 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1089 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1092 void misc_laser_aim()
1097 if(self.spawnflags & 2)
1099 if(self.enemy.origin != self.mangle)
1101 self.mangle = self.enemy.origin;
1102 self.SendFlags |= 2;
1107 a = vectoangles(self.enemy.origin - self.origin);
1109 if(a != self.mangle)
1112 self.SendFlags |= 2;
1118 if(self.angles != self.mangle)
1120 self.mangle = self.angles;
1121 self.SendFlags |= 2;
1124 if(self.origin != self.oldorigin)
1126 self.SendFlags |= 1;
1127 self.oldorigin = self.origin;
1131 void misc_laser_init()
1133 if(self.target != "")
1134 self.enemy = find(world, targetname, self.target);
1138 void misc_laser_think()
1145 self.nextthink = time;
1154 o = self.enemy.origin;
1155 if not(self.spawnflags & 2)
1156 o = self.origin + normalize(o - self.origin) * 32768;
1160 makevectors(self.mangle);
1161 o = self.origin + v_forward * 32768;
1164 if(self.dmg || self.enemy.target != "")
1166 traceline(self.origin, o, MOVE_NORMAL, self);
1169 hitloc = trace_endpos;
1171 if(self.enemy.target != "") // DETECTOR laser
1173 if(trace_ent.iscreature)
1175 self.pusher = hitent;
1182 activator = self.pusher;
1195 activator = self.pusher;
1205 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1207 if(hitent.takedamage)
1208 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1212 float laser_SendEntity(entity to, float fl)
1214 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1215 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1216 if(self.spawnflags & 2)
1220 if(self.scale != 1 || self.modelscale != 1)
1222 if(self.spawnflags & 4)
1224 WriteByte(MSG_ENTITY, fl);
1227 WriteCoord(MSG_ENTITY, self.origin_x);
1228 WriteCoord(MSG_ENTITY, self.origin_y);
1229 WriteCoord(MSG_ENTITY, self.origin_z);
1233 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1234 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1235 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1237 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1240 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1241 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1243 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1244 WriteShort(MSG_ENTITY, self.cnt + 1);
1250 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1251 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1252 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1256 WriteAngle(MSG_ENTITY, self.mangle_x);
1257 WriteAngle(MSG_ENTITY, self.mangle_y);
1261 WriteByte(MSG_ENTITY, self.state);
1265 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1266 Any object touching the beam will be hurt
1269 spawnfunc_target_position where the laser ends
1271 name of beam end effect to use
1273 color of the beam (default: red)
1275 damage per second (-1 for a laser that kills immediately)
1279 self.state = !self.state;
1280 self.SendFlags |= 4;
1286 if(self.spawnflags & 1)
1292 void spawnfunc_misc_laser()
1296 if(self.mdl == "none")
1300 self.cnt = particleeffectnum(self.mdl);
1303 self.cnt = particleeffectnum("laser_deadly");
1309 self.cnt = particleeffectnum("laser_deadly");
1316 if(self.colormod == '0 0 0')
1318 self.colormod = '1 0 0';
1320 self.message = "saw the light";
1322 self.message2 = "was pushed into a laser by";
1325 if(!self.modelscale)
1326 self.modelscale = 1;
1327 else if(self.modelscale < 0)
1328 self.modelscale = 0;
1329 self.think = misc_laser_think;
1330 self.nextthink = time;
1331 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1333 self.mangle = self.angles;
1335 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1339 self.reset = laser_reset;
1341 self.use = laser_use;
1347 // tZorks trigger impulse / gravity
1351 .float lastpushtime;
1353 // targeted (directional) mode
1354 void trigger_impulse_touch1()
1357 float pushdeltatime;
1360 if (self.active != ACTIVE_ACTIVE)
1363 // FIXME: Better checking for what to push and not.
1364 if not(other.iscreature)
1365 if (other.classname != "corpse")
1366 if (other.classname != "body")
1367 if (other.classname != "gib")
1368 if (other.classname != "missile")
1369 if (other.classname != "rocket")
1370 if (other.classname != "casing")
1371 if (other.classname != "grenade")
1372 if (other.classname != "plasma")
1373 if (other.classname != "plasma_prim")
1374 if (other.classname != "plasma_chain")
1375 if (other.classname != "droppedweapon")
1376 if (other.classname != "nexball_basketball")
1377 if (other.classname != "nexball_football")
1380 if (other.deadflag && other.iscreature)
1385 targ = find(world, targetname, self.target);
1388 objerror("trigger_force without a (valid) .target!\n");
1393 if(self.falloff == 1)
1394 str = (str / self.radius) * self.strength;
1395 else if(self.falloff == 2)
1396 str = (1 - (str / self.radius)) * self.strength;
1398 str = self.strength;
1400 pushdeltatime = time - other.lastpushtime;
1401 if (pushdeltatime > 0.15) pushdeltatime = 0;
1402 other.lastpushtime = time;
1403 if(!pushdeltatime) return;
1405 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1406 other.flags &~= FL_ONGROUND;
1407 UpdateCSQCProjectile(other);
1410 // Directionless (accelerator/decelerator) mode
1411 void trigger_impulse_touch2()
1413 float pushdeltatime;
1415 if (self.active != ACTIVE_ACTIVE)
1418 // FIXME: Better checking for what to push and not.
1419 if not(other.iscreature)
1420 if (other.classname != "corpse")
1421 if (other.classname != "body")
1422 if (other.classname != "gib")
1423 if (other.classname != "missile")
1424 if (other.classname != "rocket")
1425 if (other.classname != "casing")
1426 if (other.classname != "grenade")
1427 if (other.classname != "plasma")
1428 if (other.classname != "plasma_prim")
1429 if (other.classname != "plasma_chain")
1430 if (other.classname != "droppedweapon")
1431 if (other.classname != "nexball_basketball")
1432 if (other.classname != "nexball_football")
1435 if (other.deadflag && other.iscreature)
1440 pushdeltatime = time - other.lastpushtime;
1441 if (pushdeltatime > 0.15) pushdeltatime = 0;
1442 other.lastpushtime = time;
1443 if(!pushdeltatime) return;
1445 // div0: ticrate independent, 1 = identity (not 20)
1446 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1447 UpdateCSQCProjectile(other);
1450 // Spherical (gravity/repulsor) mode
1451 void trigger_impulse_touch3()
1453 float pushdeltatime;
1456 if (self.active != ACTIVE_ACTIVE)
1459 // FIXME: Better checking for what to push and not.
1460 if not(other.iscreature)
1461 if (other.classname != "corpse")
1462 if (other.classname != "body")
1463 if (other.classname != "gib")
1464 if (other.classname != "missile")
1465 if (other.classname != "rocket")
1466 if (other.classname != "casing")
1467 if (other.classname != "grenade")
1468 if (other.classname != "plasma")
1469 if (other.classname != "plasma_prim")
1470 if (other.classname != "plasma_chain")
1471 if (other.classname != "droppedweapon")
1472 if (other.classname != "nexball_basketball")
1473 if (other.classname != "nexball_football")
1476 if (other.deadflag && other.iscreature)
1481 pushdeltatime = time - other.lastpushtime;
1482 if (pushdeltatime > 0.15) pushdeltatime = 0;
1483 other.lastpushtime = time;
1484 if(!pushdeltatime) return;
1486 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1488 str = min(self.radius, vlen(self.origin - other.origin));
1490 if(self.falloff == 1)
1491 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1492 else if(self.falloff == 2)
1493 str = (str / self.radius) * self.strength; // 0 in the inside
1495 str = self.strength;
1497 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1498 UpdateCSQCProjectile(other);
1501 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1502 -------- KEYS --------
1503 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1504 If not, this trigger acts like a damper/accelerator field.
1506 strength : This is how mutch force to add in the direction of .target each second
1507 when .target is set. If not, this is hoe mutch to slow down/accelerate
1508 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1510 radius : If set, act as a spherical device rather then a liniar one.
1512 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1514 -------- NOTES --------
1515 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1516 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1519 void spawnfunc_trigger_impulse()
1521 self.active = ACTIVE_ACTIVE;
1526 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1527 setorigin(self, self.origin);
1528 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1529 self.touch = trigger_impulse_touch3;
1535 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1536 self.touch = trigger_impulse_touch1;
1540 if(!self.strength) self.strength = 0.9;
1541 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1542 self.touch = trigger_impulse_touch2;
1547 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1548 "Flip-flop" trigger gate... lets only every second trigger event through
1552 self.state = !self.state;
1557 void spawnfunc_trigger_flipflop()
1559 if(self.spawnflags & 1)
1561 self.use = flipflop_use;
1562 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1565 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1566 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1570 self.nextthink = time + self.wait;
1571 self.enemy = activator;
1577 void monoflop_fixed_use()
1581 self.nextthink = time + self.wait;
1583 self.enemy = activator;
1587 void monoflop_think()
1590 activator = self.enemy;
1594 void monoflop_reset()
1600 void spawnfunc_trigger_monoflop()
1604 if(self.spawnflags & 1)
1605 self.use = monoflop_fixed_use;
1607 self.use = monoflop_use;
1608 self.think = monoflop_think;
1610 self.reset = monoflop_reset;
1613 void multivibrator_send()
1618 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1620 newstate = (time < cyclestart + self.wait);
1623 if(self.state != newstate)
1625 self.state = newstate;
1628 self.nextthink = cyclestart + self.wait + 0.01;
1630 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1633 void multivibrator_toggle()
1635 if(self.nextthink == 0)
1637 multivibrator_send();
1650 void multivibrator_reset()
1652 if(!(self.spawnflags & 1))
1653 self.nextthink = 0; // wait for a trigger event
1655 self.nextthink = max(1, time);
1658 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1659 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1660 -------- KEYS --------
1661 target: trigger all entities with this targetname when it goes off
1662 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1663 phase: offset of the timing
1664 wait: "on" cycle time (default: 1)
1665 respawntime: "off" cycle time (default: same as wait)
1666 -------- SPAWNFLAGS --------
1667 START_ON: assume it is already turned on (when targeted)
1669 void spawnfunc_trigger_multivibrator()
1673 if(!self.respawntime)
1674 self.respawntime = self.wait;
1677 self.use = multivibrator_toggle;
1678 self.think = multivibrator_send;
1679 self.nextthink = time;
1682 multivibrator_reset();
1691 if(self.killtarget != "")
1692 src = find(world, targetname, self.killtarget);
1693 if(self.target != "")
1694 dst = find(world, targetname, self.target);
1698 objerror("follow: could not find target/killtarget");
1704 // already done :P entity must stay
1708 else if(!src || !dst)
1710 objerror("follow: could not find target/killtarget");
1713 else if(self.spawnflags & 1)
1716 if(self.spawnflags & 2)
1718 setattachment(dst, src, self.message);
1722 attach_sameorigin(dst, src, self.message);
1725 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1730 if(self.spawnflags & 2)
1732 dst.movetype = MOVETYPE_FOLLOW;
1734 // dst.punchangle = '0 0 0'; // keep unchanged
1735 dst.view_ofs = dst.origin;
1736 dst.v_angle = dst.angles;
1740 follow_sameorigin(dst, src);
1747 void spawnfunc_misc_follow()
1749 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1754 void gamestart_use() {
1760 void spawnfunc_trigger_gamestart() {
1761 self.use = gamestart_use;
1762 self.reset2 = spawnfunc_trigger_gamestart;
1766 self.think = self.use;
1767 self.nextthink = game_starttime + self.wait;
1770 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1776 .entity voicescript; // attached voice script
1777 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1778 .float voicescript_nextthink; // time to play next voice
1779 .float voicescript_voiceend; // time when this voice ends
1781 void target_voicescript_clear(entity pl)
1783 pl.voicescript = world;
1786 void target_voicescript_use()
1788 if(activator.voicescript != self)
1790 activator.voicescript = self;
1791 activator.voicescript_index = 0;
1792 activator.voicescript_nextthink = time + self.delay;
1796 void target_voicescript_next(entity pl)
1801 vs = pl.voicescript;
1804 if(vs.message == "")
1806 if(pl.classname != "player")
1811 if(time >= pl.voicescript_voiceend)
1813 if(time >= pl.voicescript_nextthink)
1815 // get the next voice...
1816 n = tokenize_console(vs.message);
1818 if(pl.voicescript_index < vs.cnt)
1819 i = pl.voicescript_index * 2;
1820 else if(n > vs.cnt * 2)
1821 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1827 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1828 dt = stof(argv(i + 1));
1831 pl.voicescript_voiceend = time + dt;
1832 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1836 pl.voicescript_voiceend = time - dt;
1837 pl.voicescript_nextthink = pl.voicescript_voiceend;
1840 pl.voicescript_index += 1;
1844 pl.voicescript = world; // stop trying then
1850 void spawnfunc_target_voicescript()
1852 // netname: directory of the sound files
1853 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1854 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1855 // Here, a - in front of the duration means that no delay is to be
1856 // added after this message
1857 // wait: average time between messages
1858 // delay: initial delay before the first message
1861 self.use = target_voicescript_use;
1863 n = tokenize_console(self.message);
1865 for(i = 0; i+1 < n; i += 2)
1872 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1878 void trigger_relay_teamcheck_use()
1882 if(self.spawnflags & 2)
1884 if(activator.team != self.team)
1889 if(activator.team == self.team)
1895 if(self.spawnflags & 1)
1900 void trigger_relay_teamcheck_reset()
1902 self.team = self.team_saved;
1905 void spawnfunc_trigger_relay_teamcheck()
1907 self.team_saved = self.team;
1908 self.use = trigger_relay_teamcheck_use;
1909 self.reset = trigger_relay_teamcheck_reset;
1914 void trigger_disablerelay_use()
1921 for(e = world; (e = find(e, targetname, self.target)); )
1923 if(e.use == SUB_UseTargets)
1925 e.use = SUB_DontUseTargets;
1928 else if(e.use == SUB_DontUseTargets)
1930 e.use = SUB_UseTargets;
1936 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1939 void spawnfunc_trigger_disablerelay()
1941 self.use = trigger_disablerelay_use;
1944 float magicear_matched;
1945 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1947 float domatch, dotrigger, matchstart, l;
1951 magicear_matched = FALSE;
1953 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1954 domatch = ((ear.spawnflags & 32) || dotrigger);
1960 if(ear.spawnflags & 4)
1966 if(ear.spawnflags & 1)
1969 if(ear.spawnflags & 2)
1972 if(ear.spawnflags & 8)
1977 l = strlen(ear.message);
1979 if(self.spawnflags & 128)
1982 msg = strdecolorize(msgin);
1984 if(substring(ear.message, 0, 1) == "*")
1986 if(substring(ear.message, -1, 1) == "*")
1989 // as we need multi-replacement here...
1990 s = substring(ear.message, 1, -2);
1992 if(strstrofs(msg, s, 0) >= 0)
1993 matchstart = -2; // we use strreplace on s
1998 s = substring(ear.message, 1, -1);
2000 if(substring(msg, -l, l) == s)
2001 matchstart = strlen(msg) - l;
2006 if(substring(ear.message, -1, 1) == "*")
2009 s = substring(ear.message, 0, -2);
2011 if(substring(msg, 0, l) == s)
2018 if(msg == ear.message)
2023 if(matchstart == -1) // no match
2026 magicear_matched = TRUE;
2030 oldself = activator = self;
2036 if(ear.spawnflags & 16)
2040 else if(ear.netname != "")
2043 return strreplace(s, ear.netname, msg);
2046 substring(msg, 0, matchstart),
2048 substring(msg, matchstart + l, -1)
2056 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2060 for(ear = magicears; ear; ear = ear.enemy)
2062 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2063 if not(ear.spawnflags & 64)
2064 if(magicear_matched)
2071 void spawnfunc_trigger_magicear()
2073 self.enemy = magicears;
2076 // actually handled in "say" processing
2079 // 2 = ignore teamsay
2081 // 8 = ignore tell to unknown player
2082 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2083 // 32 = perform the replacement even if outside the radius or dead
2084 // 64 = continue replacing/triggering even if this one matched
2094 // if set, replacement for the matched text
2096 // "hearing distance"
2101 void relay_activators_use()
2107 for(trg = world; (trg = find(trg, targetname, os.target)); )
2111 trg.setactive(os.cnt);
2114 //bprint("Not using setactive\n");
2115 if(os.cnt == ACTIVE_TOGGLE)
2116 if(trg.active == ACTIVE_ACTIVE)
2117 trg.active = ACTIVE_NOT;
2119 trg.active = ACTIVE_ACTIVE;
2121 trg.active = os.cnt;
2127 void spawnfunc_relay_activate()
2129 self.cnt = ACTIVE_ACTIVE;
2130 self.use = relay_activators_use;
2133 void spawnfunc_relay_deactivate()
2135 self.cnt = ACTIVE_NOT;
2136 self.use = relay_activators_use;
2139 void spawnfunc_relay_activatetoggle()
2141 self.cnt = ACTIVE_TOGGLE;
2142 self.use = relay_activators_use;
2145 .string chmap, gametype;
2146 void spawnfunc_target_changelevel_use()
2148 if(self.gametype != "")
2149 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2151 if (self.chmap == "")
2152 localcmd("endmatch\n");
2154 localcmd(strcat("changelevel ", self.chmap, "\n"));
2157 void spawnfunc_target_changelevel()
2159 self.use = spawnfunc_target_changelevel_use;