1 #include "g_triggers.qh"
2 #include "t_jumppads.qh"
4 void SUB_DontUseTargets()
11 activator = self.enemy;
17 ==============================
20 the global "activator" should be set to the entity that initiated the firing.
22 If self.delay is set, a DelayedUse entity will be created that will actually
23 do the SUB_UseTargets after that many seconds have passed.
25 Centerprints any self.message to the activator.
27 Removes all entities with a targetname that match self.killtarget,
28 and removes them, so some events can remove other triggers.
30 Search for (string)targetname in all entities that
31 match (string)self.target and call their .use function
33 ==============================
37 entity t, stemp, otemp, act;
46 // create a temp object to fire at a later time
48 t.classname = "DelayedUse";
49 t.nextthink = time + self.delay;
52 t.message = self.message;
53 t.killtarget = self.killtarget;
54 t.target = self.target;
55 t.target2 = self.target2;
56 t.target3 = self.target3;
57 t.target4 = self.target4;
66 if(IS_PLAYER(activator) && self.message != "")
67 if(IS_REAL_CLIENT(activator))
69 centerprint(activator, self.message);
71 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 // Flag to set func_clientwall state
107 // 1 == deactivate, 2 == activate, 0 == do nothing
108 float aw_inactive = self.clientwall_flag;
109 for(t = world; (t = find(t, targetname, s)); )
112 if(stemp.target_random)
114 RandomSelection_Add(t, 0, string_null, 1, 0);
118 if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary") {
119 if (aw_inactive == 1) {
121 } else if (aw_inactive == 2) {
127 t.solid = t.default_solid;
139 if(stemp.target_random && RandomSelection_chosen_ent)
141 self = RandomSelection_chosen_ent;
153 //=============================================================================
155 // the wait time has passed, so set back up for another activation
160 self.health = self.max_health;
161 self.takedamage = DAMAGE_YES;
162 self.solid = SOLID_BBOX;
167 // the trigger was just touched/killed/used
168 // self.enemy should be set to the activator so it can be held through a delay
169 // so wait for the delay time before firing
172 if (self.nextthink > time)
174 return; // allready been triggered
177 if (self.classname == "trigger_secret")
179 if (!IS_PLAYER(self.enemy))
181 found_secrets = found_secrets + 1;
182 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
186 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
188 // don't trigger again until reset
189 self.takedamage = DAMAGE_NO;
191 activator = self.enemy;
192 other = self.goalentity;
197 self.think = multi_wait;
198 self.nextthink = time + self.wait;
200 else if (self.wait == 0)
202 multi_wait(); // waiting finished
205 { // we can't just remove (self) here, because this is a touch function
206 // called wheil C code is looping through area links...
207 self.touch = func_null;
213 self.goalentity = other;
214 self.enemy = activator;
220 if(!(self.spawnflags & 2))
221 if(!other.iscreature)
225 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
228 // if the trigger has an angles field, check player's facing direction
229 if (self.movedir != '0 0 0')
231 makevectors (other.angles);
232 if (v_forward * self.movedir < 0)
233 return; // not facing the right way
239 self.goalentity = other;
243 void multi_eventdamage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
245 if (!self.takedamage)
247 if(self.spawnflags & DOOR_NOSPLASH)
248 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
250 self.health = self.health - damage;
251 if (self.health <= 0)
253 self.enemy = attacker;
254 self.goalentity = inflictor;
261 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
262 self.touch = multi_touch;
265 self.health = self.max_health;
266 self.takedamage = DAMAGE_YES;
267 self.solid = SOLID_BBOX;
269 self.think = func_null;
271 self.team = self.team_saved;
274 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
275 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.
276 If "delay" is set, the trigger waits some time after activating before firing.
277 "wait" : Seconds between triggerings. (.2 default)
278 If notouch is set, the trigger is only fired by other entities, not by touching.
279 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
285 set "message" to text string
287 void spawnfunc_trigger_multiple()
289 self.reset = multi_reset;
290 if (self.sounds == 1)
292 precache_sound ("misc/secret.wav");
293 self.noise = "misc/secret.wav";
295 else if (self.sounds == 2)
297 precache_sound ("misc/talk.wav");
298 self.noise = "misc/talk.wav";
300 else if (self.sounds == 3)
302 precache_sound ("misc/trigger1.wav");
303 self.noise = "misc/trigger1.wav";
308 else if(self.wait < -1)
310 self.use = multi_use;
314 self.team_saved = self.team;
318 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
319 objerror ("health and notouch don't make sense\n");
320 self.max_health = self.health;
321 self.event_damage = multi_eventdamage;
322 self.takedamage = DAMAGE_YES;
323 self.solid = SOLID_BBOX;
324 setorigin (self, self.origin); // make sure it links into the world
328 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
330 self.touch = multi_touch;
331 setorigin (self, self.origin); // make sure it links into the world
337 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
338 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
339 "targetname". If "health" is set, the trigger must be killed to activate.
340 If notouch is set, the trigger is only fired by other entities, not by touching.
341 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
342 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.
348 set "message" to text string
350 void spawnfunc_trigger_once()
353 spawnfunc_trigger_multiple();
356 //=============================================================================
358 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
359 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
361 void spawnfunc_trigger_relay()
363 self.use = SUB_UseTargets;
364 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
369 self.think = SUB_UseTargets;
370 self.nextthink = self.wait;
375 self.think = func_null;
379 void spawnfunc_trigger_delay()
384 self.use = delay_use;
385 self.reset = delay_reset;
388 //=============================================================================
399 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
400 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
402 self.enemy = activator;
407 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
409 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
411 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
417 self.count = self.cnt;
421 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
422 Acts as an intermediary for an action that takes multiple inputs.
424 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
426 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
428 void spawnfunc_trigger_counter()
433 self.cnt = self.count;
435 self.use = counter_use;
436 self.reset = counter_reset;
439 void trigger_hurt_use()
441 if(IS_PLAYER(activator))
442 self.enemy = activator;
444 self.enemy = world; // let's just destroy it, if taking over is too much work
447 void trigger_hurt_touch()
449 if (self.active != ACTIVE_ACTIVE)
453 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
456 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
457 if (other.iscreature)
459 if (other.takedamage)
460 if (other.triggerhurttime < time)
463 other.triggerhurttime = time + 1;
470 self.enemy = world; // I still hate you all
473 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
476 else if(other.damagedbytriggers)
481 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
488 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
489 Any object touching this will be hurt
490 set dmg to damage amount
493 void spawnfunc_trigger_hurt()
496 self.active = ACTIVE_ACTIVE;
497 self.touch = trigger_hurt_touch;
498 self.use = trigger_hurt_use;
499 self.enemy = world; // I hate you all
502 if (self.message == "")
503 self.message = "was in the wrong place";
504 if (self.message2 == "")
505 self.message2 = "was thrown into a world of hurt by";
506 // self.message = "someone like %s always gets wrongplaced";
508 if(!trigger_hurt_first)
509 trigger_hurt_first = self;
510 if(trigger_hurt_last)
511 trigger_hurt_last.trigger_hurt_next = self;
512 trigger_hurt_last = self;
515 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
519 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
520 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
526 //////////////////////////////////////////////////////////////
530 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
532 //////////////////////////////////////////////////////////////
534 void trigger_heal_touch()
536 if (self.active != ACTIVE_ACTIVE)
539 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
540 if (other.iscreature)
542 if (other.takedamage)
544 if (other.triggerhealtime < time)
547 other.triggerhealtime = time + 1;
549 if (other.health < self.max_health)
551 other.health = min(other.health + self.health, self.max_health);
552 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
553 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
559 void spawnfunc_trigger_heal()
561 self.active = ACTIVE_ACTIVE;
564 self.touch = trigger_heal_touch;
567 if (!self.max_health)
568 self.max_health = 200; //Max health topoff for field
570 self.noise = "misc/mediumhealth.wav";
571 precache_sound(self.noise);
575 //////////////////////////////////////////////////////////////
581 //////////////////////////////////////////////////////////////
583 void trigger_gravity_remove(entity own)
585 if(own.trigger_gravity_check.owner == own)
587 UpdateCSQCProjectile(own);
588 own.gravity = own.trigger_gravity_check.gravity;
589 remove(own.trigger_gravity_check);
592 backtrace("Removing a trigger_gravity_check with no valid owner");
593 own.trigger_gravity_check = world;
595 void trigger_gravity_check_think()
597 // This spawns when a player enters the gravity zone and checks if he left.
598 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
599 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
602 if(self.owner.trigger_gravity_check == self)
603 trigger_gravity_remove(self.owner);
611 self.nextthink = time;
615 void trigger_gravity_use()
617 self.state = !self.state;
620 void trigger_gravity_touch()
624 if(self.state != true)
631 if (!(self.spawnflags & 1))
633 if(other.trigger_gravity_check)
635 if(self == other.trigger_gravity_check.enemy)
638 other.trigger_gravity_check.count = 2; // gravity one more frame...
643 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
644 trigger_gravity_remove(other);
648 other.trigger_gravity_check = spawn();
649 other.trigger_gravity_check.enemy = self;
650 other.trigger_gravity_check.owner = other;
651 other.trigger_gravity_check.gravity = other.gravity;
652 other.trigger_gravity_check.think = trigger_gravity_check_think;
653 other.trigger_gravity_check.nextthink = time;
654 other.trigger_gravity_check.count = 2;
659 if (other.gravity != g)
663 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
664 UpdateCSQCProjectile(self.owner);
668 void spawnfunc_trigger_gravity()
670 if(self.gravity == 1)
674 self.touch = trigger_gravity_touch;
676 precache_sound(self.noise);
681 self.use = trigger_gravity_use;
682 if(self.spawnflags & 2)
687 //=============================================================================
689 // TODO add a way to do looped sounds with sound(); then complete this entity
690 void target_speaker_use_activator()
692 if (!IS_REAL_CLIENT(activator))
695 if(substring(self.noise, 0, 1) == "*")
698 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
699 if(GetPlayerSoundSampleField_notFound)
700 snd = "misc/null.wav";
701 else if(activator.sample == "")
702 snd = "misc/null.wav";
705 tokenize_console(activator.sample);
709 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
711 snd = strcat(argv(0), ".wav"); // randomization
716 msg_entity = activator;
717 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
719 void target_speaker_use_on()
722 if(substring(self.noise, 0, 1) == "*")
725 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
726 if(GetPlayerSoundSampleField_notFound)
727 snd = "misc/null.wav";
728 else if(activator.sample == "")
729 snd = "misc/null.wav";
732 tokenize_console(activator.sample);
736 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
738 snd = strcat(argv(0), ".wav"); // randomization
743 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
744 if(self.spawnflags & 3)
745 self.use = target_speaker_use_off;
747 void target_speaker_use_off()
749 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
750 self.use = target_speaker_use_on;
752 void target_speaker_reset()
754 if(self.spawnflags & 1) // LOOPED_ON
756 if(self.use == target_speaker_use_on)
757 target_speaker_use_on();
759 else if(self.spawnflags & 2)
761 if(self.use == target_speaker_use_off)
762 target_speaker_use_off();
766 void spawnfunc_target_speaker()
768 // TODO: "*" prefix to sound file name
769 // TODO: wait and random (just, HOW? random is not a field)
771 precache_sound (self.noise);
773 if(!self.atten && !(self.spawnflags & 4))
776 self.atten = ATTEN_NORM;
778 self.atten = ATTEN_STATIC;
780 else if(self.atten < 0)
788 if(self.spawnflags & 8) // ACTIVATOR
789 self.use = target_speaker_use_activator;
790 else if(self.spawnflags & 1) // LOOPED_ON
792 target_speaker_use_on();
793 self.reset = target_speaker_reset;
795 else if(self.spawnflags & 2) // LOOPED_OFF
797 self.use = target_speaker_use_on;
798 self.reset = target_speaker_reset;
801 self.use = target_speaker_use_on;
803 else if(self.spawnflags & 1) // LOOPED_ON
805 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
808 else if(self.spawnflags & 2) // LOOPED_OFF
810 objerror("This sound entity can never be activated");
814 // Quake/Nexuiz fallback
815 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
821 void spawnfunc_func_stardust() {
822 self.effects = EF_STARDUST;
825 float pointparticles_SendEntity(entity to, float fl)
827 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
829 // optional features to save space
831 if(self.spawnflags & 2)
832 fl |= 0x10; // absolute count on toggle-on
833 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
834 fl |= 0x20; // 4 bytes - saves CPU
835 if(self.waterlevel || self.count != 1)
836 fl |= 0x40; // 4 bytes - obscure features almost never used
837 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
838 fl |= 0x80; // 14 bytes - saves lots of space
840 WriteByte(MSG_ENTITY, fl);
844 WriteCoord(MSG_ENTITY, self.impulse);
846 WriteCoord(MSG_ENTITY, 0); // off
850 WriteCoord(MSG_ENTITY, self.origin.x);
851 WriteCoord(MSG_ENTITY, self.origin.y);
852 WriteCoord(MSG_ENTITY, self.origin.z);
856 if(self.model != "null")
858 WriteShort(MSG_ENTITY, self.modelindex);
861 WriteCoord(MSG_ENTITY, self.mins.x);
862 WriteCoord(MSG_ENTITY, self.mins.y);
863 WriteCoord(MSG_ENTITY, self.mins.z);
864 WriteCoord(MSG_ENTITY, self.maxs.x);
865 WriteCoord(MSG_ENTITY, self.maxs.y);
866 WriteCoord(MSG_ENTITY, self.maxs.z);
871 WriteShort(MSG_ENTITY, 0);
874 WriteCoord(MSG_ENTITY, self.maxs.x);
875 WriteCoord(MSG_ENTITY, self.maxs.y);
876 WriteCoord(MSG_ENTITY, self.maxs.z);
879 WriteShort(MSG_ENTITY, self.cnt);
882 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
883 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
887 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
888 WriteByte(MSG_ENTITY, self.count * 16.0);
890 WriteString(MSG_ENTITY, self.noise);
893 WriteByte(MSG_ENTITY, floor(self.atten * 64));
894 WriteByte(MSG_ENTITY, floor(self.volume * 255));
896 WriteString(MSG_ENTITY, self.bgmscript);
897 if(self.bgmscript != "")
899 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
900 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
901 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
902 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
908 void pointparticles_use()
910 self.state = !self.state;
914 void pointparticles_think()
916 if(self.origin != self.oldorigin)
919 self.oldorigin = self.origin;
921 self.nextthink = time;
924 void pointparticles_reset()
926 if(self.spawnflags & 1)
932 void spawnfunc_func_pointparticles()
935 setmodel(self, self.model);
937 precache_sound (self.noise);
939 if(!self.bgmscriptsustain)
940 self.bgmscriptsustain = 1;
941 else if(self.bgmscriptsustain < 0)
942 self.bgmscriptsustain = 0;
945 self.atten = ATTEN_NORM;
946 else if(self.atten < 0)
957 setorigin(self, self.origin + self.mins);
958 setsize(self, '0 0 0', self.maxs - self.mins);
961 self.cnt = particleeffectnum(self.mdl);
963 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
967 self.use = pointparticles_use;
968 self.reset = pointparticles_reset;
973 self.think = pointparticles_think;
974 self.nextthink = time;
977 void spawnfunc_func_sparks()
979 // self.cnt is the amount of sparks that one burst will spawn
981 self.cnt = 25.0; // nice default value
984 // self.wait is the probability that a sparkthink will spawn a spark shower
985 // range: 0 - 1, but 0 makes little sense, so...
986 if(self.wait < 0.05) {
987 self.wait = 0.25; // nice default value
990 self.count = self.cnt;
993 self.velocity = '0 0 -1';
994 self.mdl = "TE_SPARK";
995 self.impulse = 10 * self.wait; // by default 2.5/sec
997 self.cnt = 0; // use mdl
999 spawnfunc_func_pointparticles();
1002 float rainsnow_SendEntity(entity to, float sf)
1004 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1005 WriteByte(MSG_ENTITY, self.state);
1006 WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
1007 WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
1008 WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
1009 WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
1010 WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
1011 WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
1012 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1013 WriteShort(MSG_ENTITY, self.count);
1014 WriteByte(MSG_ENTITY, self.cnt);
1018 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1019 This is an invisible area like a trigger, which rain falls inside of.
1023 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1025 sets color of rain (default 12 - white)
1027 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1029 void spawnfunc_func_rain()
1031 self.dest = self.velocity;
1032 self.velocity = '0 0 0';
1034 self.dest = '0 0 -700';
1035 self.angles = '0 0 0';
1036 self.movetype = MOVETYPE_NONE;
1037 self.solid = SOLID_NOT;
1038 SetBrushEntityModel();
1043 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1046 if(self.count > 65535)
1049 self.state = 1; // 1 is rain, 0 is snow
1052 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1056 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1057 This is an invisible area like a trigger, which snow falls inside of.
1061 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1063 sets color of rain (default 12 - white)
1065 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1067 void spawnfunc_func_snow()
1069 self.dest = self.velocity;
1070 self.velocity = '0 0 0';
1072 self.dest = '0 0 -300';
1073 self.angles = '0 0 0';
1074 self.movetype = MOVETYPE_NONE;
1075 self.solid = SOLID_NOT;
1076 SetBrushEntityModel();
1081 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1084 if(self.count > 65535)
1087 self.state = 0; // 1 is rain, 0 is snow
1090 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1093 void misc_laser_aim()
1098 if(self.spawnflags & 2)
1100 if(self.enemy.origin != self.mangle)
1102 self.mangle = self.enemy.origin;
1103 self.SendFlags |= 2;
1108 a = vectoangles(self.enemy.origin - self.origin);
1110 if(a != self.mangle)
1113 self.SendFlags |= 2;
1119 if(self.angles != self.mangle)
1121 self.mangle = self.angles;
1122 self.SendFlags |= 2;
1125 if(self.origin != self.oldorigin)
1127 self.SendFlags |= 1;
1128 self.oldorigin = self.origin;
1132 void misc_laser_init()
1134 if(self.target != "")
1135 self.enemy = find(world, targetname, self.target);
1138 void misc_laser_think()
1145 self.nextthink = time;
1154 o = self.enemy.origin;
1155 if (!(self.spawnflags & 2))
1156 o = self.origin + normalize(o - self.origin) * 32768;
1160 makevectors(self.mangle);
1161 o = self.origin + v_forward * 32768;
1164 if(self.dmg || self.enemy.target != "")
1166 traceline(self.origin, o, MOVE_NORMAL, self);
1169 hitloc = trace_endpos;
1171 if(self.enemy.target != "") // DETECTOR laser
1173 if(trace_ent.iscreature)
1175 self.pusher = hitent;
1182 activator = self.pusher;
1195 activator = self.pusher;
1205 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1207 if(hitent.takedamage)
1208 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1212 float laser_SendEntity(entity to, float fl)
1214 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1215 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1216 if(self.spawnflags & 2)
1220 if(self.scale != 1 || self.modelscale != 1)
1222 if(self.spawnflags & 4)
1224 WriteByte(MSG_ENTITY, fl);
1227 WriteCoord(MSG_ENTITY, self.origin.x);
1228 WriteCoord(MSG_ENTITY, self.origin.y);
1229 WriteCoord(MSG_ENTITY, self.origin.z);
1233 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
1234 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
1235 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
1237 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1240 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1241 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1243 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1244 WriteShort(MSG_ENTITY, self.cnt + 1);
1250 WriteCoord(MSG_ENTITY, self.enemy.origin.x);
1251 WriteCoord(MSG_ENTITY, self.enemy.origin.y);
1252 WriteCoord(MSG_ENTITY, self.enemy.origin.z);
1256 WriteAngle(MSG_ENTITY, self.mangle.x);
1257 WriteAngle(MSG_ENTITY, self.mangle.y);
1261 WriteByte(MSG_ENTITY, self.state);
1265 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1266 Any object touching the beam will be hurt
1269 spawnfunc_target_position where the laser ends
1271 name of beam end effect to use
1273 color of the beam (default: red)
1275 damage per second (-1 for a laser that kills immediately)
1279 self.state = !self.state;
1280 self.SendFlags |= 4;
1286 if(self.spawnflags & 1)
1292 void spawnfunc_misc_laser()
1296 if(self.mdl == "none")
1300 self.cnt = particleeffectnum(self.mdl);
1303 self.cnt = particleeffectnum("laser_deadly");
1309 self.cnt = particleeffectnum("laser_deadly");
1316 if(self.colormod == '0 0 0')
1318 self.colormod = '1 0 0';
1319 if(self.message == "")
1320 self.message = "saw the light";
1321 if (self.message2 == "")
1322 self.message2 = "was pushed into a laser by";
1325 if(!self.modelscale)
1326 self.modelscale = 1;
1327 else if(self.modelscale < 0)
1328 self.modelscale = 0;
1329 self.think = misc_laser_think;
1330 self.nextthink = time;
1331 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1333 self.mangle = self.angles;
1335 Net_LinkEntity(self, false, 0, laser_SendEntity);
1339 self.reset = laser_reset;
1341 self.use = laser_use;
1347 // tZorks trigger impulse / gravity
1349 // targeted (directional) mode
1350 void trigger_impulse_touch1()
1353 float pushdeltatime;
1356 if (self.active != ACTIVE_ACTIVE)
1359 if (!isPushable(other))
1364 targ = find(world, targetname, self.target);
1367 objerror("trigger_force without a (valid) .target!\n");
1372 str = min(self.radius, vlen(self.origin - other.origin));
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 if (!isPushable(other))
1404 pushdeltatime = time - other.lastpushtime;
1405 if (pushdeltatime > 0.15) pushdeltatime = 0;
1406 other.lastpushtime = time;
1407 if(!pushdeltatime) return;
1409 // div0: ticrate independent, 1 = identity (not 20)
1410 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1411 UpdateCSQCProjectile(other);
1414 // Spherical (gravity/repulsor) mode
1415 void trigger_impulse_touch3()
1417 float pushdeltatime;
1420 if (self.active != ACTIVE_ACTIVE)
1423 if (!isPushable(other))
1428 pushdeltatime = time - other.lastpushtime;
1429 if (pushdeltatime > 0.15) pushdeltatime = 0;
1430 other.lastpushtime = time;
1431 if(!pushdeltatime) return;
1433 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1435 str = min(self.radius, vlen(self.origin - other.origin));
1437 if(self.falloff == 1)
1438 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1439 else if(self.falloff == 2)
1440 str = (str / self.radius) * self.strength; // 0 in the inside
1442 str = self.strength;
1444 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1445 UpdateCSQCProjectile(other);
1448 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1449 -------- KEYS --------
1450 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1451 If not, this trigger acts like a damper/accelerator field.
1453 strength : This is how mutch force to add in the direction of .target each second
1454 when .target is set. If not, this is hoe mutch to slow down/accelerate
1455 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1457 radius : If set, act as a spherical device rather then a liniar one.
1459 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1461 -------- NOTES --------
1462 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1463 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1466 void spawnfunc_trigger_impulse()
1468 self.active = ACTIVE_ACTIVE;
1473 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1474 setorigin(self, self.origin);
1475 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1476 self.touch = trigger_impulse_touch3;
1482 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1483 self.touch = trigger_impulse_touch1;
1487 if(!self.strength) self.strength = 0.9;
1488 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1489 self.touch = trigger_impulse_touch2;
1494 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1495 "Flip-flop" trigger gate... lets only every second trigger event through
1499 self.state = !self.state;
1504 void spawnfunc_trigger_flipflop()
1506 if(self.spawnflags & 1)
1508 self.use = flipflop_use;
1509 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1512 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1513 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1517 self.nextthink = time + self.wait;
1518 self.enemy = activator;
1524 void monoflop_fixed_use()
1528 self.nextthink = time + self.wait;
1530 self.enemy = activator;
1534 void monoflop_think()
1537 activator = self.enemy;
1541 void monoflop_reset()
1547 void spawnfunc_trigger_monoflop()
1551 if(self.spawnflags & 1)
1552 self.use = monoflop_fixed_use;
1554 self.use = monoflop_use;
1555 self.think = monoflop_think;
1557 self.reset = monoflop_reset;
1560 void multivibrator_send()
1565 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1567 newstate = (time < cyclestart + self.wait);
1570 if(self.state != newstate)
1572 self.state = newstate;
1575 self.nextthink = cyclestart + self.wait + 0.01;
1577 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1580 void multivibrator_toggle()
1582 if(self.nextthink == 0)
1584 multivibrator_send();
1597 void multivibrator_reset()
1599 if(!(self.spawnflags & 1))
1600 self.nextthink = 0; // wait for a trigger event
1602 self.nextthink = max(1, time);
1605 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1606 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1607 -------- KEYS --------
1608 target: trigger all entities with this targetname when it goes off
1609 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1610 phase: offset of the timing
1611 wait: "on" cycle time (default: 1)
1612 respawntime: "off" cycle time (default: same as wait)
1613 -------- SPAWNFLAGS --------
1614 START_ON: assume it is already turned on (when targeted)
1616 void spawnfunc_trigger_multivibrator()
1620 if(!self.respawntime)
1621 self.respawntime = self.wait;
1624 self.use = multivibrator_toggle;
1625 self.think = multivibrator_send;
1626 self.nextthink = max(1, time);
1629 multivibrator_reset();
1638 if(self.killtarget != "")
1639 src = find(world, targetname, self.killtarget);
1640 if(self.target != "")
1641 dst = find(world, targetname, self.target);
1645 objerror("follow: could not find target/killtarget");
1651 // already done :P entity must stay
1655 else if(!src || !dst)
1657 objerror("follow: could not find target/killtarget");
1660 else if(self.spawnflags & 1)
1663 if(self.spawnflags & 2)
1665 setattachment(dst, src, self.message);
1669 attach_sameorigin(dst, src, self.message);
1672 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1677 if(self.spawnflags & 2)
1679 dst.movetype = MOVETYPE_FOLLOW;
1681 // dst.punchangle = '0 0 0'; // keep unchanged
1682 dst.view_ofs = dst.origin;
1683 dst.v_angle = dst.angles;
1687 follow_sameorigin(dst, src);
1694 void spawnfunc_misc_follow()
1696 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1701 void gamestart_use() {
1707 void spawnfunc_trigger_gamestart() {
1708 self.use = gamestart_use;
1709 self.reset2 = spawnfunc_trigger_gamestart;
1713 self.think = self.use;
1714 self.nextthink = game_starttime + self.wait;
1717 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1723 void target_voicescript_clear(entity pl)
1725 pl.voicescript = world;
1728 void target_voicescript_use()
1730 if(activator.voicescript != self)
1732 activator.voicescript = self;
1733 activator.voicescript_index = 0;
1734 activator.voicescript_nextthink = time + self.delay;
1738 void target_voicescript_next(entity pl)
1743 vs = pl.voicescript;
1746 if(vs.message == "")
1753 if(time >= pl.voicescript_voiceend)
1755 if(time >= pl.voicescript_nextthink)
1757 // get the next voice...
1758 n = tokenize_console(vs.message);
1760 if(pl.voicescript_index < vs.cnt)
1761 i = pl.voicescript_index * 2;
1762 else if(n > vs.cnt * 2)
1763 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1769 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1770 dt = stof(argv(i + 1));
1773 pl.voicescript_voiceend = time + dt;
1774 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1778 pl.voicescript_voiceend = time - dt;
1779 pl.voicescript_nextthink = pl.voicescript_voiceend;
1782 pl.voicescript_index += 1;
1786 pl.voicescript = world; // stop trying then
1792 void spawnfunc_target_voicescript()
1794 // netname: directory of the sound files
1795 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1796 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1797 // Here, a - in front of the duration means that no delay is to be
1798 // added after this message
1799 // wait: average time between messages
1800 // delay: initial delay before the first message
1803 self.use = target_voicescript_use;
1805 n = tokenize_console(self.message);
1807 for(i = 0; i+1 < n; i += 2)
1814 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1820 void trigger_relay_teamcheck_use()
1824 if(self.spawnflags & 2)
1826 if(activator.team != self.team)
1831 if(activator.team == self.team)
1837 if(self.spawnflags & 1)
1842 void trigger_relay_teamcheck_reset()
1844 self.team = self.team_saved;
1847 void spawnfunc_trigger_relay_teamcheck()
1849 self.team_saved = self.team;
1850 self.use = trigger_relay_teamcheck_use;
1851 self.reset = trigger_relay_teamcheck_reset;
1856 void trigger_disablerelay_use()
1863 for(e = world; (e = find(e, targetname, self.target)); )
1865 if(e.use == SUB_UseTargets)
1867 e.use = SUB_DontUseTargets;
1870 else if(e.use == SUB_DontUseTargets)
1872 e.use = SUB_UseTargets;
1878 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1881 void spawnfunc_trigger_disablerelay()
1883 self.use = trigger_disablerelay_use;
1886 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1888 float domatch, dotrigger, matchstart, l;
1893 magicear_matched = false;
1895 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1896 domatch = ((ear.spawnflags & 32) || dotrigger);
1903 // we are in TUBA mode!
1904 if (!(ear.spawnflags & 256))
1907 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
1910 magicear_matched = true;
1917 savemessage = self.message;
1918 self.message = string_null;
1920 self.message = savemessage;
1924 if(ear.netname != "")
1930 if(ear.spawnflags & 256) // ENOTUBA
1935 if(ear.spawnflags & 4)
1941 if(ear.spawnflags & 1)
1944 if(ear.spawnflags & 2)
1947 if(ear.spawnflags & 8)
1952 l = strlen(ear.message);
1954 if(ear.spawnflags & 128)
1957 msg = strdecolorize(msgin);
1959 if(substring(ear.message, 0, 1) == "*")
1961 if(substring(ear.message, -1, 1) == "*")
1964 // as we need multi-replacement here...
1965 s = substring(ear.message, 1, -2);
1967 if(strstrofs(msg, s, 0) >= 0)
1968 matchstart = -2; // we use strreplace on s
1973 s = substring(ear.message, 1, -1);
1975 if(substring(msg, -l, l) == s)
1976 matchstart = strlen(msg) - l;
1981 if(substring(ear.message, -1, 1) == "*")
1984 s = substring(ear.message, 0, -2);
1986 if(substring(msg, 0, l) == s)
1993 if(msg == ear.message)
1998 if(matchstart == -1) // no match
2001 magicear_matched = true;
2008 savemessage = self.message;
2009 self.message = string_null;
2011 self.message = savemessage;
2015 if(ear.spawnflags & 16)
2019 else if(ear.netname != "")
2022 return strreplace(s, ear.netname, msg);
2025 substring(msg, 0, matchstart),
2027 substring(msg, matchstart + l, -1)
2034 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2038 for(ear = magicears; ear; ear = ear.enemy)
2040 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2041 if(!(ear.spawnflags & 64))
2042 if(magicear_matched)
2049 void spawnfunc_trigger_magicear()
2051 self.enemy = magicears;
2054 // actually handled in "say" processing
2057 // 2 = ignore teamsay
2059 // 8 = ignore tell to unknown player
2060 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2061 // 32 = perform the replacement even if outside the radius or dead
2062 // 64 = continue replacing/triggering even if this one matched
2063 // 128 = don't decolorize message before matching
2064 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2065 // 512 = tuba notes must be exact right pitch, no transposing
2075 // if set, replacement for the matched text
2077 // "hearing distance"
2081 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2083 self.movedir_x -= 1; // map to tuba instrument numbers
2086 void relay_activators_use()
2092 for(trg = world; (trg = find(trg, targetname, os.target)); )
2096 trg.setactive(os.cnt);
2099 //bprint("Not using setactive\n");
2100 if(os.cnt == ACTIVE_TOGGLE)
2101 if(trg.active == ACTIVE_ACTIVE)
2102 trg.active = ACTIVE_NOT;
2104 trg.active = ACTIVE_ACTIVE;
2106 trg.active = os.cnt;
2112 void spawnfunc_relay_activate()
2114 self.cnt = ACTIVE_ACTIVE;
2115 self.use = relay_activators_use;
2118 void spawnfunc_relay_deactivate()
2120 self.cnt = ACTIVE_NOT;
2121 self.use = relay_activators_use;
2124 void spawnfunc_relay_activatetoggle()
2126 self.cnt = ACTIVE_TOGGLE;
2127 self.use = relay_activators_use;
2130 void spawnfunc_target_changelevel_use()
2132 if(self.gametype != "")
2133 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2135 if (self.chmap == "")
2136 localcmd("endmatch\n");
2138 localcmd(strcat("changelevel ", self.chmap, "\n"));
2141 void spawnfunc_target_changelevel()
2143 self.use = spawnfunc_target_changelevel_use;