1 void SUB_DontUseTargets() { }
7 activator = self.enemy;
13 ==============================
16 the global "activator" should be set to the entity that initiated the firing.
18 If self.delay is set, a DelayedUse entity will be created that will actually
19 do the SUB_UseTargets after that many seconds have passed.
21 Centerprints any self.message to the activator.
23 Removes all entities with a targetname that match self.killtarget,
24 and removes them, so some events can remove other triggers.
26 Search for (string)targetname in all entities that
27 match (string)self.target and call their .use function
29 ==============================
33 entity t, stemp, otemp, act;
42 // create a temp object to fire at a later time
44 t.classname = "DelayedUse";
45 t.nextthink = time + self.delay;
48 t.message = self.message;
49 t.killtarget = self.killtarget;
50 t.target = self.target;
51 t.target2 = self.target2;
52 t.target3 = self.target3;
53 t.target4 = self.target4;
63 if(IS_PLAYER(activator) && self.message != "")
64 if(IS_REAL_CLIENT(activator))
66 centerprint(activator, self.message);
68 play2(activator, "misc/talk.wav");
72 // kill the killtagets
77 for(t = world; (t = find(t, targetname, s)); )
89 if(stemp.target_random)
90 RandomSelection_Init();
92 for(i = 0; i < 4; ++i)
97 case 0: s = stemp.target; break;
98 case 1: s = stemp.target2; break;
99 case 2: s = stemp.target3; break;
100 case 3: s = stemp.target4; break;
104 for(t = world; (t = find(t, targetname, s)); )
107 if(stemp.target_random)
109 RandomSelection_Add(t, 0, string_null, 1, 0);
122 if(stemp.target_random && RandomSelection_chosen_ent)
124 self = RandomSelection_chosen_ent;
136 //=============================================================================
138 const float SPAWNFLAG_NOMESSAGE = 1;
139 const float SPAWNFLAG_NOTOUCH = 1;
141 // the wait time has passed, so set back up for another activation
146 self.health = self.max_health;
147 self.takedamage = DAMAGE_YES;
148 self.solid = SOLID_BBOX;
153 // the trigger was just touched/killed/used
154 // self.enemy should be set to the activator so it can be held through a delay
155 // so wait for the delay time before firing
158 if (self.nextthink > time)
160 return; // allready been triggered
163 if (self.classname == "trigger_secret")
165 if (!IS_PLAYER(self.enemy))
167 found_secrets = found_secrets + 1;
168 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
172 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
174 // don't trigger again until reset
175 self.takedamage = DAMAGE_NO;
177 activator = self.enemy;
178 other = self.goalentity;
183 self.think = multi_wait;
184 self.nextthink = time + self.wait;
186 else if (self.wait == 0)
188 multi_wait(); // waiting finished
191 { // we can't just remove (self) here, because this is a touch function
192 // called wheil C code is looping through area links...
193 self.touch = func_null;
199 self.goalentity = other;
200 self.enemy = activator;
206 if(!(self.spawnflags & 2))
207 if(!other.iscreature)
211 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
214 // if the trigger has an angles field, check player's facing direction
215 if (self.movedir != '0 0 0')
217 makevectors (other.angles);
218 if (v_forward * self.movedir < 0)
219 return; // not facing the right way
225 self.goalentity = other;
229 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
231 if (!self.takedamage)
233 if(self.spawnflags & DOOR_NOSPLASH)
234 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
236 self.health = self.health - damage;
237 if (self.health <= 0)
239 self.enemy = attacker;
240 self.goalentity = inflictor;
247 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
248 self.touch = multi_touch;
251 self.health = self.max_health;
252 self.takedamage = DAMAGE_YES;
253 self.solid = SOLID_BBOX;
255 self.think = func_null;
257 self.team = self.team_saved;
260 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
261 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.
262 If "delay" is set, the trigger waits some time after activating before firing.
263 "wait" : Seconds between triggerings. (.2 default)
264 If notouch is set, the trigger is only fired by other entities, not by touching.
265 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
271 set "message" to text string
273 void spawnfunc_trigger_multiple()
275 self.reset = multi_reset;
276 if (self.sounds == 1)
278 precache_sound ("misc/secret.wav");
279 self.noise = "misc/secret.wav";
281 else if (self.sounds == 2)
283 precache_sound ("misc/talk.wav");
284 self.noise = "misc/talk.wav";
286 else if (self.sounds == 3)
288 precache_sound ("misc/trigger1.wav");
289 self.noise = "misc/trigger1.wav";
294 else if(self.wait < -1)
296 self.use = multi_use;
300 self.team_saved = self.team;
304 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
305 objerror ("health and notouch don't make sense\n");
306 self.max_health = self.health;
307 self.event_damage = multi_eventdamage;
308 self.takedamage = DAMAGE_YES;
309 self.solid = SOLID_BBOX;
310 setorigin (self, self.origin); // make sure it links into the world
314 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
316 self.touch = multi_touch;
317 setorigin (self, self.origin); // make sure it links into the world
323 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
324 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
325 "targetname". If "health" is set, the trigger must be killed to activate.
326 If notouch is set, the trigger is only fired by other entities, not by touching.
327 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
328 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.
334 set "message" to text string
336 void spawnfunc_trigger_once()
339 spawnfunc_trigger_multiple();
342 //=============================================================================
344 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
345 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
347 void spawnfunc_trigger_relay()
349 self.use = SUB_UseTargets;
350 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
355 self.think = SUB_UseTargets;
356 self.nextthink = self.wait;
361 self.think = func_null;
365 void spawnfunc_trigger_delay()
370 self.use = delay_use;
371 self.reset = delay_reset;
374 //=============================================================================
385 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
386 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
388 self.enemy = activator;
393 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
395 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
397 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
403 self.count = self.cnt;
407 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
408 Acts as an intermediary for an action that takes multiple inputs.
410 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
412 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
414 void spawnfunc_trigger_counter()
419 self.cnt = self.count;
421 self.use = counter_use;
422 self.reset = counter_reset;
425 void trigger_hurt_use()
427 if(IS_PLAYER(activator))
428 self.enemy = activator;
430 self.enemy = world; // let's just destroy it, if taking over is too much work
433 .float triggerhurttime;
434 void trigger_hurt_touch()
436 if (self.active != ACTIVE_ACTIVE)
440 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
443 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
444 if (other.iscreature)
446 if (other.takedamage)
447 if (other.triggerhurttime < time)
450 other.triggerhurttime = time + 1;
457 self.enemy = world; // I still hate you all
460 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
463 else if(other.damagedbytriggers)
468 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
475 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
476 Any object touching this will be hurt
477 set dmg to damage amount
480 .entity trigger_hurt_next;
481 entity trigger_hurt_last;
482 entity trigger_hurt_first;
483 void spawnfunc_trigger_hurt()
486 self.active = ACTIVE_ACTIVE;
487 self.touch = trigger_hurt_touch;
488 self.use = trigger_hurt_use;
489 self.enemy = world; // I hate you all
492 if (self.message == "")
493 self.message = "was in the wrong place";
494 if (self.message2 == "")
495 self.message2 = "was thrown into a world of hurt by";
496 // self.message = "someone like %s always gets wrongplaced";
498 if(!trigger_hurt_first)
499 trigger_hurt_first = self;
500 if(trigger_hurt_last)
501 trigger_hurt_last.trigger_hurt_next = self;
502 trigger_hurt_last = self;
505 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
509 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
510 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
516 //////////////////////////////////////////////////////////////
520 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
522 //////////////////////////////////////////////////////////////
524 .float triggerhealtime;
525 void trigger_heal_touch()
527 if (self.active != ACTIVE_ACTIVE)
530 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
531 if (other.iscreature)
533 if (other.takedamage)
535 if (other.triggerhealtime < time)
538 other.triggerhealtime = time + 1;
540 if (other.health < self.max_health)
542 other.health = min(other.health + self.health, self.max_health);
543 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
544 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
550 void spawnfunc_trigger_heal()
552 self.active = ACTIVE_ACTIVE;
555 self.touch = trigger_heal_touch;
558 if (!self.max_health)
559 self.max_health = 200; //Max health topoff for field
561 self.noise = "misc/mediumhealth.wav";
562 precache_sound(self.noise);
566 //////////////////////////////////////////////////////////////
572 //////////////////////////////////////////////////////////////
574 .entity trigger_gravity_check;
575 void trigger_gravity_remove(entity own)
577 if(own.trigger_gravity_check.owner == own)
579 UpdateCSQCProjectile(own);
580 own.gravity = own.trigger_gravity_check.gravity;
581 remove(own.trigger_gravity_check);
584 backtrace("Removing a trigger_gravity_check with no valid owner");
585 own.trigger_gravity_check = world;
587 void trigger_gravity_check_think()
589 // This spawns when a player enters the gravity zone and checks if he left.
590 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
591 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
594 if(self.owner.trigger_gravity_check == self)
595 trigger_gravity_remove(self.owner);
603 self.nextthink = time;
607 void trigger_gravity_use()
609 self.state = !self.state;
612 void trigger_gravity_touch()
616 if(self.state != TRUE)
623 if (!(self.spawnflags & 1))
625 if(other.trigger_gravity_check)
627 if(self == other.trigger_gravity_check.enemy)
630 other.trigger_gravity_check.count = 2; // gravity one more frame...
635 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
636 trigger_gravity_remove(other);
640 other.trigger_gravity_check = spawn();
641 other.trigger_gravity_check.enemy = self;
642 other.trigger_gravity_check.owner = other;
643 other.trigger_gravity_check.gravity = other.gravity;
644 other.trigger_gravity_check.think = trigger_gravity_check_think;
645 other.trigger_gravity_check.nextthink = time;
646 other.trigger_gravity_check.count = 2;
651 if (other.gravity != g)
655 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
656 UpdateCSQCProjectile(self.owner);
660 void spawnfunc_trigger_gravity()
662 if(self.gravity == 1)
666 self.touch = trigger_gravity_touch;
668 precache_sound(self.noise);
673 self.use = trigger_gravity_use;
674 if(self.spawnflags & 2)
679 //=============================================================================
681 // TODO add a way to do looped sounds with sound(); then complete this entity
682 .float volume, atten;
683 void target_speaker_use_off();
684 void target_speaker_use_activator()
686 if (!IS_REAL_CLIENT(activator))
689 if(substring(self.noise, 0, 1) == "*")
692 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
693 if(GetPlayerSoundSampleField_notFound)
694 snd = "misc/null.wav";
695 else if(activator.sample == "")
696 snd = "misc/null.wav";
699 tokenize_console(activator.sample);
703 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
705 snd = strcat(argv(0), ".wav"); // randomization
710 msg_entity = activator;
711 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
713 void target_speaker_use_on()
716 if(substring(self.noise, 0, 1) == "*")
719 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
720 if(GetPlayerSoundSampleField_notFound)
721 snd = "misc/null.wav";
722 else if(activator.sample == "")
723 snd = "misc/null.wav";
726 tokenize_console(activator.sample);
730 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
732 snd = strcat(argv(0), ".wav"); // randomization
737 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
738 if(self.spawnflags & 3)
739 self.use = target_speaker_use_off;
741 void target_speaker_use_off()
743 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
744 self.use = target_speaker_use_on;
746 void target_speaker_reset()
748 if(self.spawnflags & 1) // LOOPED_ON
750 if(self.use == target_speaker_use_on)
751 target_speaker_use_on();
753 else if(self.spawnflags & 2)
755 if(self.use == target_speaker_use_off)
756 target_speaker_use_off();
760 void spawnfunc_target_speaker()
762 // TODO: "*" prefix to sound file name
763 // TODO: wait and random (just, HOW? random is not a field)
765 precache_sound (self.noise);
767 if(!self.atten && !(self.spawnflags & 4))
770 self.atten = ATTEN_NORM;
772 self.atten = ATTEN_STATIC;
774 else if(self.atten < 0)
782 if(self.spawnflags & 8) // ACTIVATOR
783 self.use = target_speaker_use_activator;
784 else if(self.spawnflags & 1) // LOOPED_ON
786 target_speaker_use_on();
787 self.reset = target_speaker_reset;
789 else if(self.spawnflags & 2) // LOOPED_OFF
791 self.use = target_speaker_use_on;
792 self.reset = target_speaker_reset;
795 self.use = target_speaker_use_on;
797 else if(self.spawnflags & 1) // LOOPED_ON
799 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
802 else if(self.spawnflags & 2) // LOOPED_OFF
804 objerror("This sound entity can never be activated");
808 // Quake/Nexuiz fallback
809 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
815 void spawnfunc_func_stardust() {
816 self.effects = EF_STARDUST;
819 float pointparticles_SendEntity(entity to, float fl)
821 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
823 // optional features to save space
825 if(self.spawnflags & 2)
826 fl |= 0x10; // absolute count on toggle-on
827 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
828 fl |= 0x20; // 4 bytes - saves CPU
829 if(self.waterlevel || self.count != 1)
830 fl |= 0x40; // 4 bytes - obscure features almost never used
831 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
832 fl |= 0x80; // 14 bytes - saves lots of space
834 WriteByte(MSG_ENTITY, fl);
838 WriteCoord(MSG_ENTITY, self.impulse);
840 WriteCoord(MSG_ENTITY, 0); // off
844 WriteCoord(MSG_ENTITY, self.origin_x);
845 WriteCoord(MSG_ENTITY, self.origin_y);
846 WriteCoord(MSG_ENTITY, self.origin_z);
850 if(self.model != "null")
852 WriteShort(MSG_ENTITY, self.modelindex);
855 WriteCoord(MSG_ENTITY, self.mins_x);
856 WriteCoord(MSG_ENTITY, self.mins_y);
857 WriteCoord(MSG_ENTITY, self.mins_z);
858 WriteCoord(MSG_ENTITY, self.maxs_x);
859 WriteCoord(MSG_ENTITY, self.maxs_y);
860 WriteCoord(MSG_ENTITY, self.maxs_z);
865 WriteShort(MSG_ENTITY, 0);
868 WriteCoord(MSG_ENTITY, self.maxs_x);
869 WriteCoord(MSG_ENTITY, self.maxs_y);
870 WriteCoord(MSG_ENTITY, self.maxs_z);
873 WriteShort(MSG_ENTITY, self.cnt);
876 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
877 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
881 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
882 WriteByte(MSG_ENTITY, self.count * 16.0);
884 WriteString(MSG_ENTITY, self.noise);
887 WriteByte(MSG_ENTITY, floor(self.atten * 64));
888 WriteByte(MSG_ENTITY, floor(self.volume * 255));
890 WriteString(MSG_ENTITY, self.bgmscript);
891 if(self.bgmscript != "")
893 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
894 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
895 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
896 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
902 void pointparticles_use()
904 self.state = !self.state;
908 void pointparticles_think()
910 if(self.origin != self.oldorigin)
913 self.oldorigin = self.origin;
915 self.nextthink = time;
918 void pointparticles_reset()
920 if(self.spawnflags & 1)
926 void spawnfunc_func_pointparticles()
929 setmodel(self, self.model);
931 precache_sound (self.noise);
933 if(!self.bgmscriptsustain)
934 self.bgmscriptsustain = 1;
935 else if(self.bgmscriptsustain < 0)
936 self.bgmscriptsustain = 0;
939 self.atten = ATTEN_NORM;
940 else if(self.atten < 0)
951 setorigin(self, self.origin + self.mins);
952 setsize(self, '0 0 0', self.maxs - self.mins);
955 self.cnt = particleeffectnum(self.mdl);
957 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
961 self.use = pointparticles_use;
962 self.reset = pointparticles_reset;
967 self.think = pointparticles_think;
968 self.nextthink = time;
971 void spawnfunc_func_sparks()
973 // self.cnt is the amount of sparks that one burst will spawn
975 self.cnt = 25.0; // nice default value
978 // self.wait is the probability that a sparkthink will spawn a spark shower
979 // range: 0 - 1, but 0 makes little sense, so...
980 if(self.wait < 0.05) {
981 self.wait = 0.25; // nice default value
984 self.count = self.cnt;
987 self.velocity = '0 0 -1';
988 self.mdl = "TE_SPARK";
989 self.impulse = 10 * self.wait; // by default 2.5/sec
991 self.cnt = 0; // use mdl
993 spawnfunc_func_pointparticles();
996 float rainsnow_SendEntity(entity to, float sf)
998 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
999 WriteByte(MSG_ENTITY, self.state);
1000 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1001 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1002 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1003 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1004 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1005 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1006 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1007 WriteShort(MSG_ENTITY, self.count);
1008 WriteByte(MSG_ENTITY, self.cnt);
1012 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1013 This is an invisible area like a trigger, which rain falls inside of.
1017 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1019 sets color of rain (default 12 - white)
1021 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1023 void spawnfunc_func_rain()
1025 self.dest = self.velocity;
1026 self.velocity = '0 0 0';
1028 self.dest = '0 0 -700';
1029 self.angles = '0 0 0';
1030 self.movetype = MOVETYPE_NONE;
1031 self.solid = SOLID_NOT;
1032 SetBrushEntityModel();
1037 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1040 if(self.count > 65535)
1043 self.state = 1; // 1 is rain, 0 is snow
1046 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1050 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1051 This is an invisible area like a trigger, which snow falls inside of.
1055 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1057 sets color of rain (default 12 - white)
1059 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1061 void spawnfunc_func_snow()
1063 self.dest = self.velocity;
1064 self.velocity = '0 0 0';
1066 self.dest = '0 0 -300';
1067 self.angles = '0 0 0';
1068 self.movetype = MOVETYPE_NONE;
1069 self.solid = SOLID_NOT;
1070 SetBrushEntityModel();
1075 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1078 if(self.count > 65535)
1081 self.state = 0; // 1 is rain, 0 is snow
1084 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1088 void misc_laser_aim()
1093 if(self.spawnflags & 2)
1095 if(self.enemy.origin != self.mangle)
1097 self.mangle = self.enemy.origin;
1098 self.SendFlags |= 2;
1103 a = vectoangles(self.enemy.origin - self.origin);
1105 if(a != self.mangle)
1108 self.SendFlags |= 2;
1114 if(self.angles != self.mangle)
1116 self.mangle = self.angles;
1117 self.SendFlags |= 2;
1120 if(self.origin != self.oldorigin)
1122 self.SendFlags |= 1;
1123 self.oldorigin = self.origin;
1127 void misc_laser_init()
1129 if(self.target != "")
1130 self.enemy = find(world, targetname, self.target);
1134 void misc_laser_think()
1141 self.nextthink = time;
1150 o = self.enemy.origin;
1151 if (!(self.spawnflags & 2))
1152 o = self.origin + normalize(o - self.origin) * 32768;
1156 makevectors(self.mangle);
1157 o = self.origin + v_forward * 32768;
1160 if(self.dmg || self.enemy.target != "")
1162 traceline(self.origin, o, MOVE_NORMAL, self);
1165 hitloc = trace_endpos;
1167 if(self.enemy.target != "") // DETECTOR laser
1169 if(trace_ent.iscreature)
1171 self.pusher = hitent;
1178 activator = self.pusher;
1191 activator = self.pusher;
1201 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1203 if(hitent.takedamage)
1204 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1208 float laser_SendEntity(entity to, float fl)
1210 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1211 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1212 if(self.spawnflags & 2)
1216 if(self.scale != 1 || self.modelscale != 1)
1218 if(self.spawnflags & 4)
1220 WriteByte(MSG_ENTITY, fl);
1223 WriteCoord(MSG_ENTITY, self.origin_x);
1224 WriteCoord(MSG_ENTITY, self.origin_y);
1225 WriteCoord(MSG_ENTITY, self.origin_z);
1229 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1230 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1231 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1233 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1236 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1237 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1239 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1240 WriteShort(MSG_ENTITY, self.cnt + 1);
1246 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1247 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1248 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1252 WriteAngle(MSG_ENTITY, self.mangle_x);
1253 WriteAngle(MSG_ENTITY, self.mangle_y);
1257 WriteByte(MSG_ENTITY, self.state);
1261 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1262 Any object touching the beam will be hurt
1265 spawnfunc_target_position where the laser ends
1267 name of beam end effect to use
1269 color of the beam (default: red)
1271 damage per second (-1 for a laser that kills immediately)
1275 self.state = !self.state;
1276 self.SendFlags |= 4;
1282 if(self.spawnflags & 1)
1288 void spawnfunc_misc_laser()
1292 if(self.mdl == "none")
1296 self.cnt = particleeffectnum(self.mdl);
1299 self.cnt = particleeffectnum("laser_deadly");
1305 self.cnt = particleeffectnum("laser_deadly");
1312 if(self.colormod == '0 0 0')
1314 self.colormod = '1 0 0';
1315 if(self.message == "")
1316 self.message = "saw the light";
1317 if (self.message2 == "")
1318 self.message2 = "was pushed into a laser by";
1321 if(!self.modelscale)
1322 self.modelscale = 1;
1323 else if(self.modelscale < 0)
1324 self.modelscale = 0;
1325 self.think = misc_laser_think;
1326 self.nextthink = time;
1327 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1329 self.mangle = self.angles;
1331 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1335 self.reset = laser_reset;
1337 self.use = laser_use;
1343 // tZorks trigger impulse / gravity
1347 .float lastpushtime;
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 .entity voicescript; // attached voice script
1724 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1725 .float voicescript_nextthink; // time to play next voice
1726 .float voicescript_voiceend; // time when this voice ends
1728 void target_voicescript_clear(entity pl)
1730 pl.voicescript = world;
1733 void target_voicescript_use()
1735 if(activator.voicescript != self)
1737 activator.voicescript = self;
1738 activator.voicescript_index = 0;
1739 activator.voicescript_nextthink = time + self.delay;
1743 void target_voicescript_next(entity pl)
1748 vs = pl.voicescript;
1751 if(vs.message == "")
1758 if(time >= pl.voicescript_voiceend)
1760 if(time >= pl.voicescript_nextthink)
1762 // get the next voice...
1763 n = tokenize_console(vs.message);
1765 if(pl.voicescript_index < vs.cnt)
1766 i = pl.voicescript_index * 2;
1767 else if(n > vs.cnt * 2)
1768 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1774 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1775 dt = stof(argv(i + 1));
1778 pl.voicescript_voiceend = time + dt;
1779 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1783 pl.voicescript_voiceend = time - dt;
1784 pl.voicescript_nextthink = pl.voicescript_voiceend;
1787 pl.voicescript_index += 1;
1791 pl.voicescript = world; // stop trying then
1797 void spawnfunc_target_voicescript()
1799 // netname: directory of the sound files
1800 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1801 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1802 // Here, a - in front of the duration means that no delay is to be
1803 // added after this message
1804 // wait: average time between messages
1805 // delay: initial delay before the first message
1808 self.use = target_voicescript_use;
1810 n = tokenize_console(self.message);
1812 for(i = 0; i+1 < n; i += 2)
1819 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1825 void trigger_relay_teamcheck_use()
1829 if(self.spawnflags & 2)
1831 if(activator.team != self.team)
1836 if(activator.team == self.team)
1842 if(self.spawnflags & 1)
1847 void trigger_relay_teamcheck_reset()
1849 self.team = self.team_saved;
1852 void spawnfunc_trigger_relay_teamcheck()
1854 self.team_saved = self.team;
1855 self.use = trigger_relay_teamcheck_use;
1856 self.reset = trigger_relay_teamcheck_reset;
1861 void trigger_disablerelay_use()
1868 for(e = world; (e = find(e, targetname, self.target)); )
1870 if(e.use == SUB_UseTargets)
1872 e.use = SUB_DontUseTargets;
1875 else if(e.use == SUB_DontUseTargets)
1877 e.use = SUB_UseTargets;
1883 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1886 void spawnfunc_trigger_disablerelay()
1888 self.use = trigger_disablerelay_use;
1891 float magicear_matched;
1892 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1893 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1895 float domatch, dotrigger, matchstart, l;
1900 magicear_matched = FALSE;
1902 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1903 domatch = ((ear.spawnflags & 32) || dotrigger);
1910 // we are in TUBA mode!
1911 if (!(ear.spawnflags & 256))
1914 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1917 magicear_matched = TRUE;
1924 savemessage = self.message;
1925 self.message = string_null;
1927 self.message = savemessage;
1931 if(ear.netname != "")
1937 if(ear.spawnflags & 256) // ENOTUBA
1942 if(ear.spawnflags & 4)
1948 if(ear.spawnflags & 1)
1951 if(ear.spawnflags & 2)
1954 if(ear.spawnflags & 8)
1959 l = strlen(ear.message);
1961 if(ear.spawnflags & 128)
1964 msg = strdecolorize(msgin);
1966 if(substring(ear.message, 0, 1) == "*")
1968 if(substring(ear.message, -1, 1) == "*")
1971 // as we need multi-replacement here...
1972 s = substring(ear.message, 1, -2);
1974 if(strstrofs(msg, s, 0) >= 0)
1975 matchstart = -2; // we use strreplace on s
1980 s = substring(ear.message, 1, -1);
1982 if(substring(msg, -l, l) == s)
1983 matchstart = strlen(msg) - l;
1988 if(substring(ear.message, -1, 1) == "*")
1991 s = substring(ear.message, 0, -2);
1993 if(substring(msg, 0, l) == s)
2000 if(msg == ear.message)
2005 if(matchstart == -1) // no match
2008 magicear_matched = TRUE;
2015 savemessage = self.message;
2016 self.message = string_null;
2018 self.message = savemessage;
2022 if(ear.spawnflags & 16)
2026 else if(ear.netname != "")
2029 return strreplace(s, ear.netname, msg);
2032 substring(msg, 0, matchstart),
2034 substring(msg, matchstart + l, -1)
2042 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2046 for(ear = magicears; ear; ear = ear.enemy)
2048 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2049 if(!(ear.spawnflags & 64))
2050 if(magicear_matched)
2057 void spawnfunc_trigger_magicear()
2059 self.enemy = magicears;
2062 // actually handled in "say" processing
2065 // 2 = ignore teamsay
2067 // 8 = ignore tell to unknown player
2068 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2069 // 32 = perform the replacement even if outside the radius or dead
2070 // 64 = continue replacing/triggering even if this one matched
2071 // 128 = don't decolorize message before matching
2072 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2073 // 512 = tuba notes must be exact right pitch, no transposing
2083 // if set, replacement for the matched text
2085 // "hearing distance"
2089 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2091 self.movedir_x -= 1; // map to tuba instrument numbers
2094 void relay_activators_use()
2100 for(trg = world; (trg = find(trg, targetname, os.target)); )
2104 trg.setactive(os.cnt);
2107 //bprint("Not using setactive\n");
2108 if(os.cnt == ACTIVE_TOGGLE)
2109 if(trg.active == ACTIVE_ACTIVE)
2110 trg.active = ACTIVE_NOT;
2112 trg.active = ACTIVE_ACTIVE;
2114 trg.active = os.cnt;
2120 void spawnfunc_relay_activate()
2122 self.cnt = ACTIVE_ACTIVE;
2123 self.use = relay_activators_use;
2126 void spawnfunc_relay_deactivate()
2128 self.cnt = ACTIVE_NOT;
2129 self.use = relay_activators_use;
2132 void spawnfunc_relay_activatetoggle()
2134 self.cnt = ACTIVE_TOGGLE;
2135 self.use = relay_activators_use;
2138 .string chmap, gametype;
2139 void spawnfunc_target_changelevel_use()
2141 if(self.gametype != "")
2142 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2144 if (self.chmap == "")
2145 localcmd("endmatch\n");
2147 localcmd(strcat("changelevel ", self.chmap, "\n"));
2150 void spawnfunc_target_changelevel()
2152 self.use = spawnfunc_target_changelevel_use;