1 void SUB_DontUseTargets()
10 activator = self.enemy;
16 ==============================
19 the global "activator" should be set to the entity that initiated the firing.
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
24 Centerprints any self.message to the activator.
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
32 ==============================
36 entity t, stemp, otemp, act;
45 // create a temp object to fire at a later time
47 t.classname = "DelayedUse";
48 t.nextthink = time + self.delay;
51 t.message = self.message;
52 t.killtarget = self.killtarget;
53 t.target = self.target;
54 t.target2 = self.target2;
55 t.target3 = self.target3;
56 t.target4 = self.target4;
64 if (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 if(stemp.target_random)
92 RandomSelection_Init();
94 for(i = 0; i < 4; ++i)
99 case 0: s = stemp.target; break;
100 case 1: s = stemp.target2; break;
101 case 2: s = stemp.target3; break;
102 case 3: s = stemp.target4; break;
106 for(t = world; (t = find(t, targetname, s)); )
109 if(stemp.target_random)
111 RandomSelection_Add(t, 0, string_null, 1, 0);
124 if(stemp.target_random && RandomSelection_chosen_ent)
126 self = RandomSelection_chosen_ent;
138 //=============================================================================
140 float SPAWNFLAG_NOMESSAGE = 1;
141 float SPAWNFLAG_NOTOUCH = 1;
143 // the wait time has passed, so set back up for another activation
148 self.health = self.max_health;
149 self.takedamage = DAMAGE_YES;
150 self.solid = SOLID_BBOX;
155 // the trigger was just touched/killed/used
156 // self.enemy should be set to the activator so it can be held through a delay
157 // so wait for the delay time before firing
160 if (self.nextthink > time)
162 return; // allready been triggered
165 if (self.classname == "trigger_secret")
167 if (self.enemy.classname != "player")
169 found_secrets = found_secrets + 1;
170 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
174 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
176 // don't trigger again until reset
177 self.takedamage = DAMAGE_NO;
179 activator = self.enemy;
180 other = self.goalentity;
185 self.think = multi_wait;
186 self.nextthink = time + self.wait;
188 else if (self.wait == 0)
190 multi_wait(); // waiting finished
193 { // we can't just remove (self) here, because this is a touch function
194 // called wheil C code is looping through area links...
195 self.touch = SUB_Null;
201 self.goalentity = other;
202 self.enemy = activator;
208 if not(self.spawnflags & 2)
209 if not(other.iscreature)
213 if((self.spawnflags & 4 == 0) == (self.team != other.team))
216 // if the trigger has an angles field, check player's facing direction
217 if (self.movedir != '0 0 0')
219 makevectors (other.angles);
220 if (v_forward * self.movedir < 0)
221 return; // not facing the right way
227 self.goalentity = other;
231 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
233 if (!self.takedamage)
235 if(self.spawnflags & DOOR_NOSPLASH)
236 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
238 self.health = self.health - damage;
239 if (self.health <= 0)
241 self.enemy = attacker;
242 self.goalentity = inflictor;
249 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
250 self.touch = multi_touch;
253 self.health = self.max_health;
254 self.takedamage = DAMAGE_YES;
255 self.solid = SOLID_BBOX;
257 self.think = SUB_Null;
258 self.team = self.team_saved;
261 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
262 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.
263 If "delay" is set, the trigger waits some time after activating before firing.
264 "wait" : Seconds between triggerings. (.2 default)
265 If notouch is set, the trigger is only fired by other entities, not by touching.
266 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
272 set "message" to text string
274 void spawnfunc_trigger_multiple()
276 self.reset = multi_reset;
277 if (self.sounds == 1)
279 precache_sound ("misc/secret.wav");
280 self.noise = "misc/secret.wav";
282 else if (self.sounds == 2)
284 precache_sound ("misc/talk.wav");
285 self.noise = "misc/talk.wav";
287 else if (self.sounds == 3)
289 precache_sound ("misc/trigger1.wav");
290 self.noise = "misc/trigger1.wav";
295 else if(self.wait < -1)
297 self.use = multi_use;
301 self.team_saved = self.team;
305 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
306 objerror ("health and notouch don't make sense\n");
307 self.max_health = self.health;
308 self.event_damage = multi_eventdamage;
309 self.takedamage = DAMAGE_YES;
310 self.solid = SOLID_BBOX;
311 setorigin (self, self.origin); // make sure it links into the world
315 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
317 self.touch = multi_touch;
318 setorigin (self, self.origin); // make sure it links into the world
324 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
325 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
326 "targetname". If "health" is set, the trigger must be killed to activate.
327 If notouch is set, the trigger is only fired by other entities, not by touching.
328 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
329 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.
335 set "message" to text string
337 void spawnfunc_trigger_once()
340 spawnfunc_trigger_multiple();
343 //=============================================================================
345 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
346 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
348 void spawnfunc_trigger_relay()
350 self.use = SUB_UseTargets;
351 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
356 self.think = SUB_UseTargets;
357 self.nextthink = self.wait;
362 self.think = SUB_Null;
365 void spawnfunc_trigger_delay()
370 self.use = delay_use;
371 self.reset = delay_reset;
374 //=============================================================================
379 self.count = self.count - 1;
385 if (activator.classname == "player"
386 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
389 centerprint (activator, "There are more to go...");
390 else if (self.count == 3)
391 centerprint (activator, "Only 3 more to go...");
392 else if (self.count == 2)
393 centerprint (activator, "Only 2 more to go...");
395 centerprint (activator, "Only 1 more to go...");
400 if (activator.classname == "player"
401 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
402 centerprint(activator, "Sequence completed!");
403 self.enemy = activator;
409 self.count = self.cnt;
413 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
414 Acts as an intermediary for an action that takes multiple inputs.
416 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
418 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
420 void spawnfunc_trigger_counter()
425 self.cnt = self.count;
427 self.use = counter_use;
428 self.reset = counter_reset;
431 void trigger_hurt_use()
433 if(activator.classname == "player")
434 self.enemy = activator;
436 self.enemy = world; // let's just destroy it, if taking over is too much work
439 .float triggerhurttime;
440 void trigger_hurt_touch()
442 if (self.active != ACTIVE_ACTIVE)
446 if((self.spawnflags & 4 == 0) == (self.team != other.team))
449 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
450 if (other.iscreature)
452 if (other.takedamage)
453 if (other.triggerhurttime < time)
456 other.triggerhurttime = time + 1;
460 if(own.classname != "player")
463 self.enemy = world; // I still hate you all
466 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
469 else if(other.damagedbytriggers)
474 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
481 if (other.classname == "rune") // reset runes
484 other.nextthink = min(other.nextthink, time + 1);
492 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
493 Any object touching this will be hurt
494 set dmg to damage amount
497 .entity trigger_hurt_next;
498 entity trigger_hurt_last;
499 entity trigger_hurt_first;
500 void spawnfunc_trigger_hurt()
503 self.active = ACTIVE_ACTIVE;
504 self.touch = trigger_hurt_touch;
505 self.use = trigger_hurt_use;
506 self.enemy = world; // I hate you all
510 self.message = "was in the wrong place";
512 self.message2 = "was thrown into a world of hurt by";
513 // self.message = "someone like %s always gets wrongplaced";
515 if(!trigger_hurt_first)
516 trigger_hurt_first = self;
517 if(trigger_hurt_last)
518 trigger_hurt_last.trigger_hurt_next = self;
519 trigger_hurt_last = self;
522 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
526 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
527 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
533 //////////////////////////////////////////////////////////////
537 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
539 //////////////////////////////////////////////////////////////
541 .float triggerhealtime;
542 void trigger_heal_touch()
544 if (self.active != ACTIVE_ACTIVE)
547 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
548 if (other.iscreature)
550 if (other.takedamage)
552 if (other.triggerhealtime < time)
555 other.triggerhealtime = time + 1;
557 if (other.health < self.max_health)
559 other.health = min(other.health + self.health, self.max_health);
560 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
561 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
567 void spawnfunc_trigger_heal()
569 self.active = ACTIVE_ACTIVE;
572 self.touch = trigger_heal_touch;
575 if (!self.max_health)
576 self.max_health = 200; //Max health topoff for field
578 self.noise = "misc/mediumhealth.wav";
579 precache_sound(self.noise);
583 //////////////////////////////////////////////////////////////
589 //////////////////////////////////////////////////////////////
591 .entity trigger_gravity_check;
592 void trigger_gravity_remove(entity own)
594 if(own.trigger_gravity_check.owner == own)
596 UpdateCSQCProjectile(own);
597 own.gravity = own.trigger_gravity_check.gravity;
598 remove(own.trigger_gravity_check);
601 backtrace("Removing a trigger_gravity_check with no valid owner");
602 own.trigger_gravity_check = world;
604 void trigger_gravity_check_think()
606 // This spawns when a player enters the gravity zone and checks if he left.
607 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
608 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
611 if(self.owner.trigger_gravity_check == self)
612 trigger_gravity_remove(self.owner);
620 self.nextthink = time;
624 void trigger_gravity_use()
626 self.state = !self.state;
629 void trigger_gravity_touch()
633 if(self.state != TRUE)
640 if not(self.spawnflags & 1)
642 if(other.trigger_gravity_check)
644 if(self == other.trigger_gravity_check.enemy)
647 other.trigger_gravity_check.count = 2; // gravity one more frame...
652 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
653 trigger_gravity_remove(other);
657 other.trigger_gravity_check = spawn();
658 other.trigger_gravity_check.enemy = self;
659 other.trigger_gravity_check.owner = other;
660 other.trigger_gravity_check.gravity = other.gravity;
661 other.trigger_gravity_check.think = trigger_gravity_check_think;
662 other.trigger_gravity_check.nextthink = time;
663 other.trigger_gravity_check.count = 2;
668 if (other.gravity != g)
672 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
673 UpdateCSQCProjectile(self.owner);
677 void spawnfunc_trigger_gravity()
679 if(self.gravity == 1)
683 self.touch = trigger_gravity_touch;
685 precache_sound(self.noise);
690 self.use = trigger_gravity_use;
691 if(self.spawnflags & 2)
696 //=============================================================================
698 // TODO add a way to do looped sounds with sound(); then complete this entity
699 .float volume, atten;
700 void target_speaker_use_off();
701 void target_speaker_use_activator()
703 if(clienttype(activator) != CLIENTTYPE_REAL)
706 if(substring(self.noise, 0, 1) == "*")
709 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
710 if(GetPlayerSoundSampleField_notFound)
711 snd = "misc/null.wav";
712 else if(activator.sample == "")
713 snd = "misc/null.wav";
716 tokenize_console(activator.sample);
720 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
722 snd = strcat(argv(0), ".wav"); // randomization
727 msg_entity = activator;
728 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
730 void target_speaker_use_on()
733 if(substring(self.noise, 0, 1) == "*")
736 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
737 if(GetPlayerSoundSampleField_notFound)
738 snd = "misc/null.wav";
739 else if(activator.sample == "")
740 snd = "misc/null.wav";
743 tokenize_console(activator.sample);
747 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
749 snd = strcat(argv(0), ".wav"); // randomization
754 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
755 if(self.spawnflags & 3)
756 self.use = target_speaker_use_off;
758 void target_speaker_use_off()
760 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
761 self.use = target_speaker_use_on;
763 void target_speaker_reset()
765 if(self.spawnflags & 1) // LOOPED_ON
767 if(self.use == target_speaker_use_on)
768 target_speaker_use_on();
770 else if(self.spawnflags & 2)
772 if(self.use == target_speaker_use_off)
773 target_speaker_use_off();
777 void spawnfunc_target_speaker()
779 // TODO: "*" prefix to sound file name
780 // TODO: wait and random (just, HOW? random is not a field)
782 precache_sound (self.noise);
784 if(!self.atten && !(self.spawnflags & 4))
787 self.atten = ATTN_NORM;
789 self.atten = ATTN_STATIC;
791 else if(self.atten < 0)
799 if(self.spawnflags & 8) // ACTIVATOR
800 self.use = target_speaker_use_activator;
801 else if(self.spawnflags & 1) // LOOPED_ON
803 target_speaker_use_on();
804 self.reset = target_speaker_reset;
806 else if(self.spawnflags & 2) // LOOPED_OFF
808 self.use = target_speaker_use_on;
809 self.reset = target_speaker_reset;
812 self.use = target_speaker_use_on;
814 else if(self.spawnflags & 1) // LOOPED_ON
816 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
819 else if(self.spawnflags & 2) // LOOPED_OFF
821 objerror("This sound entity can never be activated");
825 // Quake/Nexuiz fallback
826 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
832 void spawnfunc_func_stardust() {
833 self.effects = EF_STARDUST;
837 .float bgmscriptattack;
838 .float bgmscriptdecay;
839 .float bgmscriptsustain;
840 .float bgmscriptrelease;
841 float pointparticles_SendEntity(entity to, float fl)
843 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
845 // optional features to save space
847 if(self.spawnflags & 2)
848 fl |= 0x10; // absolute count on toggle-on
849 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
850 fl |= 0x20; // 4 bytes - saves CPU
851 if(self.waterlevel || self.count != 1)
852 fl |= 0x40; // 4 bytes - obscure features almost never used
853 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
854 fl |= 0x80; // 14 bytes - saves lots of space
856 WriteByte(MSG_ENTITY, fl);
860 WriteCoord(MSG_ENTITY, self.impulse);
862 WriteCoord(MSG_ENTITY, 0); // off
866 WriteCoord(MSG_ENTITY, self.origin_x);
867 WriteCoord(MSG_ENTITY, self.origin_y);
868 WriteCoord(MSG_ENTITY, self.origin_z);
872 if(self.model != "null")
874 WriteShort(MSG_ENTITY, self.modelindex);
877 WriteCoord(MSG_ENTITY, self.mins_x);
878 WriteCoord(MSG_ENTITY, self.mins_y);
879 WriteCoord(MSG_ENTITY, self.mins_z);
880 WriteCoord(MSG_ENTITY, self.maxs_x);
881 WriteCoord(MSG_ENTITY, self.maxs_y);
882 WriteCoord(MSG_ENTITY, self.maxs_z);
887 WriteShort(MSG_ENTITY, 0);
890 WriteCoord(MSG_ENTITY, self.maxs_x);
891 WriteCoord(MSG_ENTITY, self.maxs_y);
892 WriteCoord(MSG_ENTITY, self.maxs_z);
895 WriteShort(MSG_ENTITY, self.cnt);
898 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
899 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
903 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
904 WriteByte(MSG_ENTITY, self.count * 16.0);
906 WriteString(MSG_ENTITY, self.noise);
909 WriteByte(MSG_ENTITY, floor(self.atten * 64));
910 WriteByte(MSG_ENTITY, floor(self.volume * 255));
912 WriteString(MSG_ENTITY, self.bgmscript);
913 if(self.bgmscript != "")
915 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
916 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
917 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
918 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
924 void pointparticles_use()
926 self.state = !self.state;
930 void pointparticles_think()
932 if(self.origin != self.oldorigin)
935 self.oldorigin = self.origin;
937 self.nextthink = time;
940 void pointparticles_reset()
942 if(self.spawnflags & 1)
948 void spawnfunc_func_pointparticles()
951 setmodel(self, self.model);
953 precache_sound (self.noise);
955 if(!self.bgmscriptsustain)
956 self.bgmscriptsustain = 1;
957 else if(self.bgmscriptsustain < 0)
958 self.bgmscriptsustain = 0;
961 self.atten = ATTN_NORM;
962 else if(self.atten < 0)
973 setorigin(self, self.origin + self.mins);
974 setsize(self, '0 0 0', self.maxs - self.mins);
977 self.cnt = particleeffectnum(self.mdl);
979 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
983 self.use = pointparticles_use;
984 self.reset = pointparticles_reset;
989 self.think = pointparticles_think;
990 self.nextthink = time;
993 void spawnfunc_func_sparks()
995 // self.cnt is the amount of sparks that one burst will spawn
997 self.cnt = 25.0; // nice default value
1000 // self.wait is the probability that a sparkthink will spawn a spark shower
1001 // range: 0 - 1, but 0 makes little sense, so...
1002 if(self.wait < 0.05) {
1003 self.wait = 0.25; // nice default value
1006 self.count = self.cnt;
1007 self.mins = '0 0 0';
1008 self.maxs = '0 0 0';
1009 self.velocity = '0 0 -1';
1010 self.mdl = "TE_SPARK";
1011 self.impulse = 10 * self.wait; // by default 2.5/sec
1013 self.cnt = 0; // use mdl
1015 spawnfunc_func_pointparticles();
1018 float rainsnow_SendEntity(entity to, float sf)
1020 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1021 WriteByte(MSG_ENTITY, self.state);
1022 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1023 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1024 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1025 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1026 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1027 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1028 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1029 WriteShort(MSG_ENTITY, self.count);
1030 WriteByte(MSG_ENTITY, self.cnt);
1034 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1035 This is an invisible area like a trigger, which rain falls inside of.
1039 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1041 sets color of rain (default 12 - white)
1043 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1045 void spawnfunc_func_rain()
1047 self.dest = self.velocity;
1048 self.velocity = '0 0 0';
1050 self.dest = '0 0 -700';
1051 self.angles = '0 0 0';
1052 self.movetype = MOVETYPE_NONE;
1053 self.solid = SOLID_NOT;
1054 SetBrushEntityModel();
1059 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1062 if(self.count > 65535)
1065 self.state = 1; // 1 is rain, 0 is snow
1068 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1072 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1073 This is an invisible area like a trigger, which snow falls inside of.
1077 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1079 sets color of rain (default 12 - white)
1081 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1083 void spawnfunc_func_snow()
1085 self.dest = self.velocity;
1086 self.velocity = '0 0 0';
1088 self.dest = '0 0 -300';
1089 self.angles = '0 0 0';
1090 self.movetype = MOVETYPE_NONE;
1091 self.solid = SOLID_NOT;
1092 SetBrushEntityModel();
1097 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1100 if(self.count > 65535)
1103 self.state = 0; // 1 is rain, 0 is snow
1106 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1110 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1113 void misc_laser_aim()
1118 if(self.spawnflags & 2)
1120 if(self.enemy.origin != self.mangle)
1122 self.mangle = self.enemy.origin;
1123 self.SendFlags |= 2;
1128 a = vectoangles(self.enemy.origin - self.origin);
1130 if(a != self.mangle)
1133 self.SendFlags |= 2;
1139 if(self.angles != self.mangle)
1141 self.mangle = self.angles;
1142 self.SendFlags |= 2;
1145 if(self.origin != self.oldorigin)
1147 self.SendFlags |= 1;
1148 self.oldorigin = self.origin;
1152 void misc_laser_init()
1154 if(self.target != "")
1155 self.enemy = find(world, targetname, self.target);
1159 void misc_laser_think()
1166 self.nextthink = time;
1175 o = self.enemy.origin;
1176 if not(self.spawnflags & 2)
1177 o = self.origin + normalize(o - self.origin) * 32768;
1181 makevectors(self.mangle);
1182 o = self.origin + v_forward * 32768;
1185 if(self.dmg || self.enemy.target != "")
1187 traceline(self.origin, o, MOVE_NORMAL, self);
1190 hitloc = trace_endpos;
1192 if(self.enemy.target != "") // DETECTOR laser
1194 if(trace_ent.iscreature)
1196 self.pusher = hitent;
1203 activator = self.pusher;
1216 activator = self.pusher;
1226 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1228 if(hitent.takedamage)
1229 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1233 float laser_SendEntity(entity to, float fl)
1235 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1236 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1237 if(self.spawnflags & 2)
1241 if(self.scale != 1 || self.modelscale != 1)
1243 if(self.spawnflags & 4)
1245 WriteByte(MSG_ENTITY, fl);
1248 WriteCoord(MSG_ENTITY, self.origin_x);
1249 WriteCoord(MSG_ENTITY, self.origin_y);
1250 WriteCoord(MSG_ENTITY, self.origin_z);
1254 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1255 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1256 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1258 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1261 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1262 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1264 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1265 WriteShort(MSG_ENTITY, self.cnt + 1);
1271 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1272 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1273 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1277 WriteAngle(MSG_ENTITY, self.mangle_x);
1278 WriteAngle(MSG_ENTITY, self.mangle_y);
1282 WriteByte(MSG_ENTITY, self.state);
1286 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1287 Any object touching the beam will be hurt
1290 spawnfunc_target_position where the laser ends
1292 name of beam end effect to use
1294 color of the beam (default: red)
1296 damage per second (-1 for a laser that kills immediately)
1300 self.state = !self.state;
1301 self.SendFlags |= 4;
1307 if(self.spawnflags & 1)
1313 void spawnfunc_misc_laser()
1317 if(self.mdl == "none")
1321 self.cnt = particleeffectnum(self.mdl);
1324 self.cnt = particleeffectnum("laser_deadly");
1330 self.cnt = particleeffectnum("laser_deadly");
1337 if(self.colormod == '0 0 0')
1339 self.colormod = '1 0 0';
1341 self.message = "saw the light";
1343 self.message2 = "was pushed into a laser by";
1346 if(!self.modelscale)
1347 self.modelscale = 1;
1348 else if(self.modelscale < 0)
1349 self.modelscale = 0;
1350 self.think = misc_laser_think;
1351 self.nextthink = time;
1352 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1354 self.mangle = self.angles;
1356 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1360 self.reset = laser_reset;
1362 self.use = laser_use;
1368 // tZorks trigger impulse / gravity
1372 .float lastpushtime;
1374 // targeted (directional) mode
1375 void trigger_impulse_touch1()
1378 float pushdeltatime;
1381 if (self.active != ACTIVE_ACTIVE)
1384 if (!isPushable(other))
1389 targ = find(world, targetname, self.target);
1392 objerror("trigger_force without a (valid) .target!\n");
1397 str = min(self.radius, vlen(self.origin - other.origin));
1399 if(self.falloff == 1)
1400 str = (str / self.radius) * self.strength;
1401 else if(self.falloff == 2)
1402 str = (1 - (str / self.radius)) * self.strength;
1404 str = self.strength;
1406 pushdeltatime = time - other.lastpushtime;
1407 if (pushdeltatime > 0.15) pushdeltatime = 0;
1408 other.lastpushtime = time;
1409 if(!pushdeltatime) return;
1411 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1412 other.flags &~= FL_ONGROUND;
1413 UpdateCSQCProjectile(other);
1416 // Directionless (accelerator/decelerator) mode
1417 void trigger_impulse_touch2()
1419 float pushdeltatime;
1421 if (self.active != ACTIVE_ACTIVE)
1424 if (!isPushable(other))
1429 pushdeltatime = time - other.lastpushtime;
1430 if (pushdeltatime > 0.15) pushdeltatime = 0;
1431 other.lastpushtime = time;
1432 if(!pushdeltatime) return;
1434 // div0: ticrate independent, 1 = identity (not 20)
1435 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1436 UpdateCSQCProjectile(other);
1439 // Spherical (gravity/repulsor) mode
1440 void trigger_impulse_touch3()
1442 float pushdeltatime;
1445 if (self.active != ACTIVE_ACTIVE)
1448 if (!isPushable(other))
1453 pushdeltatime = time - other.lastpushtime;
1454 if (pushdeltatime > 0.15) pushdeltatime = 0;
1455 other.lastpushtime = time;
1456 if(!pushdeltatime) return;
1458 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1460 str = min(self.radius, vlen(self.origin - other.origin));
1462 if(self.falloff == 1)
1463 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1464 else if(self.falloff == 2)
1465 str = (str / self.radius) * self.strength; // 0 in the inside
1467 str = self.strength;
1469 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1470 UpdateCSQCProjectile(other);
1473 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1474 -------- KEYS --------
1475 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1476 If not, this trigger acts like a damper/accelerator field.
1478 strength : This is how mutch force to add in the direction of .target each second
1479 when .target is set. If not, this is hoe mutch to slow down/accelerate
1480 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1482 radius : If set, act as a spherical device rather then a liniar one.
1484 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1486 -------- NOTES --------
1487 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1488 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1491 void spawnfunc_trigger_impulse()
1493 self.active = ACTIVE_ACTIVE;
1498 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1499 setorigin(self, self.origin);
1500 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1501 self.touch = trigger_impulse_touch3;
1507 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1508 self.touch = trigger_impulse_touch1;
1512 if(!self.strength) self.strength = 0.9;
1513 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1514 self.touch = trigger_impulse_touch2;
1519 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1520 "Flip-flop" trigger gate... lets only every second trigger event through
1524 self.state = !self.state;
1529 void spawnfunc_trigger_flipflop()
1531 if(self.spawnflags & 1)
1533 self.use = flipflop_use;
1534 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1537 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1538 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1542 self.nextthink = time + self.wait;
1543 self.enemy = activator;
1549 void monoflop_fixed_use()
1553 self.nextthink = time + self.wait;
1555 self.enemy = activator;
1559 void monoflop_think()
1562 activator = self.enemy;
1566 void monoflop_reset()
1572 void spawnfunc_trigger_monoflop()
1576 if(self.spawnflags & 1)
1577 self.use = monoflop_fixed_use;
1579 self.use = monoflop_use;
1580 self.think = monoflop_think;
1582 self.reset = monoflop_reset;
1585 void multivibrator_send()
1590 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1592 newstate = (time < cyclestart + self.wait);
1595 if(self.state != newstate)
1597 self.state = newstate;
1600 self.nextthink = cyclestart + self.wait + 0.01;
1602 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1605 void multivibrator_toggle()
1607 if(self.nextthink == 0)
1609 multivibrator_send();
1622 void multivibrator_reset()
1624 if(!(self.spawnflags & 1))
1625 self.nextthink = 0; // wait for a trigger event
1627 self.nextthink = max(1, time);
1630 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1631 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1632 -------- KEYS --------
1633 target: trigger all entities with this targetname when it goes off
1634 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1635 phase: offset of the timing
1636 wait: "on" cycle time (default: 1)
1637 respawntime: "off" cycle time (default: same as wait)
1638 -------- SPAWNFLAGS --------
1639 START_ON: assume it is already turned on (when targeted)
1641 void spawnfunc_trigger_multivibrator()
1645 if(!self.respawntime)
1646 self.respawntime = self.wait;
1649 self.use = multivibrator_toggle;
1650 self.think = multivibrator_send;
1651 self.nextthink = max(1, time);
1654 multivibrator_reset();
1663 if(self.killtarget != "")
1664 src = find(world, targetname, self.killtarget);
1665 if(self.target != "")
1666 dst = find(world, targetname, self.target);
1670 objerror("follow: could not find target/killtarget");
1676 // already done :P entity must stay
1680 else if(!src || !dst)
1682 objerror("follow: could not find target/killtarget");
1685 else if(self.spawnflags & 1)
1688 if(self.spawnflags & 2)
1690 setattachment(dst, src, self.message);
1694 attach_sameorigin(dst, src, self.message);
1697 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1702 if(self.spawnflags & 2)
1704 dst.movetype = MOVETYPE_FOLLOW;
1706 // dst.punchangle = '0 0 0'; // keep unchanged
1707 dst.view_ofs = dst.origin;
1708 dst.v_angle = dst.angles;
1712 follow_sameorigin(dst, src);
1719 void spawnfunc_misc_follow()
1721 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1726 void gamestart_use() {
1732 void spawnfunc_trigger_gamestart() {
1733 self.use = gamestart_use;
1734 self.reset2 = spawnfunc_trigger_gamestart;
1738 self.think = self.use;
1739 self.nextthink = game_starttime + self.wait;
1742 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1748 .entity voicescript; // attached voice script
1749 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1750 .float voicescript_nextthink; // time to play next voice
1751 .float voicescript_voiceend; // time when this voice ends
1753 void target_voicescript_clear(entity pl)
1755 pl.voicescript = world;
1758 void target_voicescript_use()
1760 if(activator.voicescript != self)
1762 activator.voicescript = self;
1763 activator.voicescript_index = 0;
1764 activator.voicescript_nextthink = time + self.delay;
1768 void target_voicescript_next(entity pl)
1773 vs = pl.voicescript;
1776 if(vs.message == "")
1778 if(pl.classname != "player")
1783 if(time >= pl.voicescript_voiceend)
1785 if(time >= pl.voicescript_nextthink)
1787 // get the next voice...
1788 n = tokenize_console(vs.message);
1790 if(pl.voicescript_index < vs.cnt)
1791 i = pl.voicescript_index * 2;
1792 else if(n > vs.cnt * 2)
1793 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1799 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1800 dt = stof(argv(i + 1));
1803 pl.voicescript_voiceend = time + dt;
1804 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1808 pl.voicescript_voiceend = time - dt;
1809 pl.voicescript_nextthink = pl.voicescript_voiceend;
1812 pl.voicescript_index += 1;
1816 pl.voicescript = world; // stop trying then
1822 void spawnfunc_target_voicescript()
1824 // netname: directory of the sound files
1825 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1826 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1827 // Here, a - in front of the duration means that no delay is to be
1828 // added after this message
1829 // wait: average time between messages
1830 // delay: initial delay before the first message
1833 self.use = target_voicescript_use;
1835 n = tokenize_console(self.message);
1837 for(i = 0; i+1 < n; i += 2)
1844 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1850 void trigger_relay_teamcheck_use()
1854 if(self.spawnflags & 2)
1856 if(activator.team != self.team)
1861 if(activator.team == self.team)
1867 if(self.spawnflags & 1)
1872 void trigger_relay_teamcheck_reset()
1874 self.team = self.team_saved;
1877 void spawnfunc_trigger_relay_teamcheck()
1879 self.team_saved = self.team;
1880 self.use = trigger_relay_teamcheck_use;
1881 self.reset = trigger_relay_teamcheck_reset;
1886 void trigger_disablerelay_use()
1893 for(e = world; (e = find(e, targetname, self.target)); )
1895 if(e.use == SUB_UseTargets)
1897 e.use = SUB_DontUseTargets;
1900 else if(e.use == SUB_DontUseTargets)
1902 e.use = SUB_UseTargets;
1908 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1911 void spawnfunc_trigger_disablerelay()
1913 self.use = trigger_disablerelay_use;
1916 float magicear_matched;
1917 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1918 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1920 float domatch, dotrigger, matchstart, l;
1925 magicear_matched = FALSE;
1927 dotrigger = ((source.classname == "player") && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1928 domatch = ((ear.spawnflags & 32) || dotrigger);
1935 // we are in TUBA mode!
1936 if not(ear.spawnflags & 256)
1939 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1942 magicear_matched = TRUE;
1949 savemessage = self.message;
1950 self.message = string_null;
1952 self.message = savemessage;
1956 if(ear.netname != "")
1962 if(ear.spawnflags & 256) // ENOTUBA
1967 if(ear.spawnflags & 4)
1973 if(ear.spawnflags & 1)
1976 if(ear.spawnflags & 2)
1979 if(ear.spawnflags & 8)
1984 l = strlen(ear.message);
1986 if(ear.spawnflags & 128)
1989 msg = strdecolorize(msgin);
1991 if(substring(ear.message, 0, 1) == "*")
1993 if(substring(ear.message, -1, 1) == "*")
1996 // as we need multi-replacement here...
1997 s = substring(ear.message, 1, -2);
1999 if(strstrofs(msg, s, 0) >= 0)
2000 matchstart = -2; // we use strreplace on s
2005 s = substring(ear.message, 1, -1);
2007 if(substring(msg, -l, l) == s)
2008 matchstart = strlen(msg) - l;
2013 if(substring(ear.message, -1, 1) == "*")
2016 s = substring(ear.message, 0, -2);
2018 if(substring(msg, 0, l) == s)
2025 if(msg == ear.message)
2030 if(matchstart == -1) // no match
2033 magicear_matched = TRUE;
2040 savemessage = self.message;
2041 self.message = string_null;
2043 self.message = savemessage;
2047 if(ear.spawnflags & 16)
2051 else if(ear.netname != "")
2054 return strreplace(s, ear.netname, msg);
2057 substring(msg, 0, matchstart),
2059 substring(msg, matchstart + l, -1)
2067 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2071 for(ear = magicears; ear; ear = ear.enemy)
2073 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2074 if not(ear.spawnflags & 64)
2075 if(magicear_matched)
2082 void spawnfunc_trigger_magicear()
2084 self.enemy = magicears;
2087 // actually handled in "say" processing
2090 // 2 = ignore teamsay
2092 // 8 = ignore tell to unknown player
2093 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2094 // 32 = perform the replacement even if outside the radius or dead
2095 // 64 = continue replacing/triggering even if this one matched
2096 // 128 = don't decolorize message before matching
2097 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2098 // 512 = tuba notes must be exact right pitch, no transposing
2108 // if set, replacement for the matched text
2110 // "hearing distance"
2114 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2116 self.movedir_x -= 1; // map to tuba instrument numbers
2119 void relay_activators_use()
2125 for(trg = world; (trg = find(trg, targetname, os.target)); )
2129 trg.setactive(os.cnt);
2132 //bprint("Not using setactive\n");
2133 if(os.cnt == ACTIVE_TOGGLE)
2134 if(trg.active == ACTIVE_ACTIVE)
2135 trg.active = ACTIVE_NOT;
2137 trg.active = ACTIVE_ACTIVE;
2139 trg.active = os.cnt;
2145 void spawnfunc_relay_activate()
2147 self.cnt = ACTIVE_ACTIVE;
2148 self.use = relay_activators_use;
2151 void spawnfunc_relay_deactivate()
2153 self.cnt = ACTIVE_NOT;
2154 self.use = relay_activators_use;
2157 void spawnfunc_relay_activatetoggle()
2159 self.cnt = ACTIVE_TOGGLE;
2160 self.use = relay_activators_use;
2163 .string chmap, gametype;
2164 void spawnfunc_target_changelevel_use()
2166 if(self.gametype != "")
2167 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2169 if (self.chmap == "")
2170 localcmd("endmatch\n");
2172 localcmd(strcat("changelevel ", self.chmap, "\n"));
2175 void spawnfunc_target_changelevel()
2177 self.use = spawnfunc_target_changelevel_use;