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 + cvar("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() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
661 void spawnfunc_target_speaker()
664 precache_sound (self.noise);
668 self.atten = ATTN_NORM;
669 else if(self.atten < 0)
673 self.use = target_speaker_use;
678 self.atten = ATTN_STATIC;
679 else if(self.atten < 0)
683 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
688 void spawnfunc_func_stardust() {
689 self.effects = EF_STARDUST;
693 .float bgmscriptattack;
694 .float bgmscriptdecay;
695 .float bgmscriptsustain;
696 .float bgmscriptrelease;
697 float pointparticles_SendEntity(entity to, float fl)
699 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
701 // optional features to save space
703 if(self.spawnflags & 2)
704 fl |= 0x10; // absolute count on toggle-on
705 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
706 fl |= 0x20; // 4 bytes - saves CPU
707 if(self.waterlevel || self.count != 1)
708 fl |= 0x40; // 4 bytes - obscure features almost never used
709 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
710 fl |= 0x80; // 14 bytes - saves lots of space
712 WriteByte(MSG_ENTITY, fl);
716 WriteCoord(MSG_ENTITY, self.impulse);
718 WriteCoord(MSG_ENTITY, 0); // off
722 WriteCoord(MSG_ENTITY, self.origin_x);
723 WriteCoord(MSG_ENTITY, self.origin_y);
724 WriteCoord(MSG_ENTITY, self.origin_z);
728 if(self.model != "null")
730 WriteShort(MSG_ENTITY, self.modelindex);
733 WriteCoord(MSG_ENTITY, self.mins_x);
734 WriteCoord(MSG_ENTITY, self.mins_y);
735 WriteCoord(MSG_ENTITY, self.mins_z);
736 WriteCoord(MSG_ENTITY, self.maxs_x);
737 WriteCoord(MSG_ENTITY, self.maxs_y);
738 WriteCoord(MSG_ENTITY, self.maxs_z);
743 WriteShort(MSG_ENTITY, 0);
746 WriteCoord(MSG_ENTITY, self.maxs_x);
747 WriteCoord(MSG_ENTITY, self.maxs_y);
748 WriteCoord(MSG_ENTITY, self.maxs_z);
751 WriteShort(MSG_ENTITY, self.cnt);
754 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
755 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
759 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
760 WriteByte(MSG_ENTITY, self.count * 16.0);
762 WriteString(MSG_ENTITY, self.noise);
765 WriteByte(MSG_ENTITY, floor(self.atten * 64));
766 WriteByte(MSG_ENTITY, floor(self.volume * 255));
768 WriteString(MSG_ENTITY, self.bgmscript);
769 if(self.bgmscript != "")
771 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
772 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
773 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
774 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
780 void pointparticles_use()
782 self.state = !self.state;
786 void pointparticles_think()
788 if(self.origin != self.oldorigin)
791 self.oldorigin = self.origin;
793 self.nextthink = time;
796 void pointparticles_reset()
798 if(self.spawnflags & 1)
804 void spawnfunc_func_pointparticles()
807 setmodel(self, self.model);
809 precache_sound (self.noise);
811 if(!self.bgmscriptsustain)
812 self.bgmscriptsustain = 1;
813 else if(self.bgmscriptsustain < 0)
814 self.bgmscriptsustain = 0;
817 self.atten = ATTN_NORM;
818 else if(self.atten < 0)
829 setorigin(self, self.origin + self.mins);
830 setsize(self, '0 0 0', self.maxs - self.mins);
833 self.cnt = particleeffectnum(self.mdl);
835 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
839 self.use = pointparticles_use;
840 self.reset = pointparticles_reset;
845 self.think = pointparticles_think;
846 self.nextthink = time;
849 void spawnfunc_func_sparks()
851 // self.cnt is the amount of sparks that one burst will spawn
853 self.cnt = 25.0; // nice default value
856 // self.wait is the probability that a sparkthink will spawn a spark shower
857 // range: 0 - 1, but 0 makes little sense, so...
858 if(self.wait < 0.05) {
859 self.wait = 0.25; // nice default value
862 self.count = self.cnt;
865 self.velocity = '0 0 -1';
866 self.mdl = "TE_SPARK";
867 self.impulse = 10 * self.wait; // by default 2.5/sec
869 self.cnt = 0; // use mdl
871 spawnfunc_func_pointparticles();
874 float rainsnow_SendEntity(entity to, float sf)
876 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
877 WriteByte(MSG_ENTITY, self.state);
878 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
879 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
880 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
881 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
882 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
883 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
884 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
885 WriteShort(MSG_ENTITY, self.count);
886 WriteByte(MSG_ENTITY, self.cnt);
890 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
891 This is an invisible area like a trigger, which rain falls inside of.
895 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
897 sets color of rain (default 12 - white)
899 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
901 void spawnfunc_func_rain()
903 self.dest = self.velocity;
904 self.velocity = '0 0 0';
906 self.dest = '0 0 -700';
907 self.angles = '0 0 0';
908 self.movetype = MOVETYPE_NONE;
909 self.solid = SOLID_NOT;
910 SetBrushEntityModel();
915 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
918 if(self.count > 65535)
921 self.state = 1; // 1 is rain, 0 is snow
924 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
928 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
929 This is an invisible area like a trigger, which snow falls inside of.
933 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
935 sets color of rain (default 12 - white)
937 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
939 void spawnfunc_func_snow()
941 self.dest = self.velocity;
942 self.velocity = '0 0 0';
944 self.dest = '0 0 -300';
945 self.angles = '0 0 0';
946 self.movetype = MOVETYPE_NONE;
947 self.solid = SOLID_NOT;
948 SetBrushEntityModel();
953 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
956 if(self.count > 65535)
959 self.state = 0; // 1 is rain, 0 is snow
962 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
966 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
969 void misc_laser_aim()
974 if(self.spawnflags & 2)
976 if(self.enemy.origin != self.mangle)
978 self.mangle = self.enemy.origin;
984 a = vectoangles(self.enemy.origin - self.origin);
995 if(self.angles != self.mangle)
997 self.mangle = self.angles;
1001 if(self.origin != self.oldorigin)
1003 self.SendFlags |= 1;
1004 self.oldorigin = self.origin;
1008 void misc_laser_init()
1010 if(self.target != "")
1011 self.enemy = find(world, targetname, self.target);
1015 void misc_laser_think()
1022 self.nextthink = time;
1031 o = self.enemy.origin;
1032 if not(self.spawnflags & 2)
1033 o = self.origin + normalize(o - self.origin) * 32768;
1037 makevectors(self.mangle);
1038 o = self.origin + v_forward * 32768;
1041 if(self.dmg || self.enemy.target != "")
1043 traceline(self.origin, o, MOVE_NORMAL, self);
1046 hitloc = trace_endpos;
1048 if(self.enemy.target != "") // DETECTOR laser
1050 if(trace_ent.iscreature)
1052 self.pusher = hitent;
1059 activator = self.pusher;
1072 activator = self.pusher;
1082 if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1084 if(hitent.takedamage)
1085 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1089 float laser_SendEntity(entity to, float fl)
1091 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1092 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1093 if(self.spawnflags & 2)
1097 if(self.scale != 1 || self.modelscale != 1)
1099 if(self.spawnflags & 4)
1101 WriteByte(MSG_ENTITY, fl);
1104 WriteCoord(MSG_ENTITY, self.origin_x);
1105 WriteCoord(MSG_ENTITY, self.origin_y);
1106 WriteCoord(MSG_ENTITY, self.origin_z);
1110 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1111 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1112 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1114 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1117 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1118 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1120 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1121 WriteShort(MSG_ENTITY, self.cnt + 1);
1127 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1128 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1129 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1133 WriteAngle(MSG_ENTITY, self.mangle_x);
1134 WriteAngle(MSG_ENTITY, self.mangle_y);
1138 WriteByte(MSG_ENTITY, self.state);
1142 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1143 Any object touching the beam will be hurt
1146 spawnfunc_target_position where the laser ends
1148 name of beam end effect to use
1150 color of the beam (default: red)
1152 damage per second (-1 for a laser that kills immediately)
1156 self.state = !self.state;
1157 self.SendFlags |= 4;
1163 if(self.spawnflags & 1)
1169 void spawnfunc_misc_laser()
1173 if(self.mdl == "none")
1177 self.cnt = particleeffectnum(self.mdl);
1180 self.cnt = particleeffectnum("laser_deadly");
1186 self.cnt = particleeffectnum("laser_deadly");
1193 if(self.colormod == '0 0 0')
1195 self.colormod = '1 0 0';
1197 self.message = "saw the light";
1199 self.message2 = "was pushed into a laser by";
1202 if(!self.modelscale)
1203 self.modelscale = 1;
1204 else if(self.modelscale < 0)
1205 self.modelscale = 0;
1206 self.think = misc_laser_think;
1207 self.nextthink = time;
1208 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1210 self.mangle = self.angles;
1212 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1216 self.reset = laser_reset;
1218 self.use = laser_use;
1224 // tZorks trigger impulse / gravity
1228 .float lastpushtime;
1230 // targeted (directional) mode
1231 void trigger_impulse_touch1()
1234 float pushdeltatime;
1237 if (self.active != ACTIVE_ACTIVE)
1240 // FIXME: Better checking for what to push and not.
1241 if not(other.iscreature)
1242 if (other.classname != "corpse")
1243 if (other.classname != "body")
1244 if (other.classname != "gib")
1245 if (other.classname != "missile")
1246 if (other.classname != "rocket")
1247 if (other.classname != "casing")
1248 if (other.classname != "grenade")
1249 if (other.classname != "plasma")
1250 if (other.classname != "plasma_prim")
1251 if (other.classname != "plasma_chain")
1252 if (other.classname != "droppedweapon")
1253 if (other.classname != "nexball_basketball")
1254 if (other.classname != "nexball_football")
1257 if (other.deadflag && other.iscreature)
1262 targ = find(world, targetname, self.target);
1265 objerror("trigger_force without a (valid) .target!\n");
1270 if(self.falloff == 1)
1271 str = (str / self.radius) * self.strength;
1272 else if(self.falloff == 2)
1273 str = (1 - (str / self.radius)) * self.strength;
1275 str = self.strength;
1277 pushdeltatime = time - other.lastpushtime;
1278 if (pushdeltatime > 0.15) pushdeltatime = 0;
1279 other.lastpushtime = time;
1280 if(!pushdeltatime) return;
1282 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1283 other.flags &~= FL_ONGROUND;
1284 UpdateCSQCProjectile(other);
1287 // Directionless (accelerator/decelerator) mode
1288 void trigger_impulse_touch2()
1290 float pushdeltatime;
1292 if (self.active != ACTIVE_ACTIVE)
1295 // FIXME: Better checking for what to push and not.
1296 if not(other.iscreature)
1297 if (other.classname != "corpse")
1298 if (other.classname != "body")
1299 if (other.classname != "gib")
1300 if (other.classname != "missile")
1301 if (other.classname != "rocket")
1302 if (other.classname != "casing")
1303 if (other.classname != "grenade")
1304 if (other.classname != "plasma")
1305 if (other.classname != "plasma_prim")
1306 if (other.classname != "plasma_chain")
1307 if (other.classname != "droppedweapon")
1308 if (other.classname != "nexball_basketball")
1309 if (other.classname != "nexball_football")
1312 if (other.deadflag && other.iscreature)
1317 pushdeltatime = time - other.lastpushtime;
1318 if (pushdeltatime > 0.15) pushdeltatime = 0;
1319 other.lastpushtime = time;
1320 if(!pushdeltatime) return;
1322 // div0: ticrate independent, 1 = identity (not 20)
1323 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1324 UpdateCSQCProjectile(other);
1327 // Spherical (gravity/repulsor) mode
1328 void trigger_impulse_touch3()
1330 float pushdeltatime;
1333 if (self.active != ACTIVE_ACTIVE)
1336 // FIXME: Better checking for what to push and not.
1337 if not(other.iscreature)
1338 if (other.classname != "corpse")
1339 if (other.classname != "body")
1340 if (other.classname != "gib")
1341 if (other.classname != "missile")
1342 if (other.classname != "rocket")
1343 if (other.classname != "casing")
1344 if (other.classname != "grenade")
1345 if (other.classname != "plasma")
1346 if (other.classname != "plasma_prim")
1347 if (other.classname != "plasma_chain")
1348 if (other.classname != "droppedweapon")
1349 if (other.classname != "nexball_basketball")
1350 if (other.classname != "nexball_football")
1353 if (other.deadflag && other.iscreature)
1358 pushdeltatime = time - other.lastpushtime;
1359 if (pushdeltatime > 0.15) pushdeltatime = 0;
1360 other.lastpushtime = time;
1361 if(!pushdeltatime) return;
1363 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1365 str = min(self.radius, vlen(self.origin - other.origin));
1367 if(self.falloff == 1)
1368 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1369 else if(self.falloff == 2)
1370 str = (str / self.radius) * self.strength; // 0 in the inside
1372 str = self.strength;
1374 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1375 UpdateCSQCProjectile(other);
1378 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1379 -------- KEYS --------
1380 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1381 If not, this trigger acts like a damper/accelerator field.
1383 strength : This is how mutch force to add in the direction of .target each second
1384 when .target is set. If not, this is hoe mutch to slow down/accelerate
1385 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1387 radius : If set, act as a spherical device rather then a liniar one.
1389 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1391 -------- NOTES --------
1392 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1393 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1396 void spawnfunc_trigger_impulse()
1398 self.active = ACTIVE_ACTIVE;
1403 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1404 setorigin(self, self.origin);
1405 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1406 self.touch = trigger_impulse_touch3;
1412 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1413 self.touch = trigger_impulse_touch1;
1417 if(!self.strength) self.strength = 0.9;
1418 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1419 self.touch = trigger_impulse_touch2;
1424 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1425 "Flip-flop" trigger gate... lets only every second trigger event through
1429 self.state = !self.state;
1434 void spawnfunc_trigger_flipflop()
1436 if(self.spawnflags & 1)
1438 self.use = flipflop_use;
1439 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1442 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1443 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1447 self.nextthink = time + self.wait;
1448 self.enemy = activator;
1454 void monoflop_fixed_use()
1458 self.nextthink = time + self.wait;
1460 self.enemy = activator;
1464 void monoflop_think()
1467 activator = self.enemy;
1471 void monoflop_reset()
1477 void spawnfunc_trigger_monoflop()
1481 if(self.spawnflags & 1)
1482 self.use = monoflop_fixed_use;
1484 self.use = monoflop_use;
1485 self.think = monoflop_think;
1487 self.reset = monoflop_reset;
1490 void multivibrator_send()
1495 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1497 newstate = (time < cyclestart + self.wait);
1500 if(self.state != newstate)
1502 self.state = newstate;
1505 self.nextthink = cyclestart + self.wait + 0.01;
1507 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1510 void multivibrator_toggle()
1512 if(self.nextthink == 0)
1514 multivibrator_send();
1527 void multivibrator_reset()
1529 if(!(self.spawnflags & 1))
1530 self.nextthink = 0; // wait for a trigger event
1532 self.nextthink = max(1, time);
1535 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1536 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1537 -------- KEYS --------
1538 target: trigger all entities with this targetname when it goes off
1539 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1540 phase: offset of the timing
1541 wait: "on" cycle time (default: 1)
1542 respawntime: "off" cycle time (default: same as wait)
1543 -------- SPAWNFLAGS --------
1544 START_ON: assume it is already turned on (when targeted)
1546 void spawnfunc_trigger_multivibrator()
1550 if(!self.respawntime)
1551 self.respawntime = self.wait;
1554 self.use = multivibrator_toggle;
1555 self.think = multivibrator_send;
1556 self.nextthink = time;
1559 multivibrator_reset();
1568 if(self.killtarget != "")
1569 src = find(world, targetname, self.killtarget);
1570 if(self.target != "")
1571 dst = find(world, targetname, self.target);
1575 objerror("follow: could not find target/killtarget");
1581 // already done :P entity must stay
1585 else if(!src || !dst)
1587 objerror("follow: could not find target/killtarget");
1590 else if(self.spawnflags & 1)
1593 if(self.spawnflags & 2)
1595 setattachment(dst, src, self.message);
1599 attach_sameorigin(dst, src, self.message);
1606 if(self.spawnflags & 2)
1608 dst.movetype = MOVETYPE_FOLLOW;
1610 // dst.punchangle = '0 0 0'; // keep unchanged
1611 dst.view_ofs = dst.origin;
1612 dst.v_angle = dst.angles;
1616 follow_sameorigin(dst, src);
1623 void spawnfunc_misc_follow()
1625 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1630 void gamestart_use() {
1636 void spawnfunc_trigger_gamestart() {
1637 self.use = gamestart_use;
1638 self.reset2 = spawnfunc_trigger_gamestart;
1642 self.think = self.use;
1643 self.nextthink = game_starttime + self.wait;
1646 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1652 .entity voicescript; // attached voice script
1653 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1654 .float voicescript_nextthink; // time to play next voice
1655 .float voicescript_voiceend; // time when this voice ends
1657 void target_voicescript_clear(entity pl)
1659 pl.voicescript = world;
1662 void target_voicescript_use()
1664 if(activator.voicescript != self)
1666 activator.voicescript = self;
1667 activator.voicescript_index = 0;
1668 activator.voicescript_nextthink = time + self.delay;
1672 void target_voicescript_next(entity pl)
1677 vs = pl.voicescript;
1680 if(vs.message == "")
1682 if(pl.classname != "player")
1687 if(time >= pl.voicescript_voiceend)
1689 if(time >= pl.voicescript_nextthink)
1691 // get the next voice...
1692 n = tokenize_console(vs.message);
1694 if(pl.voicescript_index < vs.cnt)
1695 i = pl.voicescript_index * 2;
1696 else if(n > vs.cnt * 2)
1697 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1703 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1704 dt = stof(argv(i + 1));
1707 pl.voicescript_voiceend = time + dt;
1708 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1712 pl.voicescript_voiceend = time - dt;
1713 pl.voicescript_nextthink = pl.voicescript_voiceend;
1716 pl.voicescript_index += 1;
1720 pl.voicescript = world; // stop trying then
1726 void spawnfunc_target_voicescript()
1728 // netname: directory of the sound files
1729 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1730 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1731 // Here, a - in front of the duration means that no delay is to be
1732 // added after this message
1733 // wait: average time between messages
1734 // delay: initial delay before the first message
1737 self.use = target_voicescript_use;
1739 n = tokenize_console(self.message);
1741 for(i = 0; i+1 < n; i += 2)
1748 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1754 void trigger_relay_teamcheck_use()
1758 if(self.spawnflags & 2)
1760 if(activator.team != self.team)
1765 if(activator.team == self.team)
1771 if(self.spawnflags & 1)
1776 void trigger_relay_teamcheck_reset()
1778 self.team = self.team_saved;
1781 void spawnfunc_trigger_relay_teamcheck()
1783 self.team_saved = self.team;
1784 self.use = trigger_relay_teamcheck_use;
1785 self.reset = trigger_relay_teamcheck_reset;
1790 void trigger_disablerelay_use()
1797 for(e = world; (e = find(e, targetname, self.target)); )
1799 if(e.use == SUB_UseTargets)
1801 e.use = SUB_DontUseTargets;
1804 else if(e.use == SUB_DontUseTargets)
1806 e.use = SUB_UseTargets;
1812 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1815 void spawnfunc_trigger_disablerelay()
1817 self.use = trigger_disablerelay_use;
1820 float magicear_matched;
1821 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1823 float domatch, dotrigger, matchstart, l;
1827 magicear_matched = FALSE;
1829 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1830 domatch = ((ear.spawnflags & 32) || dotrigger);
1836 if(ear.spawnflags & 4)
1842 if(ear.spawnflags & 1)
1845 if(ear.spawnflags & 2)
1848 if(ear.spawnflags & 8)
1853 l = strlen(ear.message);
1855 if(self.spawnflags & 128)
1858 msg = strdecolorize(msgin);
1860 if(substring(ear.message, 0, 1) == "*")
1862 if(substring(ear.message, -1, 1) == "*")
1865 // as we need multi-replacement here...
1866 s = substring(ear.message, 1, -2);
1868 if(strstrofs(msg, s, 0) >= 0)
1869 matchstart = -2; // we use strreplace on s
1874 s = substring(ear.message, 1, -1);
1876 if(substring(msg, -l, l) == s)
1877 matchstart = strlen(msg) - l;
1882 if(substring(ear.message, -1, 1) == "*")
1885 s = substring(ear.message, 0, -2);
1887 if(substring(msg, 0, l) == s)
1894 if(msg == ear.message)
1899 if(matchstart == -1) // no match
1902 magicear_matched = TRUE;
1906 oldself = activator = self;
1912 if(ear.spawnflags & 16)
1916 else if(ear.netname != "")
1919 return strreplace(s, ear.netname, msg);
1922 substring(msg, 0, matchstart),
1924 substring(msg, matchstart + l, -1)
1932 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1936 for(ear = magicears; ear; ear = ear.enemy)
1938 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1939 if not(ear.spawnflags & 64)
1940 if(magicear_matched)
1947 void spawnfunc_trigger_magicear()
1949 self.enemy = magicears;
1952 // actually handled in "say" processing
1955 // 2 = ignore teamsay
1957 // 8 = ignore tell to unknown player
1958 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1959 // 32 = perform the replacement even if outside the radius or dead
1960 // 64 = continue replacing/triggering even if this one matched
1970 // if set, replacement for the matched text
1972 // "hearing distance"
1977 void relay_activators_use()
1983 for(trg = world; (trg = find(trg, targetname, os.target)); )
1987 trg.setactive(os.cnt);
1990 //bprint("Not using setactive\n");
1991 if(os.cnt == ACTIVE_TOGGLE)
1992 if(trg.active == ACTIVE_ACTIVE)
1993 trg.active = ACTIVE_NOT;
1995 trg.active = ACTIVE_ACTIVE;
1997 trg.active = os.cnt;
2003 void spawnfunc_relay_activate()
2005 self.cnt = ACTIVE_ACTIVE;
2006 self.use = relay_activators_use;
2009 void spawnfunc_relay_deactivate()
2011 self.cnt = ACTIVE_NOT;
2012 self.use = relay_activators_use;
2015 void spawnfunc_relay_activatetoggle()
2017 self.cnt = ACTIVE_TOGGLE;
2018 self.use = relay_activators_use;
2021 .string chmap, gametype;
2022 void spawnfunc_target_changelevel_use()
2024 if(self.gametype != "")
2025 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2027 if (self.chmap == "")
2028 localcmd("endmatch\n");
2030 localcmd(strcat("changelevel ", self.chmap, "\n"));
2033 void spawnfunc_target_changelevel()
2035 self.use = spawnfunc_target_changelevel_use;