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 .float triggerhurttime;
415 void trigger_hurt_touch()
417 if (self.active != ACTIVE_ACTIVE)
421 if((self.spawnflags & 4 == 0) == (self.team != other.team))
424 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
425 if (other.iscreature)
427 if (other.takedamage)
428 if (other.triggerhurttime < time)
431 other.triggerhurttime = time + 1;
432 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
439 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
442 other.pain_finished = min(other.pain_finished, time + 2);
444 else if (other.classname == "rune") // reset runes
447 other.nextthink = min(other.nextthink, time + 1);
455 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
456 Any object touching this will be hurt
457 set dmg to damage amount
460 .entity trigger_hurt_next;
461 entity trigger_hurt_last;
462 entity trigger_hurt_first;
463 void spawnfunc_trigger_hurt()
466 self.active = ACTIVE_ACTIVE;
467 self.touch = trigger_hurt_touch;
471 self.message = "was in the wrong place";
473 self.message2 = "was thrown into a world of hurt by";
474 // self.message = "someone like %s always gets wrongplaced";
476 if(!trigger_hurt_first)
477 trigger_hurt_first = self;
478 if(trigger_hurt_last)
479 trigger_hurt_last.trigger_hurt_next = self;
480 trigger_hurt_last = self;
483 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
487 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
488 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
494 //////////////////////////////////////////////////////////////
498 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
500 //////////////////////////////////////////////////////////////
502 .float triggerhealtime;
503 void trigger_heal_touch()
505 if (self.active != ACTIVE_ACTIVE)
508 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
509 if (other.iscreature)
511 if (other.takedamage)
512 if (other.triggerhealtime < time)
515 other.triggerhealtime = time + 1;
517 if (other.health < self.max_health)
519 other.health = min(other.health + self.health, self.max_health);
520 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
521 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
527 void spawnfunc_trigger_heal()
529 self.active = ACTIVE_ACTIVE;
532 self.touch = trigger_heal_touch;
535 if (!self.max_health)
536 self.max_health = 200; //Max health topoff for field
538 self.noise = "misc/mediumhealth.wav";
539 precache_sound(self.noise);
543 //////////////////////////////////////////////////////////////
549 //////////////////////////////////////////////////////////////
551 .entity trigger_gravity_check;
552 void trigger_gravity_remove(entity own)
554 if(own.trigger_gravity_check.owner == own)
556 UpdateCSQCProjectile(own);
557 own.gravity = own.trigger_gravity_check.gravity;
558 remove(own.trigger_gravity_check);
561 backtrace("Removing a trigger_gravity_check with no valid owner");
562 own.trigger_gravity_check = world;
564 void trigger_gravity_check_think()
566 // This spawns when a player enters the gravity zone and checks if he left.
567 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
568 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
571 if(self.owner.trigger_gravity_check == self)
572 trigger_gravity_remove(self.owner);
580 self.nextthink = time;
584 void trigger_gravity_use()
586 self.state = !self.state;
589 void trigger_gravity_touch()
593 if(self.state != TRUE)
600 if not(self.spawnflags & 1)
602 if(other.trigger_gravity_check)
604 if(self == other.trigger_gravity_check.enemy)
607 other.trigger_gravity_check.count = 2; // gravity one more frame...
612 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
613 trigger_gravity_remove(other);
617 other.trigger_gravity_check = spawn();
618 other.trigger_gravity_check.enemy = self;
619 other.trigger_gravity_check.owner = other;
620 other.trigger_gravity_check.gravity = other.gravity;
621 other.trigger_gravity_check.think = trigger_gravity_check_think;
622 other.trigger_gravity_check.nextthink = time;
623 other.trigger_gravity_check.count = 2;
628 if (other.gravity != g)
632 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
633 UpdateCSQCProjectile(self.owner);
637 void spawnfunc_trigger_gravity()
639 if(self.gravity == 1)
643 self.touch = trigger_gravity_touch;
645 precache_sound(self.noise);
650 self.use = trigger_gravity_use;
651 if(self.spawnflags & 2)
656 //=============================================================================
658 // TODO add a way to do looped sounds with sound(); then complete this entity
659 .float volume, atten;
660 void target_speaker_use_off();
661 void target_speaker_use_activator()
663 if(clienttype(activator) != CLIENTTYPE_REAL)
666 if(substring(self.noise, 0, 1) == "*")
669 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
670 if(GetPlayerSoundSampleField_notFound)
671 snd = "misc/null.wav";
672 else if(activator.sample == "")
673 snd = "misc/null.wav";
676 tokenize_console(activator.sample);
680 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
682 snd = strcat(argv(0), ".wav"); // randomization
687 msg_entity = activator;
688 soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
690 void target_speaker_use_on()
693 if(substring(self.noise, 0, 1) == "*")
696 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
697 if(GetPlayerSoundSampleField_notFound)
698 snd = "misc/null.wav";
699 else if(activator.sample == "")
700 snd = "misc/null.wav";
703 tokenize_console(activator.sample);
707 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
709 snd = strcat(argv(0), ".wav"); // randomization
714 sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
715 if(self.spawnflags & 3)
716 self.use = target_speaker_use_off;
718 void target_speaker_use_off()
720 sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
721 self.use = target_speaker_use_on;
723 void target_speaker_reset()
725 if(self.spawnflags & 1) // LOOPED_ON
727 if(self.use == target_speaker_use_on)
728 target_speaker_use_on();
730 else if(self.spawnflags & 2)
732 if(self.use == target_speaker_use_off)
733 target_speaker_use_off();
737 void spawnfunc_target_speaker()
739 // TODO: "*" prefix to sound file name
740 // TODO: wait and random (just, HOW? random is not a field)
742 precache_sound (self.noise);
744 if(!self.atten && !(self.spawnflags & 4))
747 self.atten = ATTN_NORM;
749 self.atten = ATTN_STATIC;
751 else if(self.atten < 0)
759 if(self.spawnflags & 8) // ACTIVATOR
760 self.use = target_speaker_use_activator;
761 else if(self.spawnflags & 1) // LOOPED_ON
763 target_speaker_use_on();
764 self.reset = target_speaker_reset;
766 else if(self.spawnflags & 2) // LOOPED_OFF
768 self.use = target_speaker_use_on;
769 self.reset = target_speaker_reset;
772 self.use = target_speaker_use_on;
774 else if(self.spawnflags & 1) // LOOPED_ON
776 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
779 else if(self.spawnflags & 2) // LOOPED_OFF
781 objerror("This sound entity can never be activated");
785 // Quake/Nexuiz fallback
786 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
792 void spawnfunc_func_stardust() {
793 self.effects = EF_STARDUST;
797 .float bgmscriptattack;
798 .float bgmscriptdecay;
799 .float bgmscriptsustain;
800 .float bgmscriptrelease;
801 float pointparticles_SendEntity(entity to, float fl)
803 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
805 // optional features to save space
807 if(self.spawnflags & 2)
808 fl |= 0x10; // absolute count on toggle-on
809 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
810 fl |= 0x20; // 4 bytes - saves CPU
811 if(self.waterlevel || self.count != 1)
812 fl |= 0x40; // 4 bytes - obscure features almost never used
813 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
814 fl |= 0x80; // 14 bytes - saves lots of space
816 WriteByte(MSG_ENTITY, fl);
820 WriteCoord(MSG_ENTITY, self.impulse);
822 WriteCoord(MSG_ENTITY, 0); // off
826 WriteCoord(MSG_ENTITY, self.origin_x);
827 WriteCoord(MSG_ENTITY, self.origin_y);
828 WriteCoord(MSG_ENTITY, self.origin_z);
832 if(self.model != "null")
834 WriteShort(MSG_ENTITY, self.modelindex);
837 WriteCoord(MSG_ENTITY, self.mins_x);
838 WriteCoord(MSG_ENTITY, self.mins_y);
839 WriteCoord(MSG_ENTITY, self.mins_z);
840 WriteCoord(MSG_ENTITY, self.maxs_x);
841 WriteCoord(MSG_ENTITY, self.maxs_y);
842 WriteCoord(MSG_ENTITY, self.maxs_z);
847 WriteShort(MSG_ENTITY, 0);
850 WriteCoord(MSG_ENTITY, self.maxs_x);
851 WriteCoord(MSG_ENTITY, self.maxs_y);
852 WriteCoord(MSG_ENTITY, self.maxs_z);
855 WriteShort(MSG_ENTITY, self.cnt);
858 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
859 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
863 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
864 WriteByte(MSG_ENTITY, self.count * 16.0);
866 WriteString(MSG_ENTITY, self.noise);
869 WriteByte(MSG_ENTITY, floor(self.atten * 64));
870 WriteByte(MSG_ENTITY, floor(self.volume * 255));
872 WriteString(MSG_ENTITY, self.bgmscript);
873 if(self.bgmscript != "")
875 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
876 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
877 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
878 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
884 void pointparticles_use()
886 self.state = !self.state;
890 void pointparticles_think()
892 if(self.origin != self.oldorigin)
895 self.oldorigin = self.origin;
897 self.nextthink = time;
900 void pointparticles_reset()
902 if(self.spawnflags & 1)
908 void spawnfunc_func_pointparticles()
911 setmodel(self, self.model);
913 precache_sound (self.noise);
915 if(!self.bgmscriptsustain)
916 self.bgmscriptsustain = 1;
917 else if(self.bgmscriptsustain < 0)
918 self.bgmscriptsustain = 0;
921 self.atten = ATTN_NORM;
922 else if(self.atten < 0)
933 setorigin(self, self.origin + self.mins);
934 setsize(self, '0 0 0', self.maxs - self.mins);
937 self.cnt = particleeffectnum(self.mdl);
939 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
943 self.use = pointparticles_use;
944 self.reset = pointparticles_reset;
949 self.think = pointparticles_think;
950 self.nextthink = time;
953 void spawnfunc_func_sparks()
955 // self.cnt is the amount of sparks that one burst will spawn
957 self.cnt = 25.0; // nice default value
960 // self.wait is the probability that a sparkthink will spawn a spark shower
961 // range: 0 - 1, but 0 makes little sense, so...
962 if(self.wait < 0.05) {
963 self.wait = 0.25; // nice default value
966 self.count = self.cnt;
969 self.velocity = '0 0 -1';
970 self.mdl = "TE_SPARK";
971 self.impulse = 10 * self.wait; // by default 2.5/sec
973 self.cnt = 0; // use mdl
975 spawnfunc_func_pointparticles();
978 float rainsnow_SendEntity(entity to, float sf)
980 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
981 WriteByte(MSG_ENTITY, self.state);
982 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
983 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
984 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
985 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
986 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
987 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
988 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
989 WriteShort(MSG_ENTITY, self.count);
990 WriteByte(MSG_ENTITY, self.cnt);
994 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
995 This is an invisible area like a trigger, which rain falls inside of.
999 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1001 sets color of rain (default 12 - white)
1003 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1005 void spawnfunc_func_rain()
1007 self.dest = self.velocity;
1008 self.velocity = '0 0 0';
1010 self.dest = '0 0 -700';
1011 self.angles = '0 0 0';
1012 self.movetype = MOVETYPE_NONE;
1013 self.solid = SOLID_NOT;
1014 SetBrushEntityModel();
1019 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1022 if(self.count > 65535)
1025 self.state = 1; // 1 is rain, 0 is snow
1028 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1032 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1033 This is an invisible area like a trigger, which snow falls inside of.
1037 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1039 sets color of rain (default 12 - white)
1041 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1043 void spawnfunc_func_snow()
1045 self.dest = self.velocity;
1046 self.velocity = '0 0 0';
1048 self.dest = '0 0 -300';
1049 self.angles = '0 0 0';
1050 self.movetype = MOVETYPE_NONE;
1051 self.solid = SOLID_NOT;
1052 SetBrushEntityModel();
1057 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1060 if(self.count > 65535)
1063 self.state = 0; // 1 is rain, 0 is snow
1066 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1070 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1073 void misc_laser_aim()
1078 if(self.spawnflags & 2)
1080 if(self.enemy.origin != self.mangle)
1082 self.mangle = self.enemy.origin;
1083 self.SendFlags |= 2;
1088 a = vectoangles(self.enemy.origin - self.origin);
1090 if(a != self.mangle)
1093 self.SendFlags |= 2;
1099 if(self.angles != self.mangle)
1101 self.mangle = self.angles;
1102 self.SendFlags |= 2;
1105 if(self.origin != self.oldorigin)
1107 self.SendFlags |= 1;
1108 self.oldorigin = self.origin;
1112 void misc_laser_init()
1114 if(self.target != "")
1115 self.enemy = find(world, targetname, self.target);
1119 void misc_laser_think()
1126 self.nextthink = time;
1135 o = self.enemy.origin;
1136 if not(self.spawnflags & 2)
1137 o = self.origin + normalize(o - self.origin) * 32768;
1141 makevectors(self.mangle);
1142 o = self.origin + v_forward * 32768;
1145 if(self.dmg || self.enemy.target != "")
1147 traceline(self.origin, o, MOVE_NORMAL, self);
1150 hitloc = trace_endpos;
1152 if(self.enemy.target != "") // DETECTOR laser
1154 if(trace_ent.iscreature)
1156 self.pusher = hitent;
1163 activator = self.pusher;
1176 activator = self.pusher;
1186 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1188 if(hitent.takedamage)
1189 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1193 float laser_SendEntity(entity to, float fl)
1195 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1196 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1197 if(self.spawnflags & 2)
1201 if(self.scale != 1 || self.modelscale != 1)
1203 if(self.spawnflags & 4)
1205 WriteByte(MSG_ENTITY, fl);
1208 WriteCoord(MSG_ENTITY, self.origin_x);
1209 WriteCoord(MSG_ENTITY, self.origin_y);
1210 WriteCoord(MSG_ENTITY, self.origin_z);
1214 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1215 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1216 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1218 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1221 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1222 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1224 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1225 WriteShort(MSG_ENTITY, self.cnt + 1);
1231 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1232 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1233 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1237 WriteAngle(MSG_ENTITY, self.mangle_x);
1238 WriteAngle(MSG_ENTITY, self.mangle_y);
1242 WriteByte(MSG_ENTITY, self.state);
1246 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1247 Any object touching the beam will be hurt
1250 spawnfunc_target_position where the laser ends
1252 name of beam end effect to use
1254 color of the beam (default: red)
1256 damage per second (-1 for a laser that kills immediately)
1260 self.state = !self.state;
1261 self.SendFlags |= 4;
1267 if(self.spawnflags & 1)
1273 void spawnfunc_misc_laser()
1277 if(self.mdl == "none")
1281 self.cnt = particleeffectnum(self.mdl);
1284 self.cnt = particleeffectnum("laser_deadly");
1290 self.cnt = particleeffectnum("laser_deadly");
1297 if(self.colormod == '0 0 0')
1299 self.colormod = '1 0 0';
1301 self.message = "saw the light";
1303 self.message2 = "was pushed into a laser by";
1306 if(!self.modelscale)
1307 self.modelscale = 1;
1308 else if(self.modelscale < 0)
1309 self.modelscale = 0;
1310 self.think = misc_laser_think;
1311 self.nextthink = time;
1312 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1314 self.mangle = self.angles;
1316 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1320 self.reset = laser_reset;
1322 self.use = laser_use;
1328 // tZorks trigger impulse / gravity
1332 .float lastpushtime;
1334 // targeted (directional) mode
1335 void trigger_impulse_touch1()
1338 float pushdeltatime;
1341 if (self.active != ACTIVE_ACTIVE)
1344 // FIXME: Better checking for what to push and not.
1345 if not(other.iscreature)
1346 if (other.classname != "corpse")
1347 if (other.classname != "body")
1348 if (other.classname != "gib")
1349 if (other.classname != "missile")
1350 if (other.classname != "rocket")
1351 if (other.classname != "casing")
1352 if (other.classname != "grenade")
1353 if (other.classname != "plasma")
1354 if (other.classname != "plasma_prim")
1355 if (other.classname != "plasma_chain")
1356 if (other.classname != "droppedweapon")
1357 if (other.classname != "nexball_basketball")
1358 if (other.classname != "nexball_football")
1361 if (other.deadflag && other.iscreature)
1366 targ = find(world, targetname, self.target);
1369 objerror("trigger_force without a (valid) .target!\n");
1374 if(self.falloff == 1)
1375 str = (str / self.radius) * self.strength;
1376 else if(self.falloff == 2)
1377 str = (1 - (str / self.radius)) * self.strength;
1379 str = self.strength;
1381 pushdeltatime = time - other.lastpushtime;
1382 if (pushdeltatime > 0.15) pushdeltatime = 0;
1383 other.lastpushtime = time;
1384 if(!pushdeltatime) return;
1386 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1387 other.flags &~= FL_ONGROUND;
1388 UpdateCSQCProjectile(other);
1391 // Directionless (accelerator/decelerator) mode
1392 void trigger_impulse_touch2()
1394 float pushdeltatime;
1396 if (self.active != ACTIVE_ACTIVE)
1399 // FIXME: Better checking for what to push and not.
1400 if not(other.iscreature)
1401 if (other.classname != "corpse")
1402 if (other.classname != "body")
1403 if (other.classname != "gib")
1404 if (other.classname != "missile")
1405 if (other.classname != "rocket")
1406 if (other.classname != "casing")
1407 if (other.classname != "grenade")
1408 if (other.classname != "plasma")
1409 if (other.classname != "plasma_prim")
1410 if (other.classname != "plasma_chain")
1411 if (other.classname != "droppedweapon")
1412 if (other.classname != "nexball_basketball")
1413 if (other.classname != "nexball_football")
1416 if (other.deadflag && other.iscreature)
1421 pushdeltatime = time - other.lastpushtime;
1422 if (pushdeltatime > 0.15) pushdeltatime = 0;
1423 other.lastpushtime = time;
1424 if(!pushdeltatime) return;
1426 // div0: ticrate independent, 1 = identity (not 20)
1427 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1428 UpdateCSQCProjectile(other);
1431 // Spherical (gravity/repulsor) mode
1432 void trigger_impulse_touch3()
1434 float pushdeltatime;
1437 if (self.active != ACTIVE_ACTIVE)
1440 // FIXME: Better checking for what to push and not.
1441 if not(other.iscreature)
1442 if (other.classname != "corpse")
1443 if (other.classname != "body")
1444 if (other.classname != "gib")
1445 if (other.classname != "missile")
1446 if (other.classname != "rocket")
1447 if (other.classname != "casing")
1448 if (other.classname != "grenade")
1449 if (other.classname != "plasma")
1450 if (other.classname != "plasma_prim")
1451 if (other.classname != "plasma_chain")
1452 if (other.classname != "droppedweapon")
1453 if (other.classname != "nexball_basketball")
1454 if (other.classname != "nexball_football")
1457 if (other.deadflag && other.iscreature)
1462 pushdeltatime = time - other.lastpushtime;
1463 if (pushdeltatime > 0.15) pushdeltatime = 0;
1464 other.lastpushtime = time;
1465 if(!pushdeltatime) return;
1467 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1469 str = min(self.radius, vlen(self.origin - other.origin));
1471 if(self.falloff == 1)
1472 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1473 else if(self.falloff == 2)
1474 str = (str / self.radius) * self.strength; // 0 in the inside
1476 str = self.strength;
1478 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1479 UpdateCSQCProjectile(other);
1482 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1483 -------- KEYS --------
1484 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1485 If not, this trigger acts like a damper/accelerator field.
1487 strength : This is how mutch force to add in the direction of .target each second
1488 when .target is set. If not, this is hoe mutch to slow down/accelerate
1489 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1491 radius : If set, act as a spherical device rather then a liniar one.
1493 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1495 -------- NOTES --------
1496 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1497 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1500 void spawnfunc_trigger_impulse()
1502 self.active = ACTIVE_ACTIVE;
1507 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1508 setorigin(self, self.origin);
1509 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1510 self.touch = trigger_impulse_touch3;
1516 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1517 self.touch = trigger_impulse_touch1;
1521 if(!self.strength) self.strength = 0.9;
1522 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1523 self.touch = trigger_impulse_touch2;
1528 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1529 "Flip-flop" trigger gate... lets only every second trigger event through
1533 self.state = !self.state;
1538 void spawnfunc_trigger_flipflop()
1540 if(self.spawnflags & 1)
1542 self.use = flipflop_use;
1543 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1546 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1547 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1551 self.nextthink = time + self.wait;
1552 self.enemy = activator;
1558 void monoflop_fixed_use()
1562 self.nextthink = time + self.wait;
1564 self.enemy = activator;
1568 void monoflop_think()
1571 activator = self.enemy;
1575 void monoflop_reset()
1581 void spawnfunc_trigger_monoflop()
1585 if(self.spawnflags & 1)
1586 self.use = monoflop_fixed_use;
1588 self.use = monoflop_use;
1589 self.think = monoflop_think;
1591 self.reset = monoflop_reset;
1594 void multivibrator_send()
1599 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1601 newstate = (time < cyclestart + self.wait);
1604 if(self.state != newstate)
1606 self.state = newstate;
1609 self.nextthink = cyclestart + self.wait + 0.01;
1611 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1614 void multivibrator_toggle()
1616 if(self.nextthink == 0)
1618 multivibrator_send();
1631 void multivibrator_reset()
1633 if(!(self.spawnflags & 1))
1634 self.nextthink = 0; // wait for a trigger event
1636 self.nextthink = max(1, time);
1639 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1640 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1641 -------- KEYS --------
1642 target: trigger all entities with this targetname when it goes off
1643 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1644 phase: offset of the timing
1645 wait: "on" cycle time (default: 1)
1646 respawntime: "off" cycle time (default: same as wait)
1647 -------- SPAWNFLAGS --------
1648 START_ON: assume it is already turned on (when targeted)
1650 void spawnfunc_trigger_multivibrator()
1654 if(!self.respawntime)
1655 self.respawntime = self.wait;
1658 self.use = multivibrator_toggle;
1659 self.think = multivibrator_send;
1660 self.nextthink = time;
1663 multivibrator_reset();
1672 if(self.killtarget != "")
1673 src = find(world, targetname, self.killtarget);
1674 if(self.target != "")
1675 dst = find(world, targetname, self.target);
1679 objerror("follow: could not find target/killtarget");
1685 // already done :P entity must stay
1689 else if(!src || !dst)
1691 objerror("follow: could not find target/killtarget");
1694 else if(self.spawnflags & 1)
1697 if(self.spawnflags & 2)
1699 setattachment(dst, src, self.message);
1703 attach_sameorigin(dst, src, self.message);
1710 if(self.spawnflags & 2)
1712 dst.movetype = MOVETYPE_FOLLOW;
1714 // dst.punchangle = '0 0 0'; // keep unchanged
1715 dst.view_ofs = dst.origin;
1716 dst.v_angle = dst.angles;
1720 follow_sameorigin(dst, src);
1727 void spawnfunc_misc_follow()
1729 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1734 void gamestart_use() {
1740 void spawnfunc_trigger_gamestart() {
1741 self.use = gamestart_use;
1742 self.reset2 = spawnfunc_trigger_gamestart;
1746 self.think = self.use;
1747 self.nextthink = game_starttime + self.wait;
1750 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1756 .entity voicescript; // attached voice script
1757 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1758 .float voicescript_nextthink; // time to play next voice
1759 .float voicescript_voiceend; // time when this voice ends
1761 void target_voicescript_clear(entity pl)
1763 pl.voicescript = world;
1766 void target_voicescript_use()
1768 if(activator.voicescript != self)
1770 activator.voicescript = self;
1771 activator.voicescript_index = 0;
1772 activator.voicescript_nextthink = time + self.delay;
1776 void target_voicescript_next(entity pl)
1781 vs = pl.voicescript;
1784 if(vs.message == "")
1786 if(pl.classname != "player")
1791 if(time >= pl.voicescript_voiceend)
1793 if(time >= pl.voicescript_nextthink)
1795 // get the next voice...
1796 n = tokenize_console(vs.message);
1798 if(pl.voicescript_index < vs.cnt)
1799 i = pl.voicescript_index * 2;
1800 else if(n > vs.cnt * 2)
1801 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1807 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1808 dt = stof(argv(i + 1));
1811 pl.voicescript_voiceend = time + dt;
1812 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1816 pl.voicescript_voiceend = time - dt;
1817 pl.voicescript_nextthink = pl.voicescript_voiceend;
1820 pl.voicescript_index += 1;
1824 pl.voicescript = world; // stop trying then
1830 void spawnfunc_target_voicescript()
1832 // netname: directory of the sound files
1833 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1834 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1835 // Here, a - in front of the duration means that no delay is to be
1836 // added after this message
1837 // wait: average time between messages
1838 // delay: initial delay before the first message
1841 self.use = target_voicescript_use;
1843 n = tokenize_console(self.message);
1845 for(i = 0; i+1 < n; i += 2)
1852 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1858 void trigger_relay_teamcheck_use()
1862 if(self.spawnflags & 2)
1864 if(activator.team != self.team)
1869 if(activator.team == self.team)
1875 if(self.spawnflags & 1)
1880 void trigger_relay_teamcheck_reset()
1882 self.team = self.team_saved;
1885 void spawnfunc_trigger_relay_teamcheck()
1887 self.team_saved = self.team;
1888 self.use = trigger_relay_teamcheck_use;
1889 self.reset = trigger_relay_teamcheck_reset;
1894 void trigger_disablerelay_use()
1901 for(e = world; (e = find(e, targetname, self.target)); )
1903 if(e.use == SUB_UseTargets)
1905 e.use = SUB_DontUseTargets;
1908 else if(e.use == SUB_DontUseTargets)
1910 e.use = SUB_UseTargets;
1916 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1919 void spawnfunc_trigger_disablerelay()
1921 self.use = trigger_disablerelay_use;
1924 float magicear_matched;
1925 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1927 float domatch, dotrigger, matchstart, l;
1931 magicear_matched = FALSE;
1933 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1934 domatch = ((ear.spawnflags & 32) || dotrigger);
1940 if(ear.spawnflags & 4)
1946 if(ear.spawnflags & 1)
1949 if(ear.spawnflags & 2)
1952 if(ear.spawnflags & 8)
1957 l = strlen(ear.message);
1959 if(self.spawnflags & 128)
1962 msg = strdecolorize(msgin);
1964 if(substring(ear.message, 0, 1) == "*")
1966 if(substring(ear.message, -1, 1) == "*")
1969 // as we need multi-replacement here...
1970 s = substring(ear.message, 1, -2);
1972 if(strstrofs(msg, s, 0) >= 0)
1973 matchstart = -2; // we use strreplace on s
1978 s = substring(ear.message, 1, -1);
1980 if(substring(msg, -l, l) == s)
1981 matchstart = strlen(msg) - l;
1986 if(substring(ear.message, -1, 1) == "*")
1989 s = substring(ear.message, 0, -2);
1991 if(substring(msg, 0, l) == s)
1998 if(msg == ear.message)
2003 if(matchstart == -1) // no match
2006 magicear_matched = TRUE;
2010 oldself = activator = self;
2016 if(ear.spawnflags & 16)
2020 else if(ear.netname != "")
2023 return strreplace(s, ear.netname, msg);
2026 substring(msg, 0, matchstart),
2028 substring(msg, matchstart + l, -1)
2036 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2040 for(ear = magicears; ear; ear = ear.enemy)
2042 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2043 if not(ear.spawnflags & 64)
2044 if(magicear_matched)
2051 void spawnfunc_trigger_magicear()
2053 self.enemy = magicears;
2056 // actually handled in "say" processing
2059 // 2 = ignore teamsay
2061 // 8 = ignore tell to unknown player
2062 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2063 // 32 = perform the replacement even if outside the radius or dead
2064 // 64 = continue replacing/triggering even if this one matched
2074 // if set, replacement for the matched text
2076 // "hearing distance"
2081 void relay_activators_use()
2087 for(trg = world; (trg = find(trg, targetname, os.target)); )
2091 trg.setactive(os.cnt);
2094 //bprint("Not using setactive\n");
2095 if(os.cnt == ACTIVE_TOGGLE)
2096 if(trg.active == ACTIVE_ACTIVE)
2097 trg.active = ACTIVE_NOT;
2099 trg.active = ACTIVE_ACTIVE;
2101 trg.active = os.cnt;
2107 void spawnfunc_relay_activate()
2109 self.cnt = ACTIVE_ACTIVE;
2110 self.use = relay_activators_use;
2113 void spawnfunc_relay_deactivate()
2115 self.cnt = ACTIVE_NOT;
2116 self.use = relay_activators_use;
2119 void spawnfunc_relay_activatetoggle()
2121 self.cnt = ACTIVE_TOGGLE;
2122 self.use = relay_activators_use;
2125 .string chmap, gametype;
2126 void spawnfunc_target_changelevel_use()
2128 if(self.gametype != "")
2129 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2131 if (self.chmap == "")
2132 localcmd("endmatch\n");
2134 localcmd(strcat("changelevel ", self.chmap, "\n"));
2137 void spawnfunc_target_changelevel()
2139 self.use = spawnfunc_target_changelevel_use;