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;
61 if (activator.classname == "player" && self.message != "")
63 if(clienttype(activator) == CLIENTTYPE_REAL)
65 centerprint (activator, self.message);
67 play2(activator, "misc/talk.wav");
72 // kill the killtagets
77 for(t = world; (t = find(t, targetname, s)); )
88 for(i = 0; i < 4; ++i)
93 case 0: s = stemp.target; break;
94 case 1: s = stemp.target2; break;
95 case 2: s = stemp.target3; break;
96 case 3: s = stemp.target4; break;
100 for(t = world; (t = find(t, targetname, s)); )
103 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
118 //=============================================================================
120 float SPAWNFLAG_NOMESSAGE = 1;
121 float SPAWNFLAG_NOTOUCH = 1;
123 // the wait time has passed, so set back up for another activation
128 self.health = self.max_health;
129 self.takedamage = DAMAGE_YES;
130 self.solid = SOLID_BBOX;
135 // the trigger was just touched/killed/used
136 // self.enemy should be set to the activator so it can be held through a delay
137 // so wait for the delay time before firing
140 if (self.nextthink > time)
142 return; // allready been triggered
145 if (self.classname == "trigger_secret")
147 if (self.enemy.classname != "player")
149 found_secrets = found_secrets + 1;
150 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
154 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
156 // don't trigger again until reset
157 self.takedamage = DAMAGE_NO;
159 activator = self.enemy;
160 other = self.goalentity;
165 self.think = multi_wait;
166 self.nextthink = time + self.wait;
168 else if (self.wait == 0)
170 multi_wait(); // waiting finished
173 { // we can't just remove (self) here, because this is a touch function
174 // called wheil C code is looping through area links...
175 self.touch = SUB_Null;
181 self.goalentity = other;
182 self.enemy = activator;
188 if not(self.spawnflags & 2)
190 if not(other.iscreature)
194 if(self.team == other.team)
198 // if the trigger has an angles field, check player's facing direction
199 if (self.movedir != '0 0 0')
201 makevectors (other.angles);
202 if (v_forward * self.movedir < 0)
203 return; // not facing the right way
209 self.goalentity = other;
213 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
215 if (!self.takedamage)
217 if(self.spawnflags & DOOR_NOSPLASH)
218 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
220 self.health = self.health - damage;
221 if (self.health <= 0)
223 self.enemy = attacker;
224 self.goalentity = inflictor;
231 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
232 self.touch = multi_touch;
235 self.health = self.max_health;
236 self.takedamage = DAMAGE_YES;
237 self.solid = SOLID_BBOX;
239 self.think = SUB_Null;
240 self.team = self.team_saved;
243 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
244 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.
245 If "delay" is set, the trigger waits some time after activating before firing.
246 "wait" : Seconds between triggerings. (.2 default)
247 If notouch is set, the trigger is only fired by other entities, not by touching.
248 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
254 set "message" to text string
256 void spawnfunc_trigger_multiple()
258 self.reset = multi_reset;
259 if (self.sounds == 1)
261 precache_sound ("misc/secret.wav");
262 self.noise = "misc/secret.wav";
264 else if (self.sounds == 2)
266 precache_sound ("misc/talk.wav");
267 self.noise = "misc/talk.wav";
269 else if (self.sounds == 3)
271 precache_sound ("misc/trigger1.wav");
272 self.noise = "misc/trigger1.wav";
277 else if(self.wait < -1)
279 self.use = multi_use;
283 self.team_saved = self.team;
287 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
288 objerror ("health and notouch don't make sense\n");
289 self.max_health = self.health;
290 self.event_damage = multi_eventdamage;
291 self.takedamage = DAMAGE_YES;
292 self.solid = SOLID_BBOX;
293 setorigin (self, self.origin); // make sure it links into the world
297 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
299 self.touch = multi_touch;
300 setorigin (self, self.origin); // make sure it links into the world
306 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
307 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
308 "targetname". If "health" is set, the trigger must be killed to activate.
309 If notouch is set, the trigger is only fired by other entities, not by touching.
310 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
311 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.
317 set "message" to text string
319 void spawnfunc_trigger_once()
322 spawnfunc_trigger_multiple();
325 //=============================================================================
327 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
328 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
330 void spawnfunc_trigger_relay()
332 self.use = SUB_UseTargets;
333 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
338 self.think = SUB_UseTargets;
339 self.nextthink = self.wait;
344 self.think = SUB_Null;
347 void spawnfunc_trigger_delay()
352 self.use = delay_use;
353 self.reset = delay_reset;
356 //=============================================================================
361 self.count = self.count - 1;
367 if (activator.classname == "player"
368 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
371 centerprint (activator, "There are more to go...");
372 else if (self.count == 3)
373 centerprint (activator, "Only 3 more to go...");
374 else if (self.count == 2)
375 centerprint (activator, "Only 2 more to go...");
377 centerprint (activator, "Only 1 more to go...");
382 if (activator.classname == "player"
383 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
384 centerprint(activator, "Sequence completed!");
385 self.enemy = activator;
391 self.count = self.cnt;
395 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
396 Acts as an intermediary for an action that takes multiple inputs.
398 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
400 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
402 void spawnfunc_trigger_counter()
407 self.cnt = self.count;
409 self.use = counter_use;
410 self.reset = counter_reset;
413 .float triggerhurttime;
414 void trigger_hurt_touch()
416 if (self.active != ACTIVE_ACTIVE)
420 if((self.spawnflags & 4 == 0) == (self.team != other.team))
423 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
424 if (other.iscreature)
426 if (other.takedamage)
427 if (other.triggerhurttime < time)
430 other.triggerhurttime = time + 1;
431 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
438 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
441 other.pain_finished = min(other.pain_finished, time + 2);
443 else if (other.classname == "rune") // reset runes
446 other.nextthink = min(other.nextthink, time + 1);
454 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
455 Any object touching this will be hurt
456 set dmg to damage amount
459 .entity trigger_hurt_next;
460 entity trigger_hurt_last;
461 entity trigger_hurt_first;
462 void spawnfunc_trigger_hurt()
465 self.active = ACTIVE_ACTIVE;
466 self.touch = trigger_hurt_touch;
470 self.message = "was in the wrong place";
472 self.message2 = "was thrown into a world of hurt by";
473 // self.message = "someone like %s always gets wrongplaced";
475 if(!trigger_hurt_first)
476 trigger_hurt_first = self;
477 if(trigger_hurt_last)
478 trigger_hurt_last.trigger_hurt_next = self;
479 trigger_hurt_last = self;
482 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
486 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
487 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
493 //////////////////////////////////////////////////////////////
497 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
499 //////////////////////////////////////////////////////////////
501 .float triggerhealtime;
502 void trigger_heal_touch()
504 if (self.active != ACTIVE_ACTIVE)
507 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
508 if (other.iscreature)
510 if (other.takedamage)
511 if (other.triggerhealtime < time)
514 other.triggerhealtime = time + 1;
516 if (other.health < self.max_health)
518 other.health = min(other.health + self.health, self.max_health);
519 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
520 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
526 void spawnfunc_trigger_heal()
528 self.active = ACTIVE_ACTIVE;
531 self.touch = trigger_heal_touch;
534 if (!self.max_health)
535 self.max_health = 200; //Max health topoff for field
537 self.noise = "misc/mediumhealth.wav";
538 precache_sound(self.noise);
542 //////////////////////////////////////////////////////////////
548 //////////////////////////////////////////////////////////////
550 .entity trigger_gravity_check;
551 void trigger_gravity_remove(entity own)
553 if(own.trigger_gravity_check.owner == own)
555 UpdateCSQCProjectile(own);
556 own.gravity = own.trigger_gravity_check.gravity;
557 remove(own.trigger_gravity_check);
560 backtrace("Removing a trigger_gravity_check with no valid owner");
561 own.trigger_gravity_check = world;
563 void trigger_gravity_check_think()
565 // This spawns when a player enters the gravity zone and checks if he left.
566 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
567 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
570 if(self.owner.trigger_gravity_check == self)
571 trigger_gravity_remove(self.owner);
579 self.nextthink = time;
583 void trigger_gravity_use()
585 self.state = !self.state;
588 void trigger_gravity_touch()
592 if(self.state != TRUE)
599 if not(self.spawnflags & 1)
601 if(other.trigger_gravity_check)
603 if(self == other.trigger_gravity_check.enemy)
606 other.trigger_gravity_check.count = 2; // gravity one more frame...
611 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
612 trigger_gravity_remove(other);
616 other.trigger_gravity_check = spawn();
617 other.trigger_gravity_check.enemy = self;
618 other.trigger_gravity_check.owner = other;
619 other.trigger_gravity_check.gravity = other.gravity;
620 other.trigger_gravity_check.think = trigger_gravity_check_think;
621 other.trigger_gravity_check.nextthink = time;
622 other.trigger_gravity_check.count = 2;
627 if (other.gravity != g)
631 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
632 UpdateCSQCProjectile(self.owner);
636 void spawnfunc_trigger_gravity()
638 if(self.gravity == 1)
642 self.touch = trigger_gravity_touch;
644 precache_sound(self.noise);
649 self.use = trigger_gravity_use;
650 if(self.spawnflags & 2)
655 //=============================================================================
657 // TODO add a way to do looped sounds with sound(); then complete this entity
658 .float volume, atten;
659 void target_speaker_use_off();
660 void target_speaker_use_activator()
662 if(clienttype(activator) != CLIENTTYPE_REAL)
665 if(substring(self.noise, 0, 1) == "*")
668 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
669 if(GetPlayerSoundSampleField_notFound)
670 snd = "misc/null.wav";
671 else if(activator.sample == "")
672 snd = "misc/null.wav";
675 tokenize_console(activator.sample);
678 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
680 snd = strcat(argv(0), ".wav"); // randomization
685 msg_entity = activator;
686 soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
688 void target_speaker_use_on()
691 if(substring(self.noise, 0, 1) == "*")
694 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
695 if(GetPlayerSoundSampleField_notFound)
696 snd = "misc/null.wav";
697 else if(activator.sample == "")
698 snd = "misc/null.wav";
701 tokenize_console(activator.sample);
704 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
706 snd = strcat(argv(0), ".wav"); // randomization
711 sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
712 if(self.spawnflags & 3)
713 self.use = target_speaker_use_off;
715 void target_speaker_use_off()
717 sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
718 self.use = target_speaker_use_on;
721 void spawnfunc_target_speaker()
723 // TODO: "*" prefix to sound file name
724 // TODO: wait and random (just, HOW? random is not a field)
726 precache_sound (self.noise);
728 if(!self.atten && !(self.spawnflags & 4))
731 self.atten = ATTN_NORM;
733 self.atten = ATTN_STATIC;
735 else if(self.atten < 0)
743 if(self.spawnflags & 8) // ACTIVATOR
744 self.use = target_speaker_use_activator;
745 else if(self.spawnflags & 1) // LOOPED_ON
746 target_speaker_use_on();
748 self.use = target_speaker_use_on;
750 else if(self.spawnflags & 1) // LOOPED_ON
752 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
755 else if(self.spawnflags & 2) // LOOPED_OFF
757 objerror("This sound entity can never be activated");
761 // Quake/Nexuiz fallback
762 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
768 void spawnfunc_func_stardust() {
769 self.effects = EF_STARDUST;
773 .float bgmscriptattack;
774 .float bgmscriptdecay;
775 .float bgmscriptsustain;
776 .float bgmscriptrelease;
777 float pointparticles_SendEntity(entity to, float fl)
779 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
781 // optional features to save space
783 if(self.spawnflags & 2)
784 fl |= 0x10; // absolute count on toggle-on
785 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
786 fl |= 0x20; // 4 bytes - saves CPU
787 if(self.waterlevel || self.count != 1)
788 fl |= 0x40; // 4 bytes - obscure features almost never used
789 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
790 fl |= 0x80; // 14 bytes - saves lots of space
792 WriteByte(MSG_ENTITY, fl);
796 WriteCoord(MSG_ENTITY, self.impulse);
798 WriteCoord(MSG_ENTITY, 0); // off
802 WriteCoord(MSG_ENTITY, self.origin_x);
803 WriteCoord(MSG_ENTITY, self.origin_y);
804 WriteCoord(MSG_ENTITY, self.origin_z);
808 if(self.model != "null")
810 WriteShort(MSG_ENTITY, self.modelindex);
813 WriteCoord(MSG_ENTITY, self.mins_x);
814 WriteCoord(MSG_ENTITY, self.mins_y);
815 WriteCoord(MSG_ENTITY, self.mins_z);
816 WriteCoord(MSG_ENTITY, self.maxs_x);
817 WriteCoord(MSG_ENTITY, self.maxs_y);
818 WriteCoord(MSG_ENTITY, self.maxs_z);
823 WriteShort(MSG_ENTITY, 0);
826 WriteCoord(MSG_ENTITY, self.maxs_x);
827 WriteCoord(MSG_ENTITY, self.maxs_y);
828 WriteCoord(MSG_ENTITY, self.maxs_z);
831 WriteShort(MSG_ENTITY, self.cnt);
834 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
835 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
839 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
840 WriteByte(MSG_ENTITY, self.count * 16.0);
842 WriteString(MSG_ENTITY, self.noise);
845 WriteByte(MSG_ENTITY, floor(self.atten * 64));
846 WriteByte(MSG_ENTITY, floor(self.volume * 255));
848 WriteString(MSG_ENTITY, self.bgmscript);
849 if(self.bgmscript != "")
851 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
852 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
853 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
854 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
860 void pointparticles_use()
862 self.state = !self.state;
866 void pointparticles_think()
868 if(self.origin != self.oldorigin)
871 self.oldorigin = self.origin;
873 self.nextthink = time;
876 void pointparticles_reset()
878 if(self.spawnflags & 1)
884 void spawnfunc_func_pointparticles()
887 setmodel(self, self.model);
889 precache_sound (self.noise);
891 if(!self.bgmscriptsustain)
892 self.bgmscriptsustain = 1;
893 else if(self.bgmscriptsustain < 0)
894 self.bgmscriptsustain = 0;
897 self.atten = ATTN_NORM;
898 else if(self.atten < 0)
909 setorigin(self, self.origin + self.mins);
910 setsize(self, '0 0 0', self.maxs - self.mins);
913 self.cnt = particleeffectnum(self.mdl);
915 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
919 self.use = pointparticles_use;
920 self.reset = pointparticles_reset;
925 self.think = pointparticles_think;
926 self.nextthink = time;
929 void spawnfunc_func_sparks()
931 // self.cnt is the amount of sparks that one burst will spawn
933 self.cnt = 25.0; // nice default value
936 // self.wait is the probability that a sparkthink will spawn a spark shower
937 // range: 0 - 1, but 0 makes little sense, so...
938 if(self.wait < 0.05) {
939 self.wait = 0.25; // nice default value
942 self.count = self.cnt;
945 self.velocity = '0 0 -1';
946 self.mdl = "TE_SPARK";
947 self.impulse = 10 * self.wait; // by default 2.5/sec
949 self.cnt = 0; // use mdl
951 spawnfunc_func_pointparticles();
954 float rainsnow_SendEntity(entity to, float sf)
956 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
957 WriteByte(MSG_ENTITY, self.state);
958 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
959 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
960 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
961 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
962 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
963 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
964 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
965 WriteShort(MSG_ENTITY, self.count);
966 WriteByte(MSG_ENTITY, self.cnt);
970 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
971 This is an invisible area like a trigger, which rain falls inside of.
975 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
977 sets color of rain (default 12 - white)
979 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
981 void spawnfunc_func_rain()
983 self.dest = self.velocity;
984 self.velocity = '0 0 0';
986 self.dest = '0 0 -700';
987 self.angles = '0 0 0';
988 self.movetype = MOVETYPE_NONE;
989 self.solid = SOLID_NOT;
990 SetBrushEntityModel();
995 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
998 if(self.count > 65535)
1001 self.state = 1; // 1 is rain, 0 is snow
1004 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1008 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1009 This is an invisible area like a trigger, which snow falls inside of.
1013 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1015 sets color of rain (default 12 - white)
1017 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1019 void spawnfunc_func_snow()
1021 self.dest = self.velocity;
1022 self.velocity = '0 0 0';
1024 self.dest = '0 0 -300';
1025 self.angles = '0 0 0';
1026 self.movetype = MOVETYPE_NONE;
1027 self.solid = SOLID_NOT;
1028 SetBrushEntityModel();
1033 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1036 if(self.count > 65535)
1039 self.state = 0; // 1 is rain, 0 is snow
1042 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1046 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1049 void misc_laser_aim()
1054 if(self.spawnflags & 2)
1056 if(self.enemy.origin != self.mangle)
1058 self.mangle = self.enemy.origin;
1059 self.SendFlags |= 2;
1064 a = vectoangles(self.enemy.origin - self.origin);
1066 if(a != self.mangle)
1069 self.SendFlags |= 2;
1075 if(self.angles != self.mangle)
1077 self.mangle = self.angles;
1078 self.SendFlags |= 2;
1081 if(self.origin != self.oldorigin)
1083 self.SendFlags |= 1;
1084 self.oldorigin = self.origin;
1088 void misc_laser_init()
1090 if(self.target != "")
1091 self.enemy = find(world, targetname, self.target);
1095 void misc_laser_think()
1102 self.nextthink = time;
1111 o = self.enemy.origin;
1112 if not(self.spawnflags & 2)
1113 o = self.origin + normalize(o - self.origin) * 32768;
1117 makevectors(self.mangle);
1118 o = self.origin + v_forward * 32768;
1121 if(self.dmg || self.enemy.target != "")
1123 traceline(self.origin, o, MOVE_NORMAL, self);
1126 hitloc = trace_endpos;
1128 if(self.enemy.target != "") // DETECTOR laser
1130 if(trace_ent.iscreature)
1132 self.pusher = hitent;
1139 activator = self.pusher;
1152 activator = self.pusher;
1162 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1164 if(hitent.takedamage)
1165 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1169 float laser_SendEntity(entity to, float fl)
1171 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1172 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1173 if(self.spawnflags & 2)
1177 if(self.scale != 1 || self.modelscale != 1)
1179 if(self.spawnflags & 4)
1181 WriteByte(MSG_ENTITY, fl);
1184 WriteCoord(MSG_ENTITY, self.origin_x);
1185 WriteCoord(MSG_ENTITY, self.origin_y);
1186 WriteCoord(MSG_ENTITY, self.origin_z);
1190 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1191 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1192 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1194 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1197 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1198 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1200 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1201 WriteShort(MSG_ENTITY, self.cnt + 1);
1207 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1208 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1209 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1213 WriteAngle(MSG_ENTITY, self.mangle_x);
1214 WriteAngle(MSG_ENTITY, self.mangle_y);
1218 WriteByte(MSG_ENTITY, self.state);
1222 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1223 Any object touching the beam will be hurt
1226 spawnfunc_target_position where the laser ends
1228 name of beam end effect to use
1230 color of the beam (default: red)
1232 damage per second (-1 for a laser that kills immediately)
1236 self.state = !self.state;
1237 self.SendFlags |= 4;
1243 if(self.spawnflags & 1)
1249 void spawnfunc_misc_laser()
1253 if(self.mdl == "none")
1257 self.cnt = particleeffectnum(self.mdl);
1260 self.cnt = particleeffectnum("laser_deadly");
1266 self.cnt = particleeffectnum("laser_deadly");
1273 if(self.colormod == '0 0 0')
1275 self.colormod = '1 0 0';
1277 self.message = "saw the light";
1279 self.message2 = "was pushed into a laser by";
1282 if(!self.modelscale)
1283 self.modelscale = 1;
1284 else if(self.modelscale < 0)
1285 self.modelscale = 0;
1286 self.think = misc_laser_think;
1287 self.nextthink = time;
1288 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1290 self.mangle = self.angles;
1292 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1296 self.reset = laser_reset;
1298 self.use = laser_use;
1304 // tZorks trigger impulse / gravity
1308 .float lastpushtime;
1310 // targeted (directional) mode
1311 void trigger_impulse_touch1()
1314 float pushdeltatime;
1317 if (self.active != ACTIVE_ACTIVE)
1320 // FIXME: Better checking for what to push and not.
1321 if not(other.iscreature)
1322 if (other.classname != "corpse")
1323 if (other.classname != "body")
1324 if (other.classname != "gib")
1325 if (other.classname != "missile")
1326 if (other.classname != "rocket")
1327 if (other.classname != "casing")
1328 if (other.classname != "grenade")
1329 if (other.classname != "plasma")
1330 if (other.classname != "plasma_prim")
1331 if (other.classname != "plasma_chain")
1332 if (other.classname != "droppedweapon")
1333 if (other.classname != "nexball_basketball")
1334 if (other.classname != "nexball_football")
1337 if (other.deadflag && other.iscreature)
1342 targ = find(world, targetname, self.target);
1345 objerror("trigger_force without a (valid) .target!\n");
1350 if(self.falloff == 1)
1351 str = (str / self.radius) * self.strength;
1352 else if(self.falloff == 2)
1353 str = (1 - (str / self.radius)) * self.strength;
1355 str = self.strength;
1357 pushdeltatime = time - other.lastpushtime;
1358 if (pushdeltatime > 0.15) pushdeltatime = 0;
1359 other.lastpushtime = time;
1360 if(!pushdeltatime) return;
1362 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1363 other.flags &~= FL_ONGROUND;
1364 UpdateCSQCProjectile(other);
1367 // Directionless (accelerator/decelerator) mode
1368 void trigger_impulse_touch2()
1370 float pushdeltatime;
1372 if (self.active != ACTIVE_ACTIVE)
1375 // FIXME: Better checking for what to push and not.
1376 if not(other.iscreature)
1377 if (other.classname != "corpse")
1378 if (other.classname != "body")
1379 if (other.classname != "gib")
1380 if (other.classname != "missile")
1381 if (other.classname != "rocket")
1382 if (other.classname != "casing")
1383 if (other.classname != "grenade")
1384 if (other.classname != "plasma")
1385 if (other.classname != "plasma_prim")
1386 if (other.classname != "plasma_chain")
1387 if (other.classname != "droppedweapon")
1388 if (other.classname != "nexball_basketball")
1389 if (other.classname != "nexball_football")
1392 if (other.deadflag && other.iscreature)
1397 pushdeltatime = time - other.lastpushtime;
1398 if (pushdeltatime > 0.15) pushdeltatime = 0;
1399 other.lastpushtime = time;
1400 if(!pushdeltatime) return;
1402 // div0: ticrate independent, 1 = identity (not 20)
1403 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1404 UpdateCSQCProjectile(other);
1407 // Spherical (gravity/repulsor) mode
1408 void trigger_impulse_touch3()
1410 float pushdeltatime;
1413 if (self.active != ACTIVE_ACTIVE)
1416 // FIXME: Better checking for what to push and not.
1417 if not(other.iscreature)
1418 if (other.classname != "corpse")
1419 if (other.classname != "body")
1420 if (other.classname != "gib")
1421 if (other.classname != "missile")
1422 if (other.classname != "rocket")
1423 if (other.classname != "casing")
1424 if (other.classname != "grenade")
1425 if (other.classname != "plasma")
1426 if (other.classname != "plasma_prim")
1427 if (other.classname != "plasma_chain")
1428 if (other.classname != "droppedweapon")
1429 if (other.classname != "nexball_basketball")
1430 if (other.classname != "nexball_football")
1433 if (other.deadflag && other.iscreature)
1438 pushdeltatime = time - other.lastpushtime;
1439 if (pushdeltatime > 0.15) pushdeltatime = 0;
1440 other.lastpushtime = time;
1441 if(!pushdeltatime) return;
1443 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1445 str = min(self.radius, vlen(self.origin - other.origin));
1447 if(self.falloff == 1)
1448 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1449 else if(self.falloff == 2)
1450 str = (str / self.radius) * self.strength; // 0 in the inside
1452 str = self.strength;
1454 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1455 UpdateCSQCProjectile(other);
1458 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1459 -------- KEYS --------
1460 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1461 If not, this trigger acts like a damper/accelerator field.
1463 strength : This is how mutch force to add in the direction of .target each second
1464 when .target is set. If not, this is hoe mutch to slow down/accelerate
1465 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1467 radius : If set, act as a spherical device rather then a liniar one.
1469 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1471 -------- NOTES --------
1472 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1473 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1476 void spawnfunc_trigger_impulse()
1478 self.active = ACTIVE_ACTIVE;
1483 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1484 setorigin(self, self.origin);
1485 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1486 self.touch = trigger_impulse_touch3;
1492 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1493 self.touch = trigger_impulse_touch1;
1497 if(!self.strength) self.strength = 0.9;
1498 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1499 self.touch = trigger_impulse_touch2;
1504 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1505 "Flip-flop" trigger gate... lets only every second trigger event through
1509 self.state = !self.state;
1514 void spawnfunc_trigger_flipflop()
1516 if(self.spawnflags & 1)
1518 self.use = flipflop_use;
1519 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1522 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1523 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1527 self.nextthink = time + self.wait;
1528 self.enemy = activator;
1534 void monoflop_fixed_use()
1538 self.nextthink = time + self.wait;
1540 self.enemy = activator;
1544 void monoflop_think()
1547 activator = self.enemy;
1551 void monoflop_reset()
1557 void spawnfunc_trigger_monoflop()
1561 if(self.spawnflags & 1)
1562 self.use = monoflop_fixed_use;
1564 self.use = monoflop_use;
1565 self.think = monoflop_think;
1567 self.reset = monoflop_reset;
1570 void multivibrator_send()
1575 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1577 newstate = (time < cyclestart + self.wait);
1580 if(self.state != newstate)
1582 self.state = newstate;
1585 self.nextthink = cyclestart + self.wait + 0.01;
1587 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1590 void multivibrator_toggle()
1592 if(self.nextthink == 0)
1594 multivibrator_send();
1607 void multivibrator_reset()
1609 if(!(self.spawnflags & 1))
1610 self.nextthink = 0; // wait for a trigger event
1612 self.nextthink = max(1, time);
1615 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1616 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1617 -------- KEYS --------
1618 target: trigger all entities with this targetname when it goes off
1619 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1620 phase: offset of the timing
1621 wait: "on" cycle time (default: 1)
1622 respawntime: "off" cycle time (default: same as wait)
1623 -------- SPAWNFLAGS --------
1624 START_ON: assume it is already turned on (when targeted)
1626 void spawnfunc_trigger_multivibrator()
1630 if(!self.respawntime)
1631 self.respawntime = self.wait;
1634 self.use = multivibrator_toggle;
1635 self.think = multivibrator_send;
1636 self.nextthink = time;
1639 multivibrator_reset();
1648 if(self.killtarget != "")
1649 src = find(world, targetname, self.killtarget);
1650 if(self.target != "")
1651 dst = find(world, targetname, self.target);
1655 objerror("follow: could not find target/killtarget");
1661 // already done :P entity must stay
1665 else if(!src || !dst)
1667 objerror("follow: could not find target/killtarget");
1670 else if(self.spawnflags & 1)
1673 if(self.spawnflags & 2)
1675 setattachment(dst, src, self.message);
1679 attach_sameorigin(dst, src, self.message);
1686 if(self.spawnflags & 2)
1688 dst.movetype = MOVETYPE_FOLLOW;
1690 // dst.punchangle = '0 0 0'; // keep unchanged
1691 dst.view_ofs = dst.origin;
1692 dst.v_angle = dst.angles;
1696 follow_sameorigin(dst, src);
1703 void spawnfunc_misc_follow()
1705 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1710 void gamestart_use() {
1716 void spawnfunc_trigger_gamestart() {
1717 self.use = gamestart_use;
1718 self.reset2 = spawnfunc_trigger_gamestart;
1722 self.think = self.use;
1723 self.nextthink = game_starttime + self.wait;
1726 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1732 .entity voicescript; // attached voice script
1733 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1734 .float voicescript_nextthink; // time to play next voice
1735 .float voicescript_voiceend; // time when this voice ends
1737 void target_voicescript_clear(entity pl)
1739 pl.voicescript = world;
1742 void target_voicescript_use()
1744 if(activator.voicescript != self)
1746 activator.voicescript = self;
1747 activator.voicescript_index = 0;
1748 activator.voicescript_nextthink = time + self.delay;
1752 void target_voicescript_next(entity pl)
1757 vs = pl.voicescript;
1760 if(vs.message == "")
1762 if(pl.classname != "player")
1767 if(time >= pl.voicescript_voiceend)
1769 if(time >= pl.voicescript_nextthink)
1771 // get the next voice...
1772 n = tokenize_console(vs.message);
1774 if(pl.voicescript_index < vs.cnt)
1775 i = pl.voicescript_index * 2;
1776 else if(n > vs.cnt * 2)
1777 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1783 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1784 dt = stof(argv(i + 1));
1787 pl.voicescript_voiceend = time + dt;
1788 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1792 pl.voicescript_voiceend = time - dt;
1793 pl.voicescript_nextthink = pl.voicescript_voiceend;
1796 pl.voicescript_index += 1;
1800 pl.voicescript = world; // stop trying then
1806 void spawnfunc_target_voicescript()
1808 // netname: directory of the sound files
1809 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1810 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1811 // Here, a - in front of the duration means that no delay is to be
1812 // added after this message
1813 // wait: average time between messages
1814 // delay: initial delay before the first message
1817 self.use = target_voicescript_use;
1819 n = tokenize_console(self.message);
1821 for(i = 0; i+1 < n; i += 2)
1828 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1834 void trigger_relay_teamcheck_use()
1838 if(self.spawnflags & 2)
1840 if(activator.team != self.team)
1845 if(activator.team == self.team)
1851 if(self.spawnflags & 1)
1856 void trigger_relay_teamcheck_reset()
1858 self.team = self.team_saved;
1861 void spawnfunc_trigger_relay_teamcheck()
1863 self.team_saved = self.team;
1864 self.use = trigger_relay_teamcheck_use;
1865 self.reset = trigger_relay_teamcheck_reset;
1870 void trigger_disablerelay_use()
1877 for(e = world; (e = find(e, targetname, self.target)); )
1879 if(e.use == SUB_UseTargets)
1881 e.use = SUB_DontUseTargets;
1884 else if(e.use == SUB_DontUseTargets)
1886 e.use = SUB_UseTargets;
1892 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1895 void spawnfunc_trigger_disablerelay()
1897 self.use = trigger_disablerelay_use;
1900 float magicear_matched;
1901 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1903 float domatch, dotrigger, matchstart, l;
1907 magicear_matched = FALSE;
1909 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1910 domatch = ((ear.spawnflags & 32) || dotrigger);
1916 if(ear.spawnflags & 4)
1922 if(ear.spawnflags & 1)
1925 if(ear.spawnflags & 2)
1928 if(ear.spawnflags & 8)
1933 l = strlen(ear.message);
1935 if(self.spawnflags & 128)
1938 msg = strdecolorize(msgin);
1940 if(substring(ear.message, 0, 1) == "*")
1942 if(substring(ear.message, -1, 1) == "*")
1945 // as we need multi-replacement here...
1946 s = substring(ear.message, 1, -2);
1948 if(strstrofs(msg, s, 0) >= 0)
1949 matchstart = -2; // we use strreplace on s
1954 s = substring(ear.message, 1, -1);
1956 if(substring(msg, -l, l) == s)
1957 matchstart = strlen(msg) - l;
1962 if(substring(ear.message, -1, 1) == "*")
1965 s = substring(ear.message, 0, -2);
1967 if(substring(msg, 0, l) == s)
1974 if(msg == ear.message)
1979 if(matchstart == -1) // no match
1982 magicear_matched = TRUE;
1986 oldself = activator = self;
1992 if(ear.spawnflags & 16)
1996 else if(ear.netname != "")
1999 return strreplace(s, ear.netname, msg);
2002 substring(msg, 0, matchstart),
2004 substring(msg, matchstart + l, -1)
2012 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2016 for(ear = magicears; ear; ear = ear.enemy)
2018 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2019 if not(ear.spawnflags & 64)
2020 if(magicear_matched)
2027 void spawnfunc_trigger_magicear()
2029 self.enemy = magicears;
2032 // actually handled in "say" processing
2035 // 2 = ignore teamsay
2037 // 8 = ignore tell to unknown player
2038 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2039 // 32 = perform the replacement even if outside the radius or dead
2040 // 64 = continue replacing/triggering even if this one matched
2050 // if set, replacement for the matched text
2052 // "hearing distance"
2057 void relay_activators_use()
2063 for(trg = world; (trg = find(trg, targetname, os.target)); )
2067 trg.setactive(os.cnt);
2070 //bprint("Not using setactive\n");
2071 if(os.cnt == ACTIVE_TOGGLE)
2072 if(trg.active == ACTIVE_ACTIVE)
2073 trg.active = ACTIVE_NOT;
2075 trg.active = ACTIVE_ACTIVE;
2077 trg.active = os.cnt;
2083 void spawnfunc_relay_activate()
2085 self.cnt = ACTIVE_ACTIVE;
2086 self.use = relay_activators_use;
2089 void spawnfunc_relay_deactivate()
2091 self.cnt = ACTIVE_NOT;
2092 self.use = relay_activators_use;
2095 void spawnfunc_relay_activatetoggle()
2097 self.cnt = ACTIVE_TOGGLE;
2098 self.use = relay_activators_use;
2101 .string chmap, gametype;
2102 void spawnfunc_target_changelevel_use()
2104 if(self.gametype != "")
2105 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2107 if (self.chmap == "")
2108 localcmd("endmatch\n");
2110 localcmd(strcat("changelevel ", self.chmap, "\n"));
2113 void spawnfunc_target_changelevel()
2115 self.use = spawnfunc_target_changelevel_use;