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;
61 if (activator.classname == "player" && self.message != "")
63 if(clienttype(activator) == CLIENTTYPE_REAL)
65 centerprint (activator, self.message);
67 play2(activator, "misc/talk.wav");
72 // kill the killtagets
77 for(t = world; (t = find(t, targetname, s)); )
88 for(i = 0; i < 4; ++i)
93 case 0: s = stemp.target; break;
94 case 1: s = stemp.target2; break;
95 case 2: s = stemp.target3; break;
96 case 3: s = stemp.target4; break;
100 for(t = world; (t = find(t, targetname, s)); )
103 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
118 //=============================================================================
120 float SPAWNFLAG_NOMESSAGE = 1;
121 float SPAWNFLAG_NOTOUCH = 1;
123 // the wait time has passed, so set back up for another activation
128 self.health = self.max_health;
129 self.takedamage = DAMAGE_YES;
130 self.solid = SOLID_BBOX;
135 // the trigger was just touched/killed/used
136 // self.enemy should be set to the activator so it can be held through a delay
137 // so wait for the delay time before firing
140 if (self.nextthink > time)
142 return; // allready been triggered
145 if (self.classname == "trigger_secret")
147 if (self.enemy.classname != "player")
149 found_secrets = found_secrets + 1;
150 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
154 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
156 // don't trigger again until reset
157 self.takedamage = DAMAGE_NO;
159 activator = self.enemy;
160 other = self.goalentity;
165 self.think = multi_wait;
166 self.nextthink = time + self.wait;
168 else if (self.wait == 0)
170 multi_wait(); // waiting finished
173 { // we can't just remove (self) here, because this is a touch function
174 // called wheil C code is looping through area links...
175 self.touch = SUB_Null;
181 self.goalentity = other;
182 self.enemy = activator;
188 if not(self.spawnflags & 2)
190 if not(other.iscreature)
194 if(self.team == other.team)
198 // if the trigger has an angles field, check player's facing direction
199 if (self.movedir != '0 0 0')
201 makevectors (other.angles);
202 if (v_forward * self.movedir < 0)
203 return; // not facing the right way
209 self.goalentity = other;
213 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
215 if (!self.takedamage)
217 if(self.spawnflags & DOOR_NOSPLASH)
218 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
220 self.health = self.health - damage;
221 if (self.health <= 0)
223 self.enemy = attacker;
224 self.goalentity = inflictor;
231 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
232 self.touch = multi_touch;
235 self.health = self.max_health;
236 self.takedamage = DAMAGE_YES;
237 self.solid = SOLID_BBOX;
239 self.think = SUB_Null;
240 self.team = self.team_saved;
243 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
244 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.
245 If "delay" is set, the trigger waits some time after activating before firing.
246 "wait" : Seconds between triggerings. (.2 default)
247 If notouch is set, the trigger is only fired by other entities, not by touching.
248 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
254 set "message" to text string
256 void spawnfunc_trigger_multiple()
258 self.reset = multi_reset;
259 if (self.sounds == 1)
261 precache_sound ("misc/secret.wav");
262 self.noise = "misc/secret.wav";
264 else if (self.sounds == 2)
266 precache_sound ("misc/talk.wav");
267 self.noise = "misc/talk.wav";
269 else if (self.sounds == 3)
271 precache_sound ("misc/trigger1.wav");
272 self.noise = "misc/trigger1.wav";
277 else if(self.wait < -1)
279 self.use = multi_use;
283 self.team_saved = self.team;
287 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
288 objerror ("health and notouch don't make sense\n");
289 self.max_health = self.health;
290 self.event_damage = multi_eventdamage;
291 self.takedamage = DAMAGE_YES;
292 self.solid = SOLID_BBOX;
293 setorigin (self, self.origin); // make sure it links into the world
297 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
299 self.touch = multi_touch;
300 setorigin (self, self.origin); // make sure it links into the world
306 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
307 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
308 "targetname". If "health" is set, the trigger must be killed to activate.
309 If notouch is set, the trigger is only fired by other entities, not by touching.
310 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
311 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.
317 set "message" to text string
319 void spawnfunc_trigger_once()
322 spawnfunc_trigger_multiple();
325 //=============================================================================
327 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
328 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
330 void spawnfunc_trigger_relay()
332 self.use = SUB_UseTargets;
333 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
338 self.think = SUB_UseTargets;
339 self.nextthink = self.wait;
344 self.think = SUB_Null;
347 void spawnfunc_trigger_delay()
352 self.use = delay_use;
353 self.reset = delay_reset;
356 //=============================================================================
361 self.count = self.count - 1;
367 if (activator.classname == "player"
368 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
371 centerprint (activator, "There are more to go...");
372 else if (self.count == 3)
373 centerprint (activator, "Only 3 more to go...");
374 else if (self.count == 2)
375 centerprint (activator, "Only 2 more to go...");
377 centerprint (activator, "Only 1 more to go...");
382 if (activator.classname == "player"
383 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
384 centerprint(activator, "Sequence completed!");
385 self.enemy = activator;
391 self.count = self.cnt;
395 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
396 Acts as an intermediary for an action that takes multiple inputs.
398 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
400 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
402 void spawnfunc_trigger_counter()
407 self.cnt = self.count;
409 self.use = counter_use;
410 self.reset = counter_reset;
413 .float triggerhurttime;
414 void trigger_hurt_touch()
416 if (self.active != ACTIVE_ACTIVE)
420 if((self.spawnflags & 4 == 0) == (self.team != other.team))
423 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
424 if (other.iscreature)
426 if (other.takedamage)
427 if (other.triggerhurttime < time)
430 other.triggerhurttime = time + 1;
431 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
438 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
441 other.pain_finished = min(other.pain_finished, time + 2);
443 else if (other.classname == "rune") // reset runes
446 other.nextthink = min(other.nextthink, time + 1);
454 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
455 Any object touching this will be hurt
456 set dmg to damage amount
459 .entity trigger_hurt_next;
460 entity trigger_hurt_last;
461 entity trigger_hurt_first;
462 void spawnfunc_trigger_hurt()
465 self.active = ACTIVE_ACTIVE;
466 self.touch = trigger_hurt_touch;
470 self.message = "was in the wrong place";
472 self.message2 = "was thrown into a world of hurt by";
473 // self.message = "someone like %s always gets wrongplaced";
475 if(!trigger_hurt_first)
476 trigger_hurt_first = self;
477 if(trigger_hurt_last)
478 trigger_hurt_last.trigger_hurt_next = self;
479 trigger_hurt_last = self;
482 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
486 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
487 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
493 //////////////////////////////////////////////////////////////
497 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
499 //////////////////////////////////////////////////////////////
501 .float triggerhealtime;
502 void trigger_heal_touch()
504 if (self.active != ACTIVE_ACTIVE)
507 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
508 if (other.iscreature)
510 if (other.takedamage)
511 if (other.triggerhealtime < time)
514 other.triggerhealtime = time + 1;
516 if (other.health < self.max_health)
518 other.health = min(other.health + self.health, self.max_health);
519 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
520 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
526 void spawnfunc_trigger_heal()
528 self.active = ACTIVE_ACTIVE;
531 self.touch = trigger_heal_touch;
534 if (!self.max_health)
535 self.max_health = 200; //Max health topoff for field
537 self.noise = "misc/mediumhealth.wav";
538 precache_sound(self.noise);
542 //////////////////////////////////////////////////////////////
548 //////////////////////////////////////////////////////////////
550 .entity trigger_gravity_check;
551 void trigger_gravity_remove(entity own)
553 if(own.trigger_gravity_check.owner == own)
555 UpdateCSQCProjectile(own);
556 own.gravity = own.trigger_gravity_check.gravity;
557 remove(own.trigger_gravity_check);
560 backtrace("Removing a trigger_gravity_check with no valid owner");
561 own.trigger_gravity_check = world;
563 void trigger_gravity_check_think()
565 // This spawns when a player enters the gravity zone and checks if he left.
566 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
567 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
570 if(self.owner.trigger_gravity_check == self)
571 trigger_gravity_remove(self.owner);
579 self.nextthink = time;
583 void trigger_gravity_use()
585 self.state = !self.state;
588 void trigger_gravity_touch()
592 if(self.state != TRUE)
599 if not(self.spawnflags & 1)
601 if(other.trigger_gravity_check)
603 if(self == other.trigger_gravity_check.enemy)
606 other.trigger_gravity_check.count = 2; // gravity one more frame...
611 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
612 trigger_gravity_remove(other);
616 other.trigger_gravity_check = spawn();
617 other.trigger_gravity_check.enemy = self;
618 other.trigger_gravity_check.owner = other;
619 other.trigger_gravity_check.gravity = other.gravity;
620 other.trigger_gravity_check.think = trigger_gravity_check_think;
621 other.trigger_gravity_check.nextthink = time;
622 other.trigger_gravity_check.count = 2;
627 if (other.gravity != g)
631 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
632 UpdateCSQCProjectile(self.owner);
636 void spawnfunc_trigger_gravity()
638 if(self.gravity == 1)
642 self.touch = trigger_gravity_touch;
644 precache_sound(self.noise);
649 self.use = trigger_gravity_use;
650 if(self.spawnflags & 2)
655 //=============================================================================
657 // TODO add a way to do looped sounds with sound(); then complete this entity
658 .float volume, atten;
659 void target_speaker_use_off();
660 void target_speaker_use_activator()
662 if(clienttype(activator) != CLIENTTYPE_REAL)
665 if(substring(self.noise, 0, 1) == "*")
668 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
669 if(GetPlayerSoundSampleField_notFound)
670 snd = "misc/null.wav";
671 else if(activator.sample == "")
672 snd = "misc/null.wav";
675 tokenize_console(activator.sample);
679 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
681 snd = strcat(argv(0), ".wav"); // randomization
686 msg_entity = activator;
687 soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
689 void target_speaker_use_on()
692 if(substring(self.noise, 0, 1) == "*")
695 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
696 if(GetPlayerSoundSampleField_notFound)
697 snd = "misc/null.wav";
698 else if(activator.sample == "")
699 snd = "misc/null.wav";
702 tokenize_console(activator.sample);
706 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
708 snd = strcat(argv(0), ".wav"); // randomization
713 sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
714 if(self.spawnflags & 3)
715 self.use = target_speaker_use_off;
717 void target_speaker_use_off()
719 sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
720 self.use = target_speaker_use_on;
722 void target_speaker_reset()
724 if(self.spawnflags & 1) // LOOPED_ON
726 if(self.use == target_speaker_use_on)
727 target_speaker_use_on();
729 else if(self.spawnflags & 2)
731 if(self.use == target_speaker_use_off)
732 target_speaker_use_off();
736 void spawnfunc_target_speaker()
738 // TODO: "*" prefix to sound file name
739 // TODO: wait and random (just, HOW? random is not a field)
741 precache_sound (self.noise);
743 if(!self.atten && !(self.spawnflags & 4))
746 self.atten = ATTN_NORM;
748 self.atten = ATTN_STATIC;
750 else if(self.atten < 0)
758 if(self.spawnflags & 8) // ACTIVATOR
759 self.use = target_speaker_use_activator;
760 else if(self.spawnflags & 1) // LOOPED_ON
762 target_speaker_use_on();
763 self.reset = target_speaker_reset;
765 else if(self.spawnflags & 2) // LOOPED_OFF
767 self.use = target_speaker_use_on;
768 self.reset = target_speaker_reset;
771 self.use = target_speaker_use_on;
773 else if(self.spawnflags & 1) // LOOPED_ON
775 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
778 else if(self.spawnflags & 2) // LOOPED_OFF
780 objerror("This sound entity can never be activated");
784 // Quake/Nexuiz fallback
785 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
791 void spawnfunc_func_stardust() {
792 self.effects = EF_STARDUST;
796 .float bgmscriptattack;
797 .float bgmscriptdecay;
798 .float bgmscriptsustain;
799 .float bgmscriptrelease;
800 float pointparticles_SendEntity(entity to, float fl)
802 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
804 // optional features to save space
806 if(self.spawnflags & 2)
807 fl |= 0x10; // absolute count on toggle-on
808 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
809 fl |= 0x20; // 4 bytes - saves CPU
810 if(self.waterlevel || self.count != 1)
811 fl |= 0x40; // 4 bytes - obscure features almost never used
812 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
813 fl |= 0x80; // 14 bytes - saves lots of space
815 WriteByte(MSG_ENTITY, fl);
819 WriteCoord(MSG_ENTITY, self.impulse);
821 WriteCoord(MSG_ENTITY, 0); // off
825 WriteCoord(MSG_ENTITY, self.origin_x);
826 WriteCoord(MSG_ENTITY, self.origin_y);
827 WriteCoord(MSG_ENTITY, self.origin_z);
831 if(self.model != "null")
833 WriteShort(MSG_ENTITY, self.modelindex);
836 WriteCoord(MSG_ENTITY, self.mins_x);
837 WriteCoord(MSG_ENTITY, self.mins_y);
838 WriteCoord(MSG_ENTITY, self.mins_z);
839 WriteCoord(MSG_ENTITY, self.maxs_x);
840 WriteCoord(MSG_ENTITY, self.maxs_y);
841 WriteCoord(MSG_ENTITY, self.maxs_z);
846 WriteShort(MSG_ENTITY, 0);
849 WriteCoord(MSG_ENTITY, self.maxs_x);
850 WriteCoord(MSG_ENTITY, self.maxs_y);
851 WriteCoord(MSG_ENTITY, self.maxs_z);
854 WriteShort(MSG_ENTITY, self.cnt);
857 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
858 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
862 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
863 WriteByte(MSG_ENTITY, self.count * 16.0);
865 WriteString(MSG_ENTITY, self.noise);
868 WriteByte(MSG_ENTITY, floor(self.atten * 64));
869 WriteByte(MSG_ENTITY, floor(self.volume * 255));
871 WriteString(MSG_ENTITY, self.bgmscript);
872 if(self.bgmscript != "")
874 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
875 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
876 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
877 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
883 void pointparticles_use()
885 self.state = !self.state;
889 void pointparticles_think()
891 if(self.origin != self.oldorigin)
894 self.oldorigin = self.origin;
896 self.nextthink = time;
899 void pointparticles_reset()
901 if(self.spawnflags & 1)
907 void spawnfunc_func_pointparticles()
910 setmodel(self, self.model);
912 precache_sound (self.noise);
914 if(!self.bgmscriptsustain)
915 self.bgmscriptsustain = 1;
916 else if(self.bgmscriptsustain < 0)
917 self.bgmscriptsustain = 0;
920 self.atten = ATTN_NORM;
921 else if(self.atten < 0)
932 setorigin(self, self.origin + self.mins);
933 setsize(self, '0 0 0', self.maxs - self.mins);
936 self.cnt = particleeffectnum(self.mdl);
938 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
942 self.use = pointparticles_use;
943 self.reset = pointparticles_reset;
948 self.think = pointparticles_think;
949 self.nextthink = time;
952 void spawnfunc_func_sparks()
954 // self.cnt is the amount of sparks that one burst will spawn
956 self.cnt = 25.0; // nice default value
959 // self.wait is the probability that a sparkthink will spawn a spark shower
960 // range: 0 - 1, but 0 makes little sense, so...
961 if(self.wait < 0.05) {
962 self.wait = 0.25; // nice default value
965 self.count = self.cnt;
968 self.velocity = '0 0 -1';
969 self.mdl = "TE_SPARK";
970 self.impulse = 10 * self.wait; // by default 2.5/sec
972 self.cnt = 0; // use mdl
974 spawnfunc_func_pointparticles();
977 float rainsnow_SendEntity(entity to, float sf)
979 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
980 WriteByte(MSG_ENTITY, self.state);
981 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
982 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
983 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
984 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
985 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
986 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
987 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
988 WriteShort(MSG_ENTITY, self.count);
989 WriteByte(MSG_ENTITY, self.cnt);
993 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
994 This is an invisible area like a trigger, which rain falls inside of.
998 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1000 sets color of rain (default 12 - white)
1002 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1004 void spawnfunc_func_rain()
1006 self.dest = self.velocity;
1007 self.velocity = '0 0 0';
1009 self.dest = '0 0 -700';
1010 self.angles = '0 0 0';
1011 self.movetype = MOVETYPE_NONE;
1012 self.solid = SOLID_NOT;
1013 SetBrushEntityModel();
1018 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1021 if(self.count > 65535)
1024 self.state = 1; // 1 is rain, 0 is snow
1027 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1031 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1032 This is an invisible area like a trigger, which snow falls inside of.
1036 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1038 sets color of rain (default 12 - white)
1040 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1042 void spawnfunc_func_snow()
1044 self.dest = self.velocity;
1045 self.velocity = '0 0 0';
1047 self.dest = '0 0 -300';
1048 self.angles = '0 0 0';
1049 self.movetype = MOVETYPE_NONE;
1050 self.solid = SOLID_NOT;
1051 SetBrushEntityModel();
1056 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1059 if(self.count > 65535)
1062 self.state = 0; // 1 is rain, 0 is snow
1065 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1069 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1072 void misc_laser_aim()
1077 if(self.spawnflags & 2)
1079 if(self.enemy.origin != self.mangle)
1081 self.mangle = self.enemy.origin;
1082 self.SendFlags |= 2;
1087 a = vectoangles(self.enemy.origin - self.origin);
1089 if(a != self.mangle)
1092 self.SendFlags |= 2;
1098 if(self.angles != self.mangle)
1100 self.mangle = self.angles;
1101 self.SendFlags |= 2;
1104 if(self.origin != self.oldorigin)
1106 self.SendFlags |= 1;
1107 self.oldorigin = self.origin;
1111 void misc_laser_init()
1113 if(self.target != "")
1114 self.enemy = find(world, targetname, self.target);
1118 void misc_laser_think()
1125 self.nextthink = time;
1134 o = self.enemy.origin;
1135 if not(self.spawnflags & 2)
1136 o = self.origin + normalize(o - self.origin) * 32768;
1140 makevectors(self.mangle);
1141 o = self.origin + v_forward * 32768;
1144 if(self.dmg || self.enemy.target != "")
1146 traceline(self.origin, o, MOVE_NORMAL, self);
1149 hitloc = trace_endpos;
1151 if(self.enemy.target != "") // DETECTOR laser
1153 if(trace_ent.iscreature)
1155 self.pusher = hitent;
1162 activator = self.pusher;
1175 activator = self.pusher;
1185 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1187 if(hitent.takedamage)
1188 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1192 float laser_SendEntity(entity to, float fl)
1194 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1195 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1196 if(self.spawnflags & 2)
1200 if(self.scale != 1 || self.modelscale != 1)
1202 if(self.spawnflags & 4)
1204 WriteByte(MSG_ENTITY, fl);
1207 WriteCoord(MSG_ENTITY, self.origin_x);
1208 WriteCoord(MSG_ENTITY, self.origin_y);
1209 WriteCoord(MSG_ENTITY, self.origin_z);
1213 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1214 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1215 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1217 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1220 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1221 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1223 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1224 WriteShort(MSG_ENTITY, self.cnt + 1);
1230 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1231 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1232 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1236 WriteAngle(MSG_ENTITY, self.mangle_x);
1237 WriteAngle(MSG_ENTITY, self.mangle_y);
1241 WriteByte(MSG_ENTITY, self.state);
1245 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1246 Any object touching the beam will be hurt
1249 spawnfunc_target_position where the laser ends
1251 name of beam end effect to use
1253 color of the beam (default: red)
1255 damage per second (-1 for a laser that kills immediately)
1259 self.state = !self.state;
1260 self.SendFlags |= 4;
1266 if(self.spawnflags & 1)
1272 void spawnfunc_misc_laser()
1276 if(self.mdl == "none")
1280 self.cnt = particleeffectnum(self.mdl);
1283 self.cnt = particleeffectnum("laser_deadly");
1289 self.cnt = particleeffectnum("laser_deadly");
1296 if(self.colormod == '0 0 0')
1298 self.colormod = '1 0 0';
1300 self.message = "saw the light";
1302 self.message2 = "was pushed into a laser by";
1305 if(!self.modelscale)
1306 self.modelscale = 1;
1307 else if(self.modelscale < 0)
1308 self.modelscale = 0;
1309 self.think = misc_laser_think;
1310 self.nextthink = time;
1311 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1313 self.mangle = self.angles;
1315 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1319 self.reset = laser_reset;
1321 self.use = laser_use;
1327 // tZorks trigger impulse / gravity
1331 .float lastpushtime;
1333 // targeted (directional) mode
1334 void trigger_impulse_touch1()
1337 float pushdeltatime;
1340 if (self.active != ACTIVE_ACTIVE)
1343 // FIXME: Better checking for what to push and not.
1344 if not(other.iscreature)
1345 if (other.classname != "corpse")
1346 if (other.classname != "body")
1347 if (other.classname != "gib")
1348 if (other.classname != "missile")
1349 if (other.classname != "rocket")
1350 if (other.classname != "casing")
1351 if (other.classname != "grenade")
1352 if (other.classname != "plasma")
1353 if (other.classname != "plasma_prim")
1354 if (other.classname != "plasma_chain")
1355 if (other.classname != "droppedweapon")
1356 if (other.classname != "nexball_basketball")
1357 if (other.classname != "nexball_football")
1360 if (other.deadflag && other.iscreature)
1365 targ = find(world, targetname, self.target);
1368 objerror("trigger_force without a (valid) .target!\n");
1373 if(self.falloff == 1)
1374 str = (str / self.radius) * self.strength;
1375 else if(self.falloff == 2)
1376 str = (1 - (str / self.radius)) * self.strength;
1378 str = self.strength;
1380 pushdeltatime = time - other.lastpushtime;
1381 if (pushdeltatime > 0.15) pushdeltatime = 0;
1382 other.lastpushtime = time;
1383 if(!pushdeltatime) return;
1385 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1386 other.flags &~= FL_ONGROUND;
1387 UpdateCSQCProjectile(other);
1390 // Directionless (accelerator/decelerator) mode
1391 void trigger_impulse_touch2()
1393 float pushdeltatime;
1395 if (self.active != ACTIVE_ACTIVE)
1398 // FIXME: Better checking for what to push and not.
1399 if not(other.iscreature)
1400 if (other.classname != "corpse")
1401 if (other.classname != "body")
1402 if (other.classname != "gib")
1403 if (other.classname != "missile")
1404 if (other.classname != "rocket")
1405 if (other.classname != "casing")
1406 if (other.classname != "grenade")
1407 if (other.classname != "plasma")
1408 if (other.classname != "plasma_prim")
1409 if (other.classname != "plasma_chain")
1410 if (other.classname != "droppedweapon")
1411 if (other.classname != "nexball_basketball")
1412 if (other.classname != "nexball_football")
1415 if (other.deadflag && other.iscreature)
1420 pushdeltatime = time - other.lastpushtime;
1421 if (pushdeltatime > 0.15) pushdeltatime = 0;
1422 other.lastpushtime = time;
1423 if(!pushdeltatime) return;
1425 // div0: ticrate independent, 1 = identity (not 20)
1426 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1427 UpdateCSQCProjectile(other);
1430 // Spherical (gravity/repulsor) mode
1431 void trigger_impulse_touch3()
1433 float pushdeltatime;
1436 if (self.active != ACTIVE_ACTIVE)
1439 // FIXME: Better checking for what to push and not.
1440 if not(other.iscreature)
1441 if (other.classname != "corpse")
1442 if (other.classname != "body")
1443 if (other.classname != "gib")
1444 if (other.classname != "missile")
1445 if (other.classname != "rocket")
1446 if (other.classname != "casing")
1447 if (other.classname != "grenade")
1448 if (other.classname != "plasma")
1449 if (other.classname != "plasma_prim")
1450 if (other.classname != "plasma_chain")
1451 if (other.classname != "droppedweapon")
1452 if (other.classname != "nexball_basketball")
1453 if (other.classname != "nexball_football")
1456 if (other.deadflag && other.iscreature)
1461 pushdeltatime = time - other.lastpushtime;
1462 if (pushdeltatime > 0.15) pushdeltatime = 0;
1463 other.lastpushtime = time;
1464 if(!pushdeltatime) return;
1466 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1468 str = min(self.radius, vlen(self.origin - other.origin));
1470 if(self.falloff == 1)
1471 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1472 else if(self.falloff == 2)
1473 str = (str / self.radius) * self.strength; // 0 in the inside
1475 str = self.strength;
1477 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1478 UpdateCSQCProjectile(other);
1481 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1482 -------- KEYS --------
1483 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1484 If not, this trigger acts like a damper/accelerator field.
1486 strength : This is how mutch force to add in the direction of .target each second
1487 when .target is set. If not, this is hoe mutch to slow down/accelerate
1488 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1490 radius : If set, act as a spherical device rather then a liniar one.
1492 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1494 -------- NOTES --------
1495 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1496 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1499 void spawnfunc_trigger_impulse()
1501 self.active = ACTIVE_ACTIVE;
1506 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1507 setorigin(self, self.origin);
1508 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1509 self.touch = trigger_impulse_touch3;
1515 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1516 self.touch = trigger_impulse_touch1;
1520 if(!self.strength) self.strength = 0.9;
1521 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1522 self.touch = trigger_impulse_touch2;
1527 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1528 "Flip-flop" trigger gate... lets only every second trigger event through
1532 self.state = !self.state;
1537 void spawnfunc_trigger_flipflop()
1539 if(self.spawnflags & 1)
1541 self.use = flipflop_use;
1542 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1545 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1546 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1550 self.nextthink = time + self.wait;
1551 self.enemy = activator;
1557 void monoflop_fixed_use()
1561 self.nextthink = time + self.wait;
1563 self.enemy = activator;
1567 void monoflop_think()
1570 activator = self.enemy;
1574 void monoflop_reset()
1580 void spawnfunc_trigger_monoflop()
1584 if(self.spawnflags & 1)
1585 self.use = monoflop_fixed_use;
1587 self.use = monoflop_use;
1588 self.think = monoflop_think;
1590 self.reset = monoflop_reset;
1593 void multivibrator_send()
1598 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1600 newstate = (time < cyclestart + self.wait);
1603 if(self.state != newstate)
1605 self.state = newstate;
1608 self.nextthink = cyclestart + self.wait + 0.01;
1610 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1613 void multivibrator_toggle()
1615 if(self.nextthink == 0)
1617 multivibrator_send();
1630 void multivibrator_reset()
1632 if(!(self.spawnflags & 1))
1633 self.nextthink = 0; // wait for a trigger event
1635 self.nextthink = max(1, time);
1638 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1639 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1640 -------- KEYS --------
1641 target: trigger all entities with this targetname when it goes off
1642 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1643 phase: offset of the timing
1644 wait: "on" cycle time (default: 1)
1645 respawntime: "off" cycle time (default: same as wait)
1646 -------- SPAWNFLAGS --------
1647 START_ON: assume it is already turned on (when targeted)
1649 void spawnfunc_trigger_multivibrator()
1653 if(!self.respawntime)
1654 self.respawntime = self.wait;
1657 self.use = multivibrator_toggle;
1658 self.think = multivibrator_send;
1659 self.nextthink = time;
1662 multivibrator_reset();
1671 if(self.killtarget != "")
1672 src = find(world, targetname, self.killtarget);
1673 if(self.target != "")
1674 dst = find(world, targetname, self.target);
1678 objerror("follow: could not find target/killtarget");
1684 // already done :P entity must stay
1688 else if(!src || !dst)
1690 objerror("follow: could not find target/killtarget");
1693 else if(self.spawnflags & 1)
1696 if(self.spawnflags & 2)
1698 setattachment(dst, src, self.message);
1702 attach_sameorigin(dst, src, self.message);
1709 if(self.spawnflags & 2)
1711 dst.movetype = MOVETYPE_FOLLOW;
1713 // dst.punchangle = '0 0 0'; // keep unchanged
1714 dst.view_ofs = dst.origin;
1715 dst.v_angle = dst.angles;
1719 follow_sameorigin(dst, src);
1726 void spawnfunc_misc_follow()
1728 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1733 void gamestart_use() {
1739 void spawnfunc_trigger_gamestart() {
1740 self.use = gamestart_use;
1741 self.reset2 = spawnfunc_trigger_gamestart;
1745 self.think = self.use;
1746 self.nextthink = game_starttime + self.wait;
1749 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1755 .entity voicescript; // attached voice script
1756 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1757 .float voicescript_nextthink; // time to play next voice
1758 .float voicescript_voiceend; // time when this voice ends
1760 void target_voicescript_clear(entity pl)
1762 pl.voicescript = world;
1765 void target_voicescript_use()
1767 if(activator.voicescript != self)
1769 activator.voicescript = self;
1770 activator.voicescript_index = 0;
1771 activator.voicescript_nextthink = time + self.delay;
1775 void target_voicescript_next(entity pl)
1780 vs = pl.voicescript;
1783 if(vs.message == "")
1785 if(pl.classname != "player")
1790 if(time >= pl.voicescript_voiceend)
1792 if(time >= pl.voicescript_nextthink)
1794 // get the next voice...
1795 n = tokenize_console(vs.message);
1797 if(pl.voicescript_index < vs.cnt)
1798 i = pl.voicescript_index * 2;
1799 else if(n > vs.cnt * 2)
1800 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1806 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1807 dt = stof(argv(i + 1));
1810 pl.voicescript_voiceend = time + dt;
1811 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1815 pl.voicescript_voiceend = time - dt;
1816 pl.voicescript_nextthink = pl.voicescript_voiceend;
1819 pl.voicescript_index += 1;
1823 pl.voicescript = world; // stop trying then
1829 void spawnfunc_target_voicescript()
1831 // netname: directory of the sound files
1832 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1833 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1834 // Here, a - in front of the duration means that no delay is to be
1835 // added after this message
1836 // wait: average time between messages
1837 // delay: initial delay before the first message
1840 self.use = target_voicescript_use;
1842 n = tokenize_console(self.message);
1844 for(i = 0; i+1 < n; i += 2)
1851 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1857 void trigger_relay_teamcheck_use()
1861 if(self.spawnflags & 2)
1863 if(activator.team != self.team)
1868 if(activator.team == self.team)
1874 if(self.spawnflags & 1)
1879 void trigger_relay_teamcheck_reset()
1881 self.team = self.team_saved;
1884 void spawnfunc_trigger_relay_teamcheck()
1886 self.team_saved = self.team;
1887 self.use = trigger_relay_teamcheck_use;
1888 self.reset = trigger_relay_teamcheck_reset;
1893 void trigger_disablerelay_use()
1900 for(e = world; (e = find(e, targetname, self.target)); )
1902 if(e.use == SUB_UseTargets)
1904 e.use = SUB_DontUseTargets;
1907 else if(e.use == SUB_DontUseTargets)
1909 e.use = SUB_UseTargets;
1915 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1918 void spawnfunc_trigger_disablerelay()
1920 self.use = trigger_disablerelay_use;
1923 float magicear_matched;
1924 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1926 float domatch, dotrigger, matchstart, l;
1930 magicear_matched = FALSE;
1932 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1933 domatch = ((ear.spawnflags & 32) || dotrigger);
1939 if(ear.spawnflags & 4)
1945 if(ear.spawnflags & 1)
1948 if(ear.spawnflags & 2)
1951 if(ear.spawnflags & 8)
1956 l = strlen(ear.message);
1958 if(self.spawnflags & 128)
1961 msg = strdecolorize(msgin);
1963 if(substring(ear.message, 0, 1) == "*")
1965 if(substring(ear.message, -1, 1) == "*")
1968 // as we need multi-replacement here...
1969 s = substring(ear.message, 1, -2);
1971 if(strstrofs(msg, s, 0) >= 0)
1972 matchstart = -2; // we use strreplace on s
1977 s = substring(ear.message, 1, -1);
1979 if(substring(msg, -l, l) == s)
1980 matchstart = strlen(msg) - l;
1985 if(substring(ear.message, -1, 1) == "*")
1988 s = substring(ear.message, 0, -2);
1990 if(substring(msg, 0, l) == s)
1997 if(msg == ear.message)
2002 if(matchstart == -1) // no match
2005 magicear_matched = TRUE;
2009 oldself = activator = self;
2015 if(ear.spawnflags & 16)
2019 else if(ear.netname != "")
2022 return strreplace(s, ear.netname, msg);
2025 substring(msg, 0, matchstart),
2027 substring(msg, matchstart + l, -1)
2035 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2039 for(ear = magicears; ear; ear = ear.enemy)
2041 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2042 if not(ear.spawnflags & 64)
2043 if(magicear_matched)
2050 void spawnfunc_trigger_magicear()
2052 self.enemy = magicears;
2055 // actually handled in "say" processing
2058 // 2 = ignore teamsay
2060 // 8 = ignore tell to unknown player
2061 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2062 // 32 = perform the replacement even if outside the radius or dead
2063 // 64 = continue replacing/triggering even if this one matched
2073 // if set, replacement for the matched text
2075 // "hearing distance"
2080 void relay_activators_use()
2086 for(trg = world; (trg = find(trg, targetname, os.target)); )
2090 trg.setactive(os.cnt);
2093 //bprint("Not using setactive\n");
2094 if(os.cnt == ACTIVE_TOGGLE)
2095 if(trg.active == ACTIVE_ACTIVE)
2096 trg.active = ACTIVE_NOT;
2098 trg.active = ACTIVE_ACTIVE;
2100 trg.active = os.cnt;
2106 void spawnfunc_relay_activate()
2108 self.cnt = ACTIVE_ACTIVE;
2109 self.use = relay_activators_use;
2112 void spawnfunc_relay_deactivate()
2114 self.cnt = ACTIVE_NOT;
2115 self.use = relay_activators_use;
2118 void spawnfunc_relay_activatetoggle()
2120 self.cnt = ACTIVE_TOGGLE;
2121 self.use = relay_activators_use;
2124 .string chmap, gametype;
2125 void spawnfunc_target_changelevel_use()
2127 if(self.gametype != "")
2128 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2130 if (self.chmap == "")
2131 localcmd("endmatch\n");
2133 localcmd(strcat("changelevel ", self.chmap, "\n"));
2136 void spawnfunc_target_changelevel()
2138 self.use = spawnfunc_target_changelevel_use;