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);
679 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
681 snd = strcat(argv(0), ".wav"); // randomization
686 msg_entity = activator;
687 soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
689 void target_speaker_use_on()
692 if(substring(self.noise, 0, 1) == "*")
695 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
696 if(GetPlayerSoundSampleField_notFound)
697 snd = "misc/null.wav";
698 else if(activator.sample == "")
699 snd = "misc/null.wav";
702 tokenize_console(activator.sample);
706 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
708 snd = strcat(argv(0), ".wav"); // randomization
713 sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
714 if(self.spawnflags & 3)
715 self.use = target_speaker_use_off;
717 void target_speaker_use_off()
719 sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
720 self.use = target_speaker_use_on;
723 void spawnfunc_target_speaker()
725 // TODO: "*" prefix to sound file name
726 // TODO: wait and random (just, HOW? random is not a field)
728 precache_sound (self.noise);
730 if(!self.atten && !(self.spawnflags & 4))
733 self.atten = ATTN_NORM;
735 self.atten = ATTN_STATIC;
737 else if(self.atten < 0)
745 if(self.spawnflags & 8) // ACTIVATOR
746 self.use = target_speaker_use_activator;
747 else if(self.spawnflags & 1) // LOOPED_ON
748 target_speaker_use_on();
750 self.use = target_speaker_use_on;
752 else if(self.spawnflags & 1) // LOOPED_ON
754 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
757 else if(self.spawnflags & 2) // LOOPED_OFF
759 objerror("This sound entity can never be activated");
763 // Quake/Nexuiz fallback
764 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
770 void spawnfunc_func_stardust() {
771 self.effects = EF_STARDUST;
775 .float bgmscriptattack;
776 .float bgmscriptdecay;
777 .float bgmscriptsustain;
778 .float bgmscriptrelease;
779 float pointparticles_SendEntity(entity to, float fl)
781 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
783 // optional features to save space
785 if(self.spawnflags & 2)
786 fl |= 0x10; // absolute count on toggle-on
787 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
788 fl |= 0x20; // 4 bytes - saves CPU
789 if(self.waterlevel || self.count != 1)
790 fl |= 0x40; // 4 bytes - obscure features almost never used
791 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
792 fl |= 0x80; // 14 bytes - saves lots of space
794 WriteByte(MSG_ENTITY, fl);
798 WriteCoord(MSG_ENTITY, self.impulse);
800 WriteCoord(MSG_ENTITY, 0); // off
804 WriteCoord(MSG_ENTITY, self.origin_x);
805 WriteCoord(MSG_ENTITY, self.origin_y);
806 WriteCoord(MSG_ENTITY, self.origin_z);
810 if(self.model != "null")
812 WriteShort(MSG_ENTITY, self.modelindex);
815 WriteCoord(MSG_ENTITY, self.mins_x);
816 WriteCoord(MSG_ENTITY, self.mins_y);
817 WriteCoord(MSG_ENTITY, self.mins_z);
818 WriteCoord(MSG_ENTITY, self.maxs_x);
819 WriteCoord(MSG_ENTITY, self.maxs_y);
820 WriteCoord(MSG_ENTITY, self.maxs_z);
825 WriteShort(MSG_ENTITY, 0);
828 WriteCoord(MSG_ENTITY, self.maxs_x);
829 WriteCoord(MSG_ENTITY, self.maxs_y);
830 WriteCoord(MSG_ENTITY, self.maxs_z);
833 WriteShort(MSG_ENTITY, self.cnt);
836 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
837 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
841 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
842 WriteByte(MSG_ENTITY, self.count * 16.0);
844 WriteString(MSG_ENTITY, self.noise);
847 WriteByte(MSG_ENTITY, floor(self.atten * 64));
848 WriteByte(MSG_ENTITY, floor(self.volume * 255));
850 WriteString(MSG_ENTITY, self.bgmscript);
851 if(self.bgmscript != "")
853 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
854 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
855 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
856 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
862 void pointparticles_use()
864 self.state = !self.state;
868 void pointparticles_think()
870 if(self.origin != self.oldorigin)
873 self.oldorigin = self.origin;
875 self.nextthink = time;
878 void pointparticles_reset()
880 if(self.spawnflags & 1)
886 void spawnfunc_func_pointparticles()
889 setmodel(self, self.model);
891 precache_sound (self.noise);
893 if(!self.bgmscriptsustain)
894 self.bgmscriptsustain = 1;
895 else if(self.bgmscriptsustain < 0)
896 self.bgmscriptsustain = 0;
899 self.atten = ATTN_NORM;
900 else if(self.atten < 0)
911 setorigin(self, self.origin + self.mins);
912 setsize(self, '0 0 0', self.maxs - self.mins);
915 self.cnt = particleeffectnum(self.mdl);
917 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
921 self.use = pointparticles_use;
922 self.reset = pointparticles_reset;
927 self.think = pointparticles_think;
928 self.nextthink = time;
931 void spawnfunc_func_sparks()
933 // self.cnt is the amount of sparks that one burst will spawn
935 self.cnt = 25.0; // nice default value
938 // self.wait is the probability that a sparkthink will spawn a spark shower
939 // range: 0 - 1, but 0 makes little sense, so...
940 if(self.wait < 0.05) {
941 self.wait = 0.25; // nice default value
944 self.count = self.cnt;
947 self.velocity = '0 0 -1';
948 self.mdl = "TE_SPARK";
949 self.impulse = 10 * self.wait; // by default 2.5/sec
951 self.cnt = 0; // use mdl
953 spawnfunc_func_pointparticles();
956 float rainsnow_SendEntity(entity to, float sf)
958 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
959 WriteByte(MSG_ENTITY, self.state);
960 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
961 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
962 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
963 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
964 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
965 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
966 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
967 WriteShort(MSG_ENTITY, self.count);
968 WriteByte(MSG_ENTITY, self.cnt);
972 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
973 This is an invisible area like a trigger, which rain falls inside of.
977 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
979 sets color of rain (default 12 - white)
981 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
983 void spawnfunc_func_rain()
985 self.dest = self.velocity;
986 self.velocity = '0 0 0';
988 self.dest = '0 0 -700';
989 self.angles = '0 0 0';
990 self.movetype = MOVETYPE_NONE;
991 self.solid = SOLID_NOT;
992 SetBrushEntityModel();
997 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1000 if(self.count > 65535)
1003 self.state = 1; // 1 is rain, 0 is snow
1006 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1010 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1011 This is an invisible area like a trigger, which snow falls inside of.
1015 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1017 sets color of rain (default 12 - white)
1019 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1021 void spawnfunc_func_snow()
1023 self.dest = self.velocity;
1024 self.velocity = '0 0 0';
1026 self.dest = '0 0 -300';
1027 self.angles = '0 0 0';
1028 self.movetype = MOVETYPE_NONE;
1029 self.solid = SOLID_NOT;
1030 SetBrushEntityModel();
1035 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1038 if(self.count > 65535)
1041 self.state = 0; // 1 is rain, 0 is snow
1044 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1048 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1051 void misc_laser_aim()
1056 if(self.spawnflags & 2)
1058 if(self.enemy.origin != self.mangle)
1060 self.mangle = self.enemy.origin;
1061 self.SendFlags |= 2;
1066 a = vectoangles(self.enemy.origin - self.origin);
1068 if(a != self.mangle)
1071 self.SendFlags |= 2;
1077 if(self.angles != self.mangle)
1079 self.mangle = self.angles;
1080 self.SendFlags |= 2;
1083 if(self.origin != self.oldorigin)
1085 self.SendFlags |= 1;
1086 self.oldorigin = self.origin;
1090 void misc_laser_init()
1092 if(self.target != "")
1093 self.enemy = find(world, targetname, self.target);
1097 void misc_laser_think()
1104 self.nextthink = time;
1113 o = self.enemy.origin;
1114 if not(self.spawnflags & 2)
1115 o = self.origin + normalize(o - self.origin) * 32768;
1119 makevectors(self.mangle);
1120 o = self.origin + v_forward * 32768;
1123 if(self.dmg || self.enemy.target != "")
1125 traceline(self.origin, o, MOVE_NORMAL, self);
1128 hitloc = trace_endpos;
1130 if(self.enemy.target != "") // DETECTOR laser
1132 if(trace_ent.iscreature)
1134 self.pusher = hitent;
1141 activator = self.pusher;
1154 activator = self.pusher;
1164 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1166 if(hitent.takedamage)
1167 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1171 float laser_SendEntity(entity to, float fl)
1173 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1174 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1175 if(self.spawnflags & 2)
1179 if(self.scale != 1 || self.modelscale != 1)
1181 if(self.spawnflags & 4)
1183 WriteByte(MSG_ENTITY, fl);
1186 WriteCoord(MSG_ENTITY, self.origin_x);
1187 WriteCoord(MSG_ENTITY, self.origin_y);
1188 WriteCoord(MSG_ENTITY, self.origin_z);
1192 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1193 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1194 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1196 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1199 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1200 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1202 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1203 WriteShort(MSG_ENTITY, self.cnt + 1);
1209 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1210 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1211 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1215 WriteAngle(MSG_ENTITY, self.mangle_x);
1216 WriteAngle(MSG_ENTITY, self.mangle_y);
1220 WriteByte(MSG_ENTITY, self.state);
1224 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1225 Any object touching the beam will be hurt
1228 spawnfunc_target_position where the laser ends
1230 name of beam end effect to use
1232 color of the beam (default: red)
1234 damage per second (-1 for a laser that kills immediately)
1238 self.state = !self.state;
1239 self.SendFlags |= 4;
1245 if(self.spawnflags & 1)
1251 void spawnfunc_misc_laser()
1255 if(self.mdl == "none")
1259 self.cnt = particleeffectnum(self.mdl);
1262 self.cnt = particleeffectnum("laser_deadly");
1268 self.cnt = particleeffectnum("laser_deadly");
1275 if(self.colormod == '0 0 0')
1277 self.colormod = '1 0 0';
1279 self.message = "saw the light";
1281 self.message2 = "was pushed into a laser by";
1284 if(!self.modelscale)
1285 self.modelscale = 1;
1286 else if(self.modelscale < 0)
1287 self.modelscale = 0;
1288 self.think = misc_laser_think;
1289 self.nextthink = time;
1290 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1292 self.mangle = self.angles;
1294 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1298 self.reset = laser_reset;
1300 self.use = laser_use;
1306 // tZorks trigger impulse / gravity
1310 .float lastpushtime;
1312 // targeted (directional) mode
1313 void trigger_impulse_touch1()
1316 float pushdeltatime;
1319 if (self.active != ACTIVE_ACTIVE)
1322 // FIXME: Better checking for what to push and not.
1323 if not(other.iscreature)
1324 if (other.classname != "corpse")
1325 if (other.classname != "body")
1326 if (other.classname != "gib")
1327 if (other.classname != "missile")
1328 if (other.classname != "rocket")
1329 if (other.classname != "casing")
1330 if (other.classname != "grenade")
1331 if (other.classname != "plasma")
1332 if (other.classname != "plasma_prim")
1333 if (other.classname != "plasma_chain")
1334 if (other.classname != "droppedweapon")
1335 if (other.classname != "nexball_basketball")
1336 if (other.classname != "nexball_football")
1339 if (other.deadflag && other.iscreature)
1344 targ = find(world, targetname, self.target);
1347 objerror("trigger_force without a (valid) .target!\n");
1352 if(self.falloff == 1)
1353 str = (str / self.radius) * self.strength;
1354 else if(self.falloff == 2)
1355 str = (1 - (str / self.radius)) * self.strength;
1357 str = self.strength;
1359 pushdeltatime = time - other.lastpushtime;
1360 if (pushdeltatime > 0.15) pushdeltatime = 0;
1361 other.lastpushtime = time;
1362 if(!pushdeltatime) return;
1364 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1365 other.flags &~= FL_ONGROUND;
1366 UpdateCSQCProjectile(other);
1369 // Directionless (accelerator/decelerator) mode
1370 void trigger_impulse_touch2()
1372 float pushdeltatime;
1374 if (self.active != ACTIVE_ACTIVE)
1377 // FIXME: Better checking for what to push and not.
1378 if not(other.iscreature)
1379 if (other.classname != "corpse")
1380 if (other.classname != "body")
1381 if (other.classname != "gib")
1382 if (other.classname != "missile")
1383 if (other.classname != "rocket")
1384 if (other.classname != "casing")
1385 if (other.classname != "grenade")
1386 if (other.classname != "plasma")
1387 if (other.classname != "plasma_prim")
1388 if (other.classname != "plasma_chain")
1389 if (other.classname != "droppedweapon")
1390 if (other.classname != "nexball_basketball")
1391 if (other.classname != "nexball_football")
1394 if (other.deadflag && other.iscreature)
1399 pushdeltatime = time - other.lastpushtime;
1400 if (pushdeltatime > 0.15) pushdeltatime = 0;
1401 other.lastpushtime = time;
1402 if(!pushdeltatime) return;
1404 // div0: ticrate independent, 1 = identity (not 20)
1405 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1406 UpdateCSQCProjectile(other);
1409 // Spherical (gravity/repulsor) mode
1410 void trigger_impulse_touch3()
1412 float pushdeltatime;
1415 if (self.active != ACTIVE_ACTIVE)
1418 // FIXME: Better checking for what to push and not.
1419 if not(other.iscreature)
1420 if (other.classname != "corpse")
1421 if (other.classname != "body")
1422 if (other.classname != "gib")
1423 if (other.classname != "missile")
1424 if (other.classname != "rocket")
1425 if (other.classname != "casing")
1426 if (other.classname != "grenade")
1427 if (other.classname != "plasma")
1428 if (other.classname != "plasma_prim")
1429 if (other.classname != "plasma_chain")
1430 if (other.classname != "droppedweapon")
1431 if (other.classname != "nexball_basketball")
1432 if (other.classname != "nexball_football")
1435 if (other.deadflag && other.iscreature)
1440 pushdeltatime = time - other.lastpushtime;
1441 if (pushdeltatime > 0.15) pushdeltatime = 0;
1442 other.lastpushtime = time;
1443 if(!pushdeltatime) return;
1445 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1447 str = min(self.radius, vlen(self.origin - other.origin));
1449 if(self.falloff == 1)
1450 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1451 else if(self.falloff == 2)
1452 str = (str / self.radius) * self.strength; // 0 in the inside
1454 str = self.strength;
1456 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1457 UpdateCSQCProjectile(other);
1460 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1461 -------- KEYS --------
1462 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1463 If not, this trigger acts like a damper/accelerator field.
1465 strength : This is how mutch force to add in the direction of .target each second
1466 when .target is set. If not, this is hoe mutch to slow down/accelerate
1467 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1469 radius : If set, act as a spherical device rather then a liniar one.
1471 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1473 -------- NOTES --------
1474 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1475 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1478 void spawnfunc_trigger_impulse()
1480 self.active = ACTIVE_ACTIVE;
1485 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1486 setorigin(self, self.origin);
1487 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1488 self.touch = trigger_impulse_touch3;
1494 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1495 self.touch = trigger_impulse_touch1;
1499 if(!self.strength) self.strength = 0.9;
1500 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1501 self.touch = trigger_impulse_touch2;
1506 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1507 "Flip-flop" trigger gate... lets only every second trigger event through
1511 self.state = !self.state;
1516 void spawnfunc_trigger_flipflop()
1518 if(self.spawnflags & 1)
1520 self.use = flipflop_use;
1521 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1524 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1525 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1529 self.nextthink = time + self.wait;
1530 self.enemy = activator;
1536 void monoflop_fixed_use()
1540 self.nextthink = time + self.wait;
1542 self.enemy = activator;
1546 void monoflop_think()
1549 activator = self.enemy;
1553 void monoflop_reset()
1559 void spawnfunc_trigger_monoflop()
1563 if(self.spawnflags & 1)
1564 self.use = monoflop_fixed_use;
1566 self.use = monoflop_use;
1567 self.think = monoflop_think;
1569 self.reset = monoflop_reset;
1572 void multivibrator_send()
1577 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1579 newstate = (time < cyclestart + self.wait);
1582 if(self.state != newstate)
1584 self.state = newstate;
1587 self.nextthink = cyclestart + self.wait + 0.01;
1589 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1592 void multivibrator_toggle()
1594 if(self.nextthink == 0)
1596 multivibrator_send();
1609 void multivibrator_reset()
1611 if(!(self.spawnflags & 1))
1612 self.nextthink = 0; // wait for a trigger event
1614 self.nextthink = max(1, time);
1617 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1618 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1619 -------- KEYS --------
1620 target: trigger all entities with this targetname when it goes off
1621 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1622 phase: offset of the timing
1623 wait: "on" cycle time (default: 1)
1624 respawntime: "off" cycle time (default: same as wait)
1625 -------- SPAWNFLAGS --------
1626 START_ON: assume it is already turned on (when targeted)
1628 void spawnfunc_trigger_multivibrator()
1632 if(!self.respawntime)
1633 self.respawntime = self.wait;
1636 self.use = multivibrator_toggle;
1637 self.think = multivibrator_send;
1638 self.nextthink = time;
1641 multivibrator_reset();
1650 if(self.killtarget != "")
1651 src = find(world, targetname, self.killtarget);
1652 if(self.target != "")
1653 dst = find(world, targetname, self.target);
1657 objerror("follow: could not find target/killtarget");
1663 // already done :P entity must stay
1667 else if(!src || !dst)
1669 objerror("follow: could not find target/killtarget");
1672 else if(self.spawnflags & 1)
1675 if(self.spawnflags & 2)
1677 setattachment(dst, src, self.message);
1681 attach_sameorigin(dst, src, self.message);
1688 if(self.spawnflags & 2)
1690 dst.movetype = MOVETYPE_FOLLOW;
1692 // dst.punchangle = '0 0 0'; // keep unchanged
1693 dst.view_ofs = dst.origin;
1694 dst.v_angle = dst.angles;
1698 follow_sameorigin(dst, src);
1705 void spawnfunc_misc_follow()
1707 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1712 void gamestart_use() {
1718 void spawnfunc_trigger_gamestart() {
1719 self.use = gamestart_use;
1720 self.reset2 = spawnfunc_trigger_gamestart;
1724 self.think = self.use;
1725 self.nextthink = game_starttime + self.wait;
1728 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1734 .entity voicescript; // attached voice script
1735 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1736 .float voicescript_nextthink; // time to play next voice
1737 .float voicescript_voiceend; // time when this voice ends
1739 void target_voicescript_clear(entity pl)
1741 pl.voicescript = world;
1744 void target_voicescript_use()
1746 if(activator.voicescript != self)
1748 activator.voicescript = self;
1749 activator.voicescript_index = 0;
1750 activator.voicescript_nextthink = time + self.delay;
1754 void target_voicescript_next(entity pl)
1759 vs = pl.voicescript;
1762 if(vs.message == "")
1764 if(pl.classname != "player")
1769 if(time >= pl.voicescript_voiceend)
1771 if(time >= pl.voicescript_nextthink)
1773 // get the next voice...
1774 n = tokenize_console(vs.message);
1776 if(pl.voicescript_index < vs.cnt)
1777 i = pl.voicescript_index * 2;
1778 else if(n > vs.cnt * 2)
1779 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1785 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1786 dt = stof(argv(i + 1));
1789 pl.voicescript_voiceend = time + dt;
1790 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1794 pl.voicescript_voiceend = time - dt;
1795 pl.voicescript_nextthink = pl.voicescript_voiceend;
1798 pl.voicescript_index += 1;
1802 pl.voicescript = world; // stop trying then
1808 void spawnfunc_target_voicescript()
1810 // netname: directory of the sound files
1811 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1812 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1813 // Here, a - in front of the duration means that no delay is to be
1814 // added after this message
1815 // wait: average time between messages
1816 // delay: initial delay before the first message
1819 self.use = target_voicescript_use;
1821 n = tokenize_console(self.message);
1823 for(i = 0; i+1 < n; i += 2)
1830 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1836 void trigger_relay_teamcheck_use()
1840 if(self.spawnflags & 2)
1842 if(activator.team != self.team)
1847 if(activator.team == self.team)
1853 if(self.spawnflags & 1)
1858 void trigger_relay_teamcheck_reset()
1860 self.team = self.team_saved;
1863 void spawnfunc_trigger_relay_teamcheck()
1865 self.team_saved = self.team;
1866 self.use = trigger_relay_teamcheck_use;
1867 self.reset = trigger_relay_teamcheck_reset;
1872 void trigger_disablerelay_use()
1879 for(e = world; (e = find(e, targetname, self.target)); )
1881 if(e.use == SUB_UseTargets)
1883 e.use = SUB_DontUseTargets;
1886 else if(e.use == SUB_DontUseTargets)
1888 e.use = SUB_UseTargets;
1894 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1897 void spawnfunc_trigger_disablerelay()
1899 self.use = trigger_disablerelay_use;
1902 float magicear_matched;
1903 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1905 float domatch, dotrigger, matchstart, l;
1909 magicear_matched = FALSE;
1911 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1912 domatch = ((ear.spawnflags & 32) || dotrigger);
1918 if(ear.spawnflags & 4)
1924 if(ear.spawnflags & 1)
1927 if(ear.spawnflags & 2)
1930 if(ear.spawnflags & 8)
1935 l = strlen(ear.message);
1937 if(self.spawnflags & 128)
1940 msg = strdecolorize(msgin);
1942 if(substring(ear.message, 0, 1) == "*")
1944 if(substring(ear.message, -1, 1) == "*")
1947 // as we need multi-replacement here...
1948 s = substring(ear.message, 1, -2);
1950 if(strstrofs(msg, s, 0) >= 0)
1951 matchstart = -2; // we use strreplace on s
1956 s = substring(ear.message, 1, -1);
1958 if(substring(msg, -l, l) == s)
1959 matchstart = strlen(msg) - l;
1964 if(substring(ear.message, -1, 1) == "*")
1967 s = substring(ear.message, 0, -2);
1969 if(substring(msg, 0, l) == s)
1976 if(msg == ear.message)
1981 if(matchstart == -1) // no match
1984 magicear_matched = TRUE;
1988 oldself = activator = self;
1994 if(ear.spawnflags & 16)
1998 else if(ear.netname != "")
2001 return strreplace(s, ear.netname, msg);
2004 substring(msg, 0, matchstart),
2006 substring(msg, matchstart + l, -1)
2014 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2018 for(ear = magicears; ear; ear = ear.enemy)
2020 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2021 if not(ear.spawnflags & 64)
2022 if(magicear_matched)
2029 void spawnfunc_trigger_magicear()
2031 self.enemy = magicears;
2034 // actually handled in "say" processing
2037 // 2 = ignore teamsay
2039 // 8 = ignore tell to unknown player
2040 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2041 // 32 = perform the replacement even if outside the radius or dead
2042 // 64 = continue replacing/triggering even if this one matched
2052 // if set, replacement for the matched text
2054 // "hearing distance"
2059 void relay_activators_use()
2065 for(trg = world; (trg = find(trg, targetname, os.target)); )
2069 trg.setactive(os.cnt);
2072 //bprint("Not using setactive\n");
2073 if(os.cnt == ACTIVE_TOGGLE)
2074 if(trg.active == ACTIVE_ACTIVE)
2075 trg.active = ACTIVE_NOT;
2077 trg.active = ACTIVE_ACTIVE;
2079 trg.active = os.cnt;
2085 void spawnfunc_relay_activate()
2087 self.cnt = ACTIVE_ACTIVE;
2088 self.use = relay_activators_use;
2091 void spawnfunc_relay_deactivate()
2093 self.cnt = ACTIVE_NOT;
2094 self.use = relay_activators_use;
2097 void spawnfunc_relay_activatetoggle()
2099 self.cnt = ACTIVE_TOGGLE;
2100 self.use = relay_activators_use;
2103 .string chmap, gametype;
2104 void spawnfunc_target_changelevel_use()
2106 if(self.gametype != "")
2107 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2109 if (self.chmap == "")
2110 localcmd("endmatch\n");
2112 localcmd(strcat("changelevel ", self.chmap, "\n"));
2115 void spawnfunc_target_changelevel()
2117 self.use = spawnfunc_target_changelevel_use;