1 void SUB_DontUseTargets()
10 activator = self.enemy;
16 ==============================
19 the global "activator" should be set to the entity that initiated the firing.
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
24 Centerprints any self.message to the activator.
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
32 ==============================
36 local entity t, stemp, otemp, act;
45 // create a temp object to fire at a later time
47 t.classname = "DelayedUse";
48 t.nextthink = time + self.delay;
51 t.message = self.message;
52 t.killtarget = self.killtarget;
53 t.target = self.target;
54 t.target2 = self.target2;
55 t.target3 = self.target3;
56 t.target4 = self.target4;
64 if (activator.classname == "player" && self.message != "")
66 if(clienttype(activator) == CLIENTTYPE_REAL)
68 centerprint (activator, self.message);
70 play2(activator, "misc/talk.wav");
75 // kill the killtagets
80 for(t = world; (t = find(t, targetname, s)); )
91 for(i = 0; i < 4; ++i)
96 case 0: s = stemp.target; break;
97 case 1: s = stemp.target2; break;
98 case 2: s = stemp.target3; break;
99 case 3: s = stemp.target4; break;
103 for(t = world; (t = find(t, targetname, s)); )
106 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
121 //=============================================================================
123 float SPAWNFLAG_NOMESSAGE = 1;
124 float SPAWNFLAG_NOTOUCH = 1;
126 // the wait time has passed, so set back up for another activation
131 self.health = self.max_health;
132 self.takedamage = DAMAGE_YES;
133 self.solid = SOLID_BBOX;
138 // the trigger was just touched/killed/used
139 // self.enemy should be set to the activator so it can be held through a delay
140 // so wait for the delay time before firing
143 if (self.nextthink > time)
145 return; // allready been triggered
148 if (self.classname == "trigger_secret")
150 if (self.enemy.classname != "player")
152 found_secrets = found_secrets + 1;
153 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
157 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
159 // don't trigger again until reset
160 self.takedamage = DAMAGE_NO;
162 activator = self.enemy;
163 other = self.goalentity;
168 self.think = multi_wait;
169 self.nextthink = time + self.wait;
171 else if (self.wait == 0)
173 multi_wait(); // waiting finished
176 { // we can't just remove (self) here, because this is a touch function
177 // called wheil C code is looping through area links...
178 self.touch = SUB_Null;
184 self.goalentity = other;
185 self.enemy = activator;
191 if not(self.spawnflags & 2)
193 if not(other.iscreature)
197 if(self.team == other.team)
201 // if the trigger has an angles field, check player's facing direction
202 if (self.movedir != '0 0 0')
204 makevectors (other.angles);
205 if (v_forward * self.movedir < 0)
206 return; // not facing the right way
212 self.goalentity = other;
216 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
218 if (!self.takedamage)
220 if(self.spawnflags & DOOR_NOSPLASH)
221 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
223 self.health = self.health - damage;
224 if (self.health <= 0)
226 self.enemy = attacker;
227 self.goalentity = inflictor;
234 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
235 self.touch = multi_touch;
238 self.health = self.max_health;
239 self.takedamage = DAMAGE_YES;
240 self.solid = SOLID_BBOX;
242 self.think = SUB_Null;
243 self.team = self.team_saved;
246 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
247 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.
248 If "delay" is set, the trigger waits some time after activating before firing.
249 "wait" : Seconds between triggerings. (.2 default)
250 If notouch is set, the trigger is only fired by other entities, not by touching.
251 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
257 set "message" to text string
259 void spawnfunc_trigger_multiple()
261 self.reset = multi_reset;
262 if (self.sounds == 1)
264 precache_sound ("misc/secret.wav");
265 self.noise = "misc/secret.wav";
267 else if (self.sounds == 2)
269 precache_sound ("misc/talk.wav");
270 self.noise = "misc/talk.wav";
272 else if (self.sounds == 3)
274 precache_sound ("misc/trigger1.wav");
275 self.noise = "misc/trigger1.wav";
280 else if(self.wait < -1)
282 self.use = multi_use;
286 self.team_saved = self.team;
290 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
291 objerror ("health and notouch don't make sense\n");
292 self.max_health = self.health;
293 self.event_damage = multi_eventdamage;
294 self.takedamage = DAMAGE_YES;
295 self.solid = SOLID_BBOX;
296 setorigin (self, self.origin); // make sure it links into the world
300 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
302 self.touch = multi_touch;
303 setorigin (self, self.origin); // make sure it links into the world
309 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
310 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
311 "targetname". If "health" is set, the trigger must be killed to activate.
312 If notouch is set, the trigger is only fired by other entities, not by touching.
313 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
314 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.
320 set "message" to text string
322 void spawnfunc_trigger_once()
325 spawnfunc_trigger_multiple();
328 //=============================================================================
330 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
331 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
333 void spawnfunc_trigger_relay()
335 self.use = SUB_UseTargets;
336 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
341 self.think = SUB_UseTargets;
342 self.nextthink = self.wait;
347 self.think = SUB_Null;
350 void spawnfunc_trigger_delay()
355 self.use = delay_use;
356 self.reset = delay_reset;
359 //=============================================================================
364 self.count = self.count - 1;
370 if (activator.classname == "player"
371 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
374 centerprint (activator, "There are more to go...");
375 else if (self.count == 3)
376 centerprint (activator, "Only 3 more to go...");
377 else if (self.count == 2)
378 centerprint (activator, "Only 2 more to go...");
380 centerprint (activator, "Only 1 more to go...");
385 if (activator.classname == "player"
386 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
387 centerprint(activator, "Sequence completed!");
388 self.enemy = activator;
394 self.count = self.cnt;
398 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
399 Acts as an intermediary for an action that takes multiple inputs.
401 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
403 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
405 void spawnfunc_trigger_counter()
410 self.cnt = self.count;
412 self.use = counter_use;
413 self.reset = counter_reset;
416 .float triggerhurttime;
417 void trigger_hurt_touch()
419 if (self.active != ACTIVE_ACTIVE)
423 if((self.spawnflags & 4 == 0) == (self.team != other.team))
426 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
427 if (other.iscreature)
429 if (other.takedamage)
430 if (other.triggerhurttime < time)
433 other.triggerhurttime = time + 1;
434 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
441 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
444 other.pain_finished = min(other.pain_finished, time + 2);
446 else if (other.classname == "rune") // reset runes
449 other.nextthink = min(other.nextthink, time + 1);
457 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
458 Any object touching this will be hurt
459 set dmg to damage amount
462 .entity trigger_hurt_next;
463 entity trigger_hurt_last;
464 entity trigger_hurt_first;
465 void spawnfunc_trigger_hurt()
468 self.active = ACTIVE_ACTIVE;
469 self.touch = trigger_hurt_touch;
473 self.message = "was in the wrong place";
475 self.message2 = "was thrown into a world of hurt by";
476 // self.message = "someone like %s always gets wrongplaced";
478 if(!trigger_hurt_first)
479 trigger_hurt_first = self;
480 if(trigger_hurt_last)
481 trigger_hurt_last.trigger_hurt_next = self;
482 trigger_hurt_last = self;
485 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
489 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
490 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
496 //////////////////////////////////////////////////////////////
500 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
502 //////////////////////////////////////////////////////////////
504 .float triggerhealtime;
505 void trigger_heal_touch()
507 if (self.active != ACTIVE_ACTIVE)
510 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
511 if (other.iscreature)
513 if (other.takedamage)
514 if (other.triggerhealtime < time)
517 other.triggerhealtime = time + 1;
519 if (other.health < self.max_health)
521 other.health = min(other.health + self.health, self.max_health);
522 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
523 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
529 void spawnfunc_trigger_heal()
531 self.active = ACTIVE_ACTIVE;
534 self.touch = trigger_heal_touch;
537 if (!self.max_health)
538 self.max_health = 200; //Max health topoff for field
540 self.noise = "misc/mediumhealth.wav";
541 precache_sound(self.noise);
545 //////////////////////////////////////////////////////////////
551 //////////////////////////////////////////////////////////////
553 .entity trigger_gravity_check;
554 void trigger_gravity_remove(entity own)
556 if(own.trigger_gravity_check.owner == own)
558 UpdateCSQCProjectile(own);
559 own.gravity = own.trigger_gravity_check.gravity;
560 remove(own.trigger_gravity_check);
563 backtrace("Removing a trigger_gravity_check with no valid owner");
564 own.trigger_gravity_check = world;
566 void trigger_gravity_check_think()
568 // This spawns when a player enters the gravity zone and checks if he left.
569 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
570 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
573 if(self.owner.trigger_gravity_check == self)
574 trigger_gravity_remove(self.owner);
582 self.nextthink = time;
586 void trigger_gravity_use()
588 self.state = !self.state;
591 void trigger_gravity_touch()
595 if(self.state != TRUE)
602 if not(self.spawnflags & 1)
604 if(other.trigger_gravity_check)
606 if(self == other.trigger_gravity_check.enemy)
609 other.trigger_gravity_check.count = 2; // gravity one more frame...
614 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
615 trigger_gravity_remove(other);
619 other.trigger_gravity_check = spawn();
620 other.trigger_gravity_check.enemy = self;
621 other.trigger_gravity_check.owner = other;
622 other.trigger_gravity_check.gravity = other.gravity;
623 other.trigger_gravity_check.think = trigger_gravity_check_think;
624 other.trigger_gravity_check.nextthink = time;
625 other.trigger_gravity_check.count = 2;
630 if (other.gravity != g)
634 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
635 UpdateCSQCProjectile(self.owner);
639 void spawnfunc_trigger_gravity()
641 if(self.gravity == 1)
645 self.touch = trigger_gravity_touch;
647 precache_sound(self.noise);
652 self.use = trigger_gravity_use;
653 if(self.spawnflags & 2)
658 //=============================================================================
660 // TODO add a way to do looped sounds with sound(); then complete this entity
661 .float volume, atten;
662 void target_speaker_use_off();
663 void target_speaker_use_activator()
665 if(clienttype(activator) != CLIENTTYPE_REAL)
668 if(substring(self.noise, 0, 1) == "*")
671 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
672 if(GetPlayerSoundSampleField_notFound)
673 snd = "misc/null.wav";
674 else if(activator.sample == "")
675 snd = "misc/null.wav";
678 tokenize_console(activator.sample);
682 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
684 snd = strcat(argv(0), ".wav"); // randomization
689 msg_entity = activator;
690 soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
692 void target_speaker_use_on()
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 sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
717 if(self.spawnflags & 3)
718 self.use = target_speaker_use_off;
720 void target_speaker_use_off()
722 sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
723 self.use = target_speaker_use_on;
725 void target_speaker_reset()
727 if(self.spawnflags & 1) // LOOPED_ON
729 if(self.use == target_speaker_use_on)
730 target_speaker_use_on();
732 else if(self.spawnflags & 2)
734 if(self.use == target_speaker_use_off)
735 target_speaker_use_off();
739 void spawnfunc_target_speaker()
741 // TODO: "*" prefix to sound file name
742 // TODO: wait and random (just, HOW? random is not a field)
744 precache_sound (self.noise);
746 if(!self.atten && !(self.spawnflags & 4))
749 self.atten = ATTN_NORM;
751 self.atten = ATTN_STATIC;
753 else if(self.atten < 0)
761 if(self.spawnflags & 8) // ACTIVATOR
762 self.use = target_speaker_use_activator;
763 else if(self.spawnflags & 1) // LOOPED_ON
765 target_speaker_use_on();
766 self.reset = target_speaker_reset;
768 else if(self.spawnflags & 2) // LOOPED_OFF
770 self.use = target_speaker_use_on;
771 self.reset = target_speaker_reset;
774 self.use = target_speaker_use_on;
776 else if(self.spawnflags & 1) // LOOPED_ON
778 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
781 else if(self.spawnflags & 2) // LOOPED_OFF
783 objerror("This sound entity can never be activated");
787 // Quake/Nexuiz fallback
788 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
794 void spawnfunc_func_stardust() {
795 self.effects = EF_STARDUST;
799 .float bgmscriptattack;
800 .float bgmscriptdecay;
801 .float bgmscriptsustain;
802 .float bgmscriptrelease;
803 float pointparticles_SendEntity(entity to, float fl)
805 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
807 // optional features to save space
809 if(self.spawnflags & 2)
810 fl |= 0x10; // absolute count on toggle-on
811 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
812 fl |= 0x20; // 4 bytes - saves CPU
813 if(self.waterlevel || self.count != 1)
814 fl |= 0x40; // 4 bytes - obscure features almost never used
815 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
816 fl |= 0x80; // 14 bytes - saves lots of space
818 WriteByte(MSG_ENTITY, fl);
822 WriteCoord(MSG_ENTITY, self.impulse);
824 WriteCoord(MSG_ENTITY, 0); // off
828 WriteCoord(MSG_ENTITY, self.origin_x);
829 WriteCoord(MSG_ENTITY, self.origin_y);
830 WriteCoord(MSG_ENTITY, self.origin_z);
834 if(self.model != "null")
836 WriteShort(MSG_ENTITY, self.modelindex);
839 WriteCoord(MSG_ENTITY, self.mins_x);
840 WriteCoord(MSG_ENTITY, self.mins_y);
841 WriteCoord(MSG_ENTITY, self.mins_z);
842 WriteCoord(MSG_ENTITY, self.maxs_x);
843 WriteCoord(MSG_ENTITY, self.maxs_y);
844 WriteCoord(MSG_ENTITY, self.maxs_z);
849 WriteShort(MSG_ENTITY, 0);
852 WriteCoord(MSG_ENTITY, self.maxs_x);
853 WriteCoord(MSG_ENTITY, self.maxs_y);
854 WriteCoord(MSG_ENTITY, self.maxs_z);
857 WriteShort(MSG_ENTITY, self.cnt);
860 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
861 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
865 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
866 WriteByte(MSG_ENTITY, self.count * 16.0);
868 WriteString(MSG_ENTITY, self.noise);
871 WriteByte(MSG_ENTITY, floor(self.atten * 64));
872 WriteByte(MSG_ENTITY, floor(self.volume * 255));
874 WriteString(MSG_ENTITY, self.bgmscript);
875 if(self.bgmscript != "")
877 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
878 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
879 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
880 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
886 void pointparticles_use()
888 self.state = !self.state;
892 void pointparticles_think()
894 if(self.origin != self.oldorigin)
897 self.oldorigin = self.origin;
899 self.nextthink = time;
902 void pointparticles_reset()
904 if(self.spawnflags & 1)
910 void spawnfunc_func_pointparticles()
913 setmodel(self, self.model);
915 precache_sound (self.noise);
917 if(!self.bgmscriptsustain)
918 self.bgmscriptsustain = 1;
919 else if(self.bgmscriptsustain < 0)
920 self.bgmscriptsustain = 0;
923 self.atten = ATTN_NORM;
924 else if(self.atten < 0)
935 setorigin(self, self.origin + self.mins);
936 setsize(self, '0 0 0', self.maxs - self.mins);
939 self.cnt = particleeffectnum(self.mdl);
941 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
945 self.use = pointparticles_use;
946 self.reset = pointparticles_reset;
951 self.think = pointparticles_think;
952 self.nextthink = time;
955 void spawnfunc_func_sparks()
957 // self.cnt is the amount of sparks that one burst will spawn
959 self.cnt = 25.0; // nice default value
962 // self.wait is the probability that a sparkthink will spawn a spark shower
963 // range: 0 - 1, but 0 makes little sense, so...
964 if(self.wait < 0.05) {
965 self.wait = 0.25; // nice default value
968 self.count = self.cnt;
971 self.velocity = '0 0 -1';
972 self.mdl = "TE_SPARK";
973 self.impulse = 10 * self.wait; // by default 2.5/sec
975 self.cnt = 0; // use mdl
977 spawnfunc_func_pointparticles();
980 float rainsnow_SendEntity(entity to, float sf)
982 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
983 WriteByte(MSG_ENTITY, self.state);
984 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
985 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
986 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
987 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
988 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
989 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
990 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
991 WriteShort(MSG_ENTITY, self.count);
992 WriteByte(MSG_ENTITY, self.cnt);
996 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
997 This is an invisible area like a trigger, which rain falls inside of.
1001 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1003 sets color of rain (default 12 - white)
1005 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1007 void spawnfunc_func_rain()
1009 self.dest = self.velocity;
1010 self.velocity = '0 0 0';
1012 self.dest = '0 0 -700';
1013 self.angles = '0 0 0';
1014 self.movetype = MOVETYPE_NONE;
1015 self.solid = SOLID_NOT;
1016 SetBrushEntityModel();
1021 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1024 if(self.count > 65535)
1027 self.state = 1; // 1 is rain, 0 is snow
1030 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1034 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1035 This is an invisible area like a trigger, which snow falls inside of.
1039 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1041 sets color of rain (default 12 - white)
1043 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1045 void spawnfunc_func_snow()
1047 self.dest = self.velocity;
1048 self.velocity = '0 0 0';
1050 self.dest = '0 0 -300';
1051 self.angles = '0 0 0';
1052 self.movetype = MOVETYPE_NONE;
1053 self.solid = SOLID_NOT;
1054 SetBrushEntityModel();
1059 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1062 if(self.count > 65535)
1065 self.state = 0; // 1 is rain, 0 is snow
1068 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1072 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1075 void misc_laser_aim()
1080 if(self.spawnflags & 2)
1082 if(self.enemy.origin != self.mangle)
1084 self.mangle = self.enemy.origin;
1085 self.SendFlags |= 2;
1090 a = vectoangles(self.enemy.origin - self.origin);
1092 if(a != self.mangle)
1095 self.SendFlags |= 2;
1101 if(self.angles != self.mangle)
1103 self.mangle = self.angles;
1104 self.SendFlags |= 2;
1107 if(self.origin != self.oldorigin)
1109 self.SendFlags |= 1;
1110 self.oldorigin = self.origin;
1114 void misc_laser_init()
1116 if(self.target != "")
1117 self.enemy = find(world, targetname, self.target);
1121 void misc_laser_think()
1128 self.nextthink = time;
1137 o = self.enemy.origin;
1138 if not(self.spawnflags & 2)
1139 o = self.origin + normalize(o - self.origin) * 32768;
1143 makevectors(self.mangle);
1144 o = self.origin + v_forward * 32768;
1147 if(self.dmg || self.enemy.target != "")
1149 traceline(self.origin, o, MOVE_NORMAL, self);
1152 hitloc = trace_endpos;
1154 if(self.enemy.target != "") // DETECTOR laser
1156 if(trace_ent.iscreature)
1158 self.pusher = hitent;
1165 activator = self.pusher;
1178 activator = self.pusher;
1188 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1190 if(hitent.takedamage)
1191 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1195 float laser_SendEntity(entity to, float fl)
1197 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1198 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1199 if(self.spawnflags & 2)
1203 if(self.scale != 1 || self.modelscale != 1)
1205 if(self.spawnflags & 4)
1207 WriteByte(MSG_ENTITY, fl);
1210 WriteCoord(MSG_ENTITY, self.origin_x);
1211 WriteCoord(MSG_ENTITY, self.origin_y);
1212 WriteCoord(MSG_ENTITY, self.origin_z);
1216 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1217 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1218 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1220 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1223 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1224 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1226 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1227 WriteShort(MSG_ENTITY, self.cnt + 1);
1233 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1234 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1235 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1239 WriteAngle(MSG_ENTITY, self.mangle_x);
1240 WriteAngle(MSG_ENTITY, self.mangle_y);
1244 WriteByte(MSG_ENTITY, self.state);
1248 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1249 Any object touching the beam will be hurt
1252 spawnfunc_target_position where the laser ends
1254 name of beam end effect to use
1256 color of the beam (default: red)
1258 damage per second (-1 for a laser that kills immediately)
1262 self.state = !self.state;
1263 self.SendFlags |= 4;
1269 if(self.spawnflags & 1)
1275 void spawnfunc_misc_laser()
1279 if(self.mdl == "none")
1283 self.cnt = particleeffectnum(self.mdl);
1286 self.cnt = particleeffectnum("laser_deadly");
1292 self.cnt = particleeffectnum("laser_deadly");
1299 if(self.colormod == '0 0 0')
1301 self.colormod = '1 0 0';
1303 self.message = "saw the light";
1305 self.message2 = "was pushed into a laser by";
1308 if(!self.modelscale)
1309 self.modelscale = 1;
1310 else if(self.modelscale < 0)
1311 self.modelscale = 0;
1312 self.think = misc_laser_think;
1313 self.nextthink = time;
1314 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1316 self.mangle = self.angles;
1318 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1322 self.reset = laser_reset;
1324 self.use = laser_use;
1330 // tZorks trigger impulse / gravity
1334 .float lastpushtime;
1336 // targeted (directional) mode
1337 void trigger_impulse_touch1()
1340 float pushdeltatime;
1343 if (self.active != ACTIVE_ACTIVE)
1346 // FIXME: Better checking for what to push and not.
1347 if not(other.iscreature)
1348 if (other.classname != "corpse")
1349 if (other.classname != "body")
1350 if (other.classname != "gib")
1351 if (other.classname != "missile")
1352 if (other.classname != "rocket")
1353 if (other.classname != "casing")
1354 if (other.classname != "grenade")
1355 if (other.classname != "plasma")
1356 if (other.classname != "plasma_prim")
1357 if (other.classname != "plasma_chain")
1358 if (other.classname != "droppedweapon")
1359 if (other.classname != "nexball_basketball")
1360 if (other.classname != "nexball_football")
1363 if (other.deadflag && other.iscreature)
1368 targ = find(world, targetname, self.target);
1371 objerror("trigger_force without a (valid) .target!\n");
1376 if(self.falloff == 1)
1377 str = (str / self.radius) * self.strength;
1378 else if(self.falloff == 2)
1379 str = (1 - (str / self.radius)) * self.strength;
1381 str = self.strength;
1383 pushdeltatime = time - other.lastpushtime;
1384 if (pushdeltatime > 0.15) pushdeltatime = 0;
1385 other.lastpushtime = time;
1386 if(!pushdeltatime) return;
1388 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1389 other.flags &~= FL_ONGROUND;
1390 UpdateCSQCProjectile(other);
1393 // Directionless (accelerator/decelerator) mode
1394 void trigger_impulse_touch2()
1396 float pushdeltatime;
1398 if (self.active != ACTIVE_ACTIVE)
1401 // FIXME: Better checking for what to push and not.
1402 if not(other.iscreature)
1403 if (other.classname != "corpse")
1404 if (other.classname != "body")
1405 if (other.classname != "gib")
1406 if (other.classname != "missile")
1407 if (other.classname != "rocket")
1408 if (other.classname != "casing")
1409 if (other.classname != "grenade")
1410 if (other.classname != "plasma")
1411 if (other.classname != "plasma_prim")
1412 if (other.classname != "plasma_chain")
1413 if (other.classname != "droppedweapon")
1414 if (other.classname != "nexball_basketball")
1415 if (other.classname != "nexball_football")
1418 if (other.deadflag && other.iscreature)
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 // FIXME: Better checking for what to push and not.
1443 if not(other.iscreature)
1444 if (other.classname != "corpse")
1445 if (other.classname != "body")
1446 if (other.classname != "gib")
1447 if (other.classname != "missile")
1448 if (other.classname != "rocket")
1449 if (other.classname != "casing")
1450 if (other.classname != "grenade")
1451 if (other.classname != "plasma")
1452 if (other.classname != "plasma_prim")
1453 if (other.classname != "plasma_chain")
1454 if (other.classname != "droppedweapon")
1455 if (other.classname != "nexball_basketball")
1456 if (other.classname != "nexball_football")
1459 if (other.deadflag && other.iscreature)
1464 pushdeltatime = time - other.lastpushtime;
1465 if (pushdeltatime > 0.15) pushdeltatime = 0;
1466 other.lastpushtime = time;
1467 if(!pushdeltatime) return;
1469 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1471 str = min(self.radius, vlen(self.origin - other.origin));
1473 if(self.falloff == 1)
1474 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1475 else if(self.falloff == 2)
1476 str = (str / self.radius) * self.strength; // 0 in the inside
1478 str = self.strength;
1480 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1481 UpdateCSQCProjectile(other);
1484 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1485 -------- KEYS --------
1486 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1487 If not, this trigger acts like a damper/accelerator field.
1489 strength : This is how mutch force to add in the direction of .target each second
1490 when .target is set. If not, this is hoe mutch to slow down/accelerate
1491 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1493 radius : If set, act as a spherical device rather then a liniar one.
1495 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1497 -------- NOTES --------
1498 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1499 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1502 void spawnfunc_trigger_impulse()
1504 self.active = ACTIVE_ACTIVE;
1509 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1510 setorigin(self, self.origin);
1511 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1512 self.touch = trigger_impulse_touch3;
1518 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1519 self.touch = trigger_impulse_touch1;
1523 if(!self.strength) self.strength = 0.9;
1524 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1525 self.touch = trigger_impulse_touch2;
1530 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1531 "Flip-flop" trigger gate... lets only every second trigger event through
1535 self.state = !self.state;
1540 void spawnfunc_trigger_flipflop()
1542 if(self.spawnflags & 1)
1544 self.use = flipflop_use;
1545 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1548 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1549 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1553 self.nextthink = time + self.wait;
1554 self.enemy = activator;
1560 void monoflop_fixed_use()
1564 self.nextthink = time + self.wait;
1566 self.enemy = activator;
1570 void monoflop_think()
1573 activator = self.enemy;
1577 void monoflop_reset()
1583 void spawnfunc_trigger_monoflop()
1587 if(self.spawnflags & 1)
1588 self.use = monoflop_fixed_use;
1590 self.use = monoflop_use;
1591 self.think = monoflop_think;
1593 self.reset = monoflop_reset;
1596 void multivibrator_send()
1601 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1603 newstate = (time < cyclestart + self.wait);
1606 if(self.state != newstate)
1608 self.state = newstate;
1611 self.nextthink = cyclestart + self.wait + 0.01;
1613 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1616 void multivibrator_toggle()
1618 if(self.nextthink == 0)
1620 multivibrator_send();
1633 void multivibrator_reset()
1635 if(!(self.spawnflags & 1))
1636 self.nextthink = 0; // wait for a trigger event
1638 self.nextthink = max(1, time);
1641 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1642 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1643 -------- KEYS --------
1644 target: trigger all entities with this targetname when it goes off
1645 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1646 phase: offset of the timing
1647 wait: "on" cycle time (default: 1)
1648 respawntime: "off" cycle time (default: same as wait)
1649 -------- SPAWNFLAGS --------
1650 START_ON: assume it is already turned on (when targeted)
1652 void spawnfunc_trigger_multivibrator()
1656 if(!self.respawntime)
1657 self.respawntime = self.wait;
1660 self.use = multivibrator_toggle;
1661 self.think = multivibrator_send;
1662 self.nextthink = time;
1665 multivibrator_reset();
1674 if(self.killtarget != "")
1675 src = find(world, targetname, self.killtarget);
1676 if(self.target != "")
1677 dst = find(world, targetname, self.target);
1681 objerror("follow: could not find target/killtarget");
1687 // already done :P entity must stay
1691 else if(!src || !dst)
1693 objerror("follow: could not find target/killtarget");
1696 else if(self.spawnflags & 1)
1699 if(self.spawnflags & 2)
1701 setattachment(dst, src, self.message);
1705 attach_sameorigin(dst, src, self.message);
1712 if(self.spawnflags & 2)
1714 dst.movetype = MOVETYPE_FOLLOW;
1716 // dst.punchangle = '0 0 0'; // keep unchanged
1717 dst.view_ofs = dst.origin;
1718 dst.v_angle = dst.angles;
1722 follow_sameorigin(dst, src);
1729 void spawnfunc_misc_follow()
1731 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1736 void gamestart_use() {
1742 void spawnfunc_trigger_gamestart() {
1743 self.use = gamestart_use;
1744 self.reset2 = spawnfunc_trigger_gamestart;
1748 self.think = self.use;
1749 self.nextthink = game_starttime + self.wait;
1752 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1758 .entity voicescript; // attached voice script
1759 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1760 .float voicescript_nextthink; // time to play next voice
1761 .float voicescript_voiceend; // time when this voice ends
1763 void target_voicescript_clear(entity pl)
1765 pl.voicescript = world;
1768 void target_voicescript_use()
1770 if(activator.voicescript != self)
1772 activator.voicescript = self;
1773 activator.voicescript_index = 0;
1774 activator.voicescript_nextthink = time + self.delay;
1778 void target_voicescript_next(entity pl)
1783 vs = pl.voicescript;
1786 if(vs.message == "")
1788 if(pl.classname != "player")
1793 if(time >= pl.voicescript_voiceend)
1795 if(time >= pl.voicescript_nextthink)
1797 // get the next voice...
1798 n = tokenize_console(vs.message);
1800 if(pl.voicescript_index < vs.cnt)
1801 i = pl.voicescript_index * 2;
1802 else if(n > vs.cnt * 2)
1803 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1809 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1810 dt = stof(argv(i + 1));
1813 pl.voicescript_voiceend = time + dt;
1814 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1818 pl.voicescript_voiceend = time - dt;
1819 pl.voicescript_nextthink = pl.voicescript_voiceend;
1822 pl.voicescript_index += 1;
1826 pl.voicescript = world; // stop trying then
1832 void spawnfunc_target_voicescript()
1834 // netname: directory of the sound files
1835 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1836 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1837 // Here, a - in front of the duration means that no delay is to be
1838 // added after this message
1839 // wait: average time between messages
1840 // delay: initial delay before the first message
1843 self.use = target_voicescript_use;
1845 n = tokenize_console(self.message);
1847 for(i = 0; i+1 < n; i += 2)
1854 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1860 void trigger_relay_teamcheck_use()
1864 if(self.spawnflags & 2)
1866 if(activator.team != self.team)
1871 if(activator.team == self.team)
1877 if(self.spawnflags & 1)
1882 void trigger_relay_teamcheck_reset()
1884 self.team = self.team_saved;
1887 void spawnfunc_trigger_relay_teamcheck()
1889 self.team_saved = self.team;
1890 self.use = trigger_relay_teamcheck_use;
1891 self.reset = trigger_relay_teamcheck_reset;
1896 void trigger_disablerelay_use()
1903 for(e = world; (e = find(e, targetname, self.target)); )
1905 if(e.use == SUB_UseTargets)
1907 e.use = SUB_DontUseTargets;
1910 else if(e.use == SUB_DontUseTargets)
1912 e.use = SUB_UseTargets;
1918 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1921 void spawnfunc_trigger_disablerelay()
1923 self.use = trigger_disablerelay_use;
1926 float magicear_matched;
1927 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1929 float domatch, dotrigger, matchstart, l;
1933 magicear_matched = FALSE;
1935 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1936 domatch = ((ear.spawnflags & 32) || dotrigger);
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(self.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;
2012 oldself = activator = self;
2018 if(ear.spawnflags & 16)
2022 else if(ear.netname != "")
2025 return strreplace(s, ear.netname, msg);
2028 substring(msg, 0, matchstart),
2030 substring(msg, matchstart + l, -1)
2038 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2042 for(ear = magicears; ear; ear = ear.enemy)
2044 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2045 if not(ear.spawnflags & 64)
2046 if(magicear_matched)
2053 void spawnfunc_trigger_magicear()
2055 self.enemy = magicears;
2058 // actually handled in "say" processing
2061 // 2 = ignore teamsay
2063 // 8 = ignore tell to unknown player
2064 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2065 // 32 = perform the replacement even if outside the radius or dead
2066 // 64 = continue replacing/triggering even if this one matched
2076 // if set, replacement for the matched text
2078 // "hearing distance"
2083 void relay_activators_use()
2089 for(trg = world; (trg = find(trg, targetname, os.target)); )
2093 trg.setactive(os.cnt);
2096 //bprint("Not using setactive\n");
2097 if(os.cnt == ACTIVE_TOGGLE)
2098 if(trg.active == ACTIVE_ACTIVE)
2099 trg.active = ACTIVE_NOT;
2101 trg.active = ACTIVE_ACTIVE;
2103 trg.active = os.cnt;
2109 void spawnfunc_relay_activate()
2111 self.cnt = ACTIVE_ACTIVE;
2112 self.use = relay_activators_use;
2115 void spawnfunc_relay_deactivate()
2117 self.cnt = ACTIVE_NOT;
2118 self.use = relay_activators_use;
2121 void spawnfunc_relay_activatetoggle()
2123 self.cnt = ACTIVE_TOGGLE;
2124 self.use = relay_activators_use;
2127 .string chmap, gametype;
2128 void spawnfunc_target_changelevel_use()
2130 if(self.gametype != "")
2131 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2133 if (self.chmap == "")
2134 localcmd("endmatch\n");
2136 localcmd(strcat("changelevel ", self.chmap, "\n"));
2139 void spawnfunc_target_changelevel()
2141 self.use = spawnfunc_target_changelevel_use;