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');
473 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
476 other.pain_finished = min(other.pain_finished, time + 2);
478 else if (other.classname == "rune") // reset runes
481 other.nextthink = min(other.nextthink, time + 1);
489 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
490 Any object touching this will be hurt
491 set dmg to damage amount
494 .entity trigger_hurt_next;
495 entity trigger_hurt_last;
496 entity trigger_hurt_first;
497 void spawnfunc_trigger_hurt()
500 self.active = ACTIVE_ACTIVE;
501 self.touch = trigger_hurt_touch;
502 self.use = trigger_hurt_use;
503 self.enemy = world; // I hate you all
507 self.message = "was in the wrong place";
509 self.message2 = "was thrown into a world of hurt by";
510 // self.message = "someone like %s always gets wrongplaced";
512 if(!trigger_hurt_first)
513 trigger_hurt_first = self;
514 if(trigger_hurt_last)
515 trigger_hurt_last.trigger_hurt_next = self;
516 trigger_hurt_last = self;
519 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
523 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
524 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
530 //////////////////////////////////////////////////////////////
534 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
536 //////////////////////////////////////////////////////////////
538 .float triggerhealtime;
539 void trigger_heal_touch()
541 if (self.active != ACTIVE_ACTIVE)
544 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
545 if (other.iscreature)
547 if (other.takedamage)
548 if (other.triggerhealtime < time)
551 other.triggerhealtime = time + 1;
553 if (other.health < self.max_health)
555 other.health = min(other.health + self.health, self.max_health);
556 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
557 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
563 void spawnfunc_trigger_heal()
565 self.active = ACTIVE_ACTIVE;
568 self.touch = trigger_heal_touch;
571 if (!self.max_health)
572 self.max_health = 200; //Max health topoff for field
574 self.noise = "misc/mediumhealth.wav";
575 precache_sound(self.noise);
579 //////////////////////////////////////////////////////////////
585 //////////////////////////////////////////////////////////////
587 .entity trigger_gravity_check;
588 void trigger_gravity_remove(entity own)
590 if(own.trigger_gravity_check.owner == own)
592 UpdateCSQCProjectile(own);
593 own.gravity = own.trigger_gravity_check.gravity;
594 remove(own.trigger_gravity_check);
597 backtrace("Removing a trigger_gravity_check with no valid owner");
598 own.trigger_gravity_check = world;
600 void trigger_gravity_check_think()
602 // This spawns when a player enters the gravity zone and checks if he left.
603 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
604 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
607 if(self.owner.trigger_gravity_check == self)
608 trigger_gravity_remove(self.owner);
616 self.nextthink = time;
620 void trigger_gravity_use()
622 self.state = !self.state;
625 void trigger_gravity_touch()
629 if(self.state != TRUE)
636 if not(self.spawnflags & 1)
638 if(other.trigger_gravity_check)
640 if(self == other.trigger_gravity_check.enemy)
643 other.trigger_gravity_check.count = 2; // gravity one more frame...
648 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
649 trigger_gravity_remove(other);
653 other.trigger_gravity_check = spawn();
654 other.trigger_gravity_check.enemy = self;
655 other.trigger_gravity_check.owner = other;
656 other.trigger_gravity_check.gravity = other.gravity;
657 other.trigger_gravity_check.think = trigger_gravity_check_think;
658 other.trigger_gravity_check.nextthink = time;
659 other.trigger_gravity_check.count = 2;
664 if (other.gravity != g)
668 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
669 UpdateCSQCProjectile(self.owner);
673 void spawnfunc_trigger_gravity()
675 if(self.gravity == 1)
679 self.touch = trigger_gravity_touch;
681 precache_sound(self.noise);
686 self.use = trigger_gravity_use;
687 if(self.spawnflags & 2)
692 //=============================================================================
694 // TODO add a way to do looped sounds with sound(); then complete this entity
695 .float volume, atten;
696 void target_speaker_use_off();
697 void target_speaker_use_activator()
699 if(clienttype(activator) != CLIENTTYPE_REAL)
702 if(substring(self.noise, 0, 1) == "*")
705 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
706 if(GetPlayerSoundSampleField_notFound)
707 snd = "misc/null.wav";
708 else if(activator.sample == "")
709 snd = "misc/null.wav";
712 tokenize_console(activator.sample);
716 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
718 snd = strcat(argv(0), ".wav"); // randomization
723 msg_entity = activator;
724 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
726 void target_speaker_use_on()
729 if(substring(self.noise, 0, 1) == "*")
732 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
733 if(GetPlayerSoundSampleField_notFound)
734 snd = "misc/null.wav";
735 else if(activator.sample == "")
736 snd = "misc/null.wav";
739 tokenize_console(activator.sample);
743 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
745 snd = strcat(argv(0), ".wav"); // randomization
750 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
751 if(self.spawnflags & 3)
752 self.use = target_speaker_use_off;
754 void target_speaker_use_off()
756 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
757 self.use = target_speaker_use_on;
759 void target_speaker_reset()
761 if(self.spawnflags & 1) // LOOPED_ON
763 if(self.use == target_speaker_use_on)
764 target_speaker_use_on();
766 else if(self.spawnflags & 2)
768 if(self.use == target_speaker_use_off)
769 target_speaker_use_off();
773 void spawnfunc_target_speaker()
775 // TODO: "*" prefix to sound file name
776 // TODO: wait and random (just, HOW? random is not a field)
778 precache_sound (self.noise);
780 if(!self.atten && !(self.spawnflags & 4))
783 self.atten = ATTN_NORM;
785 self.atten = ATTN_STATIC;
787 else if(self.atten < 0)
795 if(self.spawnflags & 8) // ACTIVATOR
796 self.use = target_speaker_use_activator;
797 else if(self.spawnflags & 1) // LOOPED_ON
799 target_speaker_use_on();
800 self.reset = target_speaker_reset;
802 else if(self.spawnflags & 2) // LOOPED_OFF
804 self.use = target_speaker_use_on;
805 self.reset = target_speaker_reset;
808 self.use = target_speaker_use_on;
810 else if(self.spawnflags & 1) // LOOPED_ON
812 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
815 else if(self.spawnflags & 2) // LOOPED_OFF
817 objerror("This sound entity can never be activated");
821 // Quake/Nexuiz fallback
822 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
828 void spawnfunc_func_stardust() {
829 self.effects = EF_STARDUST;
833 .float bgmscriptattack;
834 .float bgmscriptdecay;
835 .float bgmscriptsustain;
836 .float bgmscriptrelease;
837 float pointparticles_SendEntity(entity to, float fl)
839 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
841 // optional features to save space
843 if(self.spawnflags & 2)
844 fl |= 0x10; // absolute count on toggle-on
845 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
846 fl |= 0x20; // 4 bytes - saves CPU
847 if(self.waterlevel || self.count != 1)
848 fl |= 0x40; // 4 bytes - obscure features almost never used
849 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
850 fl |= 0x80; // 14 bytes - saves lots of space
852 WriteByte(MSG_ENTITY, fl);
856 WriteCoord(MSG_ENTITY, self.impulse);
858 WriteCoord(MSG_ENTITY, 0); // off
862 WriteCoord(MSG_ENTITY, self.origin_x);
863 WriteCoord(MSG_ENTITY, self.origin_y);
864 WriteCoord(MSG_ENTITY, self.origin_z);
868 if(self.model != "null")
870 WriteShort(MSG_ENTITY, self.modelindex);
873 WriteCoord(MSG_ENTITY, self.mins_x);
874 WriteCoord(MSG_ENTITY, self.mins_y);
875 WriteCoord(MSG_ENTITY, self.mins_z);
876 WriteCoord(MSG_ENTITY, self.maxs_x);
877 WriteCoord(MSG_ENTITY, self.maxs_y);
878 WriteCoord(MSG_ENTITY, self.maxs_z);
883 WriteShort(MSG_ENTITY, 0);
886 WriteCoord(MSG_ENTITY, self.maxs_x);
887 WriteCoord(MSG_ENTITY, self.maxs_y);
888 WriteCoord(MSG_ENTITY, self.maxs_z);
891 WriteShort(MSG_ENTITY, self.cnt);
894 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
895 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
899 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
900 WriteByte(MSG_ENTITY, self.count * 16.0);
902 WriteString(MSG_ENTITY, self.noise);
905 WriteByte(MSG_ENTITY, floor(self.atten * 64));
906 WriteByte(MSG_ENTITY, floor(self.volume * 255));
908 WriteString(MSG_ENTITY, self.bgmscript);
909 if(self.bgmscript != "")
911 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
912 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
913 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
914 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
920 void pointparticles_use()
922 self.state = !self.state;
926 void pointparticles_think()
928 if(self.origin != self.oldorigin)
931 self.oldorigin = self.origin;
933 self.nextthink = time;
936 void pointparticles_reset()
938 if(self.spawnflags & 1)
944 void spawnfunc_func_pointparticles()
947 setmodel(self, self.model);
949 precache_sound (self.noise);
951 if(!self.bgmscriptsustain)
952 self.bgmscriptsustain = 1;
953 else if(self.bgmscriptsustain < 0)
954 self.bgmscriptsustain = 0;
957 self.atten = ATTN_NORM;
958 else if(self.atten < 0)
969 setorigin(self, self.origin + self.mins);
970 setsize(self, '0 0 0', self.maxs - self.mins);
973 self.cnt = particleeffectnum(self.mdl);
975 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
979 self.use = pointparticles_use;
980 self.reset = pointparticles_reset;
985 self.think = pointparticles_think;
986 self.nextthink = time;
989 void spawnfunc_func_sparks()
991 // self.cnt is the amount of sparks that one burst will spawn
993 self.cnt = 25.0; // nice default value
996 // self.wait is the probability that a sparkthink will spawn a spark shower
997 // range: 0 - 1, but 0 makes little sense, so...
998 if(self.wait < 0.05) {
999 self.wait = 0.25; // nice default value
1002 self.count = self.cnt;
1003 self.mins = '0 0 0';
1004 self.maxs = '0 0 0';
1005 self.velocity = '0 0 -1';
1006 self.mdl = "TE_SPARK";
1007 self.impulse = 10 * self.wait; // by default 2.5/sec
1009 self.cnt = 0; // use mdl
1011 spawnfunc_func_pointparticles();
1014 float rainsnow_SendEntity(entity to, float sf)
1016 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1017 WriteByte(MSG_ENTITY, self.state);
1018 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1019 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1020 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1021 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1022 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1023 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1024 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1025 WriteShort(MSG_ENTITY, self.count);
1026 WriteByte(MSG_ENTITY, self.cnt);
1030 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1031 This is an invisible area like a trigger, which rain falls inside of.
1035 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1037 sets color of rain (default 12 - white)
1039 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1041 void spawnfunc_func_rain()
1043 self.dest = self.velocity;
1044 self.velocity = '0 0 0';
1046 self.dest = '0 0 -700';
1047 self.angles = '0 0 0';
1048 self.movetype = MOVETYPE_NONE;
1049 self.solid = SOLID_NOT;
1050 SetBrushEntityModel();
1055 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1058 if(self.count > 65535)
1061 self.state = 1; // 1 is rain, 0 is snow
1064 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1068 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1069 This is an invisible area like a trigger, which snow falls inside of.
1073 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1075 sets color of rain (default 12 - white)
1077 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1079 void spawnfunc_func_snow()
1081 self.dest = self.velocity;
1082 self.velocity = '0 0 0';
1084 self.dest = '0 0 -300';
1085 self.angles = '0 0 0';
1086 self.movetype = MOVETYPE_NONE;
1087 self.solid = SOLID_NOT;
1088 SetBrushEntityModel();
1093 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1096 if(self.count > 65535)
1099 self.state = 0; // 1 is rain, 0 is snow
1102 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1106 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1109 void misc_laser_aim()
1114 if(self.spawnflags & 2)
1116 if(self.enemy.origin != self.mangle)
1118 self.mangle = self.enemy.origin;
1119 self.SendFlags |= 2;
1124 a = vectoangles(self.enemy.origin - self.origin);
1126 if(a != self.mangle)
1129 self.SendFlags |= 2;
1135 if(self.angles != self.mangle)
1137 self.mangle = self.angles;
1138 self.SendFlags |= 2;
1141 if(self.origin != self.oldorigin)
1143 self.SendFlags |= 1;
1144 self.oldorigin = self.origin;
1148 void misc_laser_init()
1150 if(self.target != "")
1151 self.enemy = find(world, targetname, self.target);
1155 void misc_laser_think()
1162 self.nextthink = time;
1171 o = self.enemy.origin;
1172 if not(self.spawnflags & 2)
1173 o = self.origin + normalize(o - self.origin) * 32768;
1177 makevectors(self.mangle);
1178 o = self.origin + v_forward * 32768;
1181 if(self.dmg || self.enemy.target != "")
1183 traceline(self.origin, o, MOVE_NORMAL, self);
1186 hitloc = trace_endpos;
1188 if(self.enemy.target != "") // DETECTOR laser
1190 if(trace_ent.iscreature)
1192 self.pusher = hitent;
1199 activator = self.pusher;
1212 activator = self.pusher;
1222 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1224 if(hitent.takedamage)
1225 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1229 float laser_SendEntity(entity to, float fl)
1231 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1232 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1233 if(self.spawnflags & 2)
1237 if(self.scale != 1 || self.modelscale != 1)
1239 if(self.spawnflags & 4)
1241 WriteByte(MSG_ENTITY, fl);
1244 WriteCoord(MSG_ENTITY, self.origin_x);
1245 WriteCoord(MSG_ENTITY, self.origin_y);
1246 WriteCoord(MSG_ENTITY, self.origin_z);
1250 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1251 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1252 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1254 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1257 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1258 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1260 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1261 WriteShort(MSG_ENTITY, self.cnt + 1);
1267 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1268 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1269 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1273 WriteAngle(MSG_ENTITY, self.mangle_x);
1274 WriteAngle(MSG_ENTITY, self.mangle_y);
1278 WriteByte(MSG_ENTITY, self.state);
1282 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1283 Any object touching the beam will be hurt
1286 spawnfunc_target_position where the laser ends
1288 name of beam end effect to use
1290 color of the beam (default: red)
1292 damage per second (-1 for a laser that kills immediately)
1296 self.state = !self.state;
1297 self.SendFlags |= 4;
1303 if(self.spawnflags & 1)
1309 void spawnfunc_misc_laser()
1313 if(self.mdl == "none")
1317 self.cnt = particleeffectnum(self.mdl);
1320 self.cnt = particleeffectnum("laser_deadly");
1326 self.cnt = particleeffectnum("laser_deadly");
1333 if(self.colormod == '0 0 0')
1335 self.colormod = '1 0 0';
1337 self.message = "saw the light";
1339 self.message2 = "was pushed into a laser by";
1342 if(!self.modelscale)
1343 self.modelscale = 1;
1344 else if(self.modelscale < 0)
1345 self.modelscale = 0;
1346 self.think = misc_laser_think;
1347 self.nextthink = time;
1348 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1350 self.mangle = self.angles;
1352 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1356 self.reset = laser_reset;
1358 self.use = laser_use;
1364 // tZorks trigger impulse / gravity
1368 .float lastpushtime;
1370 // targeted (directional) mode
1371 void trigger_impulse_touch1()
1374 float pushdeltatime;
1377 if (self.active != ACTIVE_ACTIVE)
1380 if (!isPushable(other))
1385 targ = find(world, targetname, self.target);
1388 objerror("trigger_force without a (valid) .target!\n");
1393 if(self.falloff == 1)
1394 str = (str / self.radius) * self.strength;
1395 else if(self.falloff == 2)
1396 str = (1 - (str / self.radius)) * self.strength;
1398 str = self.strength;
1400 pushdeltatime = time - other.lastpushtime;
1401 if (pushdeltatime > 0.15) pushdeltatime = 0;
1402 other.lastpushtime = time;
1403 if(!pushdeltatime) return;
1405 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1406 other.flags &~= FL_ONGROUND;
1407 UpdateCSQCProjectile(other);
1410 // Directionless (accelerator/decelerator) mode
1411 void trigger_impulse_touch2()
1413 float pushdeltatime;
1415 if (self.active != ACTIVE_ACTIVE)
1418 if (!isPushable(other))
1423 pushdeltatime = time - other.lastpushtime;
1424 if (pushdeltatime > 0.15) pushdeltatime = 0;
1425 other.lastpushtime = time;
1426 if(!pushdeltatime) return;
1428 // div0: ticrate independent, 1 = identity (not 20)
1429 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1430 UpdateCSQCProjectile(other);
1433 // Spherical (gravity/repulsor) mode
1434 void trigger_impulse_touch3()
1436 float pushdeltatime;
1439 if (self.active != ACTIVE_ACTIVE)
1442 if (!isPushable(other))
1447 pushdeltatime = time - other.lastpushtime;
1448 if (pushdeltatime > 0.15) pushdeltatime = 0;
1449 other.lastpushtime = time;
1450 if(!pushdeltatime) return;
1452 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1454 str = min(self.radius, vlen(self.origin - other.origin));
1456 if(self.falloff == 1)
1457 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1458 else if(self.falloff == 2)
1459 str = (str / self.radius) * self.strength; // 0 in the inside
1461 str = self.strength;
1463 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1464 UpdateCSQCProjectile(other);
1467 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1468 -------- KEYS --------
1469 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1470 If not, this trigger acts like a damper/accelerator field.
1472 strength : This is how mutch force to add in the direction of .target each second
1473 when .target is set. If not, this is hoe mutch to slow down/accelerate
1474 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1476 radius : If set, act as a spherical device rather then a liniar one.
1478 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1480 -------- NOTES --------
1481 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1482 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1485 void spawnfunc_trigger_impulse()
1487 self.active = ACTIVE_ACTIVE;
1492 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1493 setorigin(self, self.origin);
1494 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1495 self.touch = trigger_impulse_touch3;
1501 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1502 self.touch = trigger_impulse_touch1;
1506 if(!self.strength) self.strength = 0.9;
1507 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1508 self.touch = trigger_impulse_touch2;
1513 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1514 "Flip-flop" trigger gate... lets only every second trigger event through
1518 self.state = !self.state;
1523 void spawnfunc_trigger_flipflop()
1525 if(self.spawnflags & 1)
1527 self.use = flipflop_use;
1528 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1531 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1532 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1536 self.nextthink = time + self.wait;
1537 self.enemy = activator;
1543 void monoflop_fixed_use()
1547 self.nextthink = time + self.wait;
1549 self.enemy = activator;
1553 void monoflop_think()
1556 activator = self.enemy;
1560 void monoflop_reset()
1566 void spawnfunc_trigger_monoflop()
1570 if(self.spawnflags & 1)
1571 self.use = monoflop_fixed_use;
1573 self.use = monoflop_use;
1574 self.think = monoflop_think;
1576 self.reset = monoflop_reset;
1579 void multivibrator_send()
1584 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1586 newstate = (time < cyclestart + self.wait);
1589 if(self.state != newstate)
1591 self.state = newstate;
1594 self.nextthink = cyclestart + self.wait + 0.01;
1596 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1599 void multivibrator_toggle()
1601 if(self.nextthink == 0)
1603 multivibrator_send();
1616 void multivibrator_reset()
1618 if(!(self.spawnflags & 1))
1619 self.nextthink = 0; // wait for a trigger event
1621 self.nextthink = max(1, time);
1624 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1625 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1626 -------- KEYS --------
1627 target: trigger all entities with this targetname when it goes off
1628 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1629 phase: offset of the timing
1630 wait: "on" cycle time (default: 1)
1631 respawntime: "off" cycle time (default: same as wait)
1632 -------- SPAWNFLAGS --------
1633 START_ON: assume it is already turned on (when targeted)
1635 void spawnfunc_trigger_multivibrator()
1639 if(!self.respawntime)
1640 self.respawntime = self.wait;
1643 self.use = multivibrator_toggle;
1644 self.think = multivibrator_send;
1645 self.nextthink = max(1, time);
1648 multivibrator_reset();
1657 if(self.killtarget != "")
1658 src = find(world, targetname, self.killtarget);
1659 if(self.target != "")
1660 dst = find(world, targetname, self.target);
1664 objerror("follow: could not find target/killtarget");
1670 // already done :P entity must stay
1674 else if(!src || !dst)
1676 objerror("follow: could not find target/killtarget");
1679 else if(self.spawnflags & 1)
1682 if(self.spawnflags & 2)
1684 setattachment(dst, src, self.message);
1688 attach_sameorigin(dst, src, self.message);
1691 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1696 if(self.spawnflags & 2)
1698 dst.movetype = MOVETYPE_FOLLOW;
1700 // dst.punchangle = '0 0 0'; // keep unchanged
1701 dst.view_ofs = dst.origin;
1702 dst.v_angle = dst.angles;
1706 follow_sameorigin(dst, src);
1713 void spawnfunc_misc_follow()
1715 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1720 void gamestart_use() {
1726 void spawnfunc_trigger_gamestart() {
1727 self.use = gamestart_use;
1728 self.reset2 = spawnfunc_trigger_gamestart;
1732 self.think = self.use;
1733 self.nextthink = game_starttime + self.wait;
1736 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1742 .entity voicescript; // attached voice script
1743 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1744 .float voicescript_nextthink; // time to play next voice
1745 .float voicescript_voiceend; // time when this voice ends
1747 void target_voicescript_clear(entity pl)
1749 pl.voicescript = world;
1752 void target_voicescript_use()
1754 if(activator.voicescript != self)
1756 activator.voicescript = self;
1757 activator.voicescript_index = 0;
1758 activator.voicescript_nextthink = time + self.delay;
1762 void target_voicescript_next(entity pl)
1767 vs = pl.voicescript;
1770 if(vs.message == "")
1772 if(pl.classname != "player")
1777 if(time >= pl.voicescript_voiceend)
1779 if(time >= pl.voicescript_nextthink)
1781 // get the next voice...
1782 n = tokenize_console(vs.message);
1784 if(pl.voicescript_index < vs.cnt)
1785 i = pl.voicescript_index * 2;
1786 else if(n > vs.cnt * 2)
1787 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1793 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1794 dt = stof(argv(i + 1));
1797 pl.voicescript_voiceend = time + dt;
1798 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1802 pl.voicescript_voiceend = time - dt;
1803 pl.voicescript_nextthink = pl.voicescript_voiceend;
1806 pl.voicescript_index += 1;
1810 pl.voicescript = world; // stop trying then
1816 void spawnfunc_target_voicescript()
1818 // netname: directory of the sound files
1819 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1820 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1821 // Here, a - in front of the duration means that no delay is to be
1822 // added after this message
1823 // wait: average time between messages
1824 // delay: initial delay before the first message
1827 self.use = target_voicescript_use;
1829 n = tokenize_console(self.message);
1831 for(i = 0; i+1 < n; i += 2)
1838 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1844 void trigger_relay_teamcheck_use()
1848 if(self.spawnflags & 2)
1850 if(activator.team != self.team)
1855 if(activator.team == self.team)
1861 if(self.spawnflags & 1)
1866 void trigger_relay_teamcheck_reset()
1868 self.team = self.team_saved;
1871 void spawnfunc_trigger_relay_teamcheck()
1873 self.team_saved = self.team;
1874 self.use = trigger_relay_teamcheck_use;
1875 self.reset = trigger_relay_teamcheck_reset;
1880 void trigger_disablerelay_use()
1887 for(e = world; (e = find(e, targetname, self.target)); )
1889 if(e.use == SUB_UseTargets)
1891 e.use = SUB_DontUseTargets;
1894 else if(e.use == SUB_DontUseTargets)
1896 e.use = SUB_UseTargets;
1902 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1905 void spawnfunc_trigger_disablerelay()
1907 self.use = trigger_disablerelay_use;
1910 float magicear_matched;
1911 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1912 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1914 float domatch, dotrigger, matchstart, l;
1919 magicear_matched = FALSE;
1921 dotrigger = ((source.classname == "player") && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1922 domatch = ((ear.spawnflags & 32) || dotrigger);
1929 // we are in TUBA mode!
1930 if not(ear.spawnflags & 256)
1933 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1936 magicear_matched = TRUE;
1943 savemessage = self.message;
1944 self.message = string_null;
1946 self.message = savemessage;
1950 if(ear.netname != "")
1956 if(ear.spawnflags & 256) // ENOTUBA
1961 if(ear.spawnflags & 4)
1967 if(ear.spawnflags & 1)
1970 if(ear.spawnflags & 2)
1973 if(ear.spawnflags & 8)
1978 l = strlen(ear.message);
1980 if(ear.spawnflags & 128)
1983 msg = strdecolorize(msgin);
1985 if(substring(ear.message, 0, 1) == "*")
1987 if(substring(ear.message, -1, 1) == "*")
1990 // as we need multi-replacement here...
1991 s = substring(ear.message, 1, -2);
1993 if(strstrofs(msg, s, 0) >= 0)
1994 matchstart = -2; // we use strreplace on s
1999 s = substring(ear.message, 1, -1);
2001 if(substring(msg, -l, l) == s)
2002 matchstart = strlen(msg) - l;
2007 if(substring(ear.message, -1, 1) == "*")
2010 s = substring(ear.message, 0, -2);
2012 if(substring(msg, 0, l) == s)
2019 if(msg == ear.message)
2024 if(matchstart == -1) // no match
2027 magicear_matched = TRUE;
2034 savemessage = self.message;
2035 self.message = string_null;
2037 self.message = savemessage;
2041 if(ear.spawnflags & 16)
2045 else if(ear.netname != "")
2048 return strreplace(s, ear.netname, msg);
2051 substring(msg, 0, matchstart),
2053 substring(msg, matchstart + l, -1)
2061 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2065 for(ear = magicears; ear; ear = ear.enemy)
2067 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2068 if not(ear.spawnflags & 64)
2069 if(magicear_matched)
2076 void spawnfunc_trigger_magicear()
2078 self.enemy = magicears;
2081 // actually handled in "say" processing
2084 // 2 = ignore teamsay
2086 // 8 = ignore tell to unknown player
2087 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2088 // 32 = perform the replacement even if outside the radius or dead
2089 // 64 = continue replacing/triggering even if this one matched
2090 // 128 = don't decolorize message before matching
2091 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2092 // 512 = tuba notes must be exact right pitch, no transposing
2102 // if set, replacement for the matched text
2104 // "hearing distance"
2108 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2110 self.movedir_x -= 1; // map to tuba instrument numbers
2113 void relay_activators_use()
2119 for(trg = world; (trg = find(trg, targetname, os.target)); )
2123 trg.setactive(os.cnt);
2126 //bprint("Not using setactive\n");
2127 if(os.cnt == ACTIVE_TOGGLE)
2128 if(trg.active == ACTIVE_ACTIVE)
2129 trg.active = ACTIVE_NOT;
2131 trg.active = ACTIVE_ACTIVE;
2133 trg.active = os.cnt;
2139 void spawnfunc_relay_activate()
2141 self.cnt = ACTIVE_ACTIVE;
2142 self.use = relay_activators_use;
2145 void spawnfunc_relay_deactivate()
2147 self.cnt = ACTIVE_NOT;
2148 self.use = relay_activators_use;
2151 void spawnfunc_relay_activatetoggle()
2153 self.cnt = ACTIVE_TOGGLE;
2154 self.use = relay_activators_use;
2157 .string chmap, gametype;
2158 void spawnfunc_target_changelevel_use()
2160 if(self.gametype != "")
2161 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2163 if (self.chmap == "")
2164 localcmd("endmatch\n");
2166 localcmd(strcat("changelevel ", self.chmap, "\n"));
2169 void spawnfunc_target_changelevel()
2171 self.use = spawnfunc_target_changelevel_use;