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 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
417 if (other.iscreature)
419 if (other.takedamage)
420 if (other.triggerhurttime < time)
423 other.triggerhurttime = time + 1;
424 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
431 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
434 other.pain_finished = min(other.pain_finished, time + 2);
436 else if (other.classname == "rune") // reset runes
439 other.nextthink = min(other.nextthink, time + 1);
447 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
448 Any object touching this will be hurt
449 set dmg to damage amount
452 .entity trigger_hurt_next;
453 entity trigger_hurt_last;
454 entity trigger_hurt_first;
455 void spawnfunc_trigger_hurt()
458 self.touch = trigger_hurt_touch;
462 self.message = "was in the wrong place";
464 self.message2 = "was thrown into a world of hurt by";
466 if(!trigger_hurt_first)
467 trigger_hurt_first = self;
468 if(trigger_hurt_last)
469 trigger_hurt_last.trigger_hurt_next = self;
470 trigger_hurt_last = self;
473 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
477 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
478 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
484 //////////////////////////////////////////////////////////////
488 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
490 //////////////////////////////////////////////////////////////
492 .float triggerhealtime;
493 void trigger_heal_touch()
495 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
496 if (other.iscreature)
498 if (other.takedamage)
499 if (other.triggerhealtime < time)
502 other.triggerhealtime = time + 1;
504 if (other.health < self.max_health)
506 other.health = min(other.health + self.health, self.max_health);
507 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
508 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
514 void spawnfunc_trigger_heal()
517 self.touch = trigger_heal_touch;
520 if (!self.max_health)
521 self.max_health = 200; //Max health topoff for field
523 self.noise = "misc/mediumhealth.wav";
524 precache_sound(self.noise);
528 //////////////////////////////////////////////////////////////
534 //////////////////////////////////////////////////////////////
536 .entity trigger_gravity_check;
537 void trigger_gravity_remove(entity own)
539 if(own.trigger_gravity_check.owner == own)
541 UpdateCSQCProjectile(own);
542 own.gravity = own.trigger_gravity_check.gravity;
543 remove(own.trigger_gravity_check);
546 backtrace("Removing a trigger_gravity_check with no valid owner");
547 own.trigger_gravity_check = world;
549 void trigger_gravity_check_think()
551 // This spawns when a player enters the gravity zone and checks if he left.
552 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
553 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
556 if(self.owner.trigger_gravity_check == self)
557 trigger_gravity_remove(self.owner);
565 self.nextthink = time;
569 void trigger_gravity_use()
571 self.state = !self.state;
574 void trigger_gravity_touch()
578 if(self.state != TRUE)
585 if not(self.spawnflags & 1)
587 if(other.trigger_gravity_check)
589 if(self == other.trigger_gravity_check.enemy)
592 other.trigger_gravity_check.count = 2; // gravity one more frame...
597 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
598 trigger_gravity_remove(other);
602 other.trigger_gravity_check = spawn();
603 other.trigger_gravity_check.enemy = self;
604 other.trigger_gravity_check.owner = other;
605 other.trigger_gravity_check.gravity = other.gravity;
606 other.trigger_gravity_check.think = trigger_gravity_check_think;
607 other.trigger_gravity_check.nextthink = time;
608 other.trigger_gravity_check.count = 2;
613 if (other.gravity != g)
617 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
618 UpdateCSQCProjectile(self.owner);
622 void spawnfunc_trigger_gravity()
624 if(self.gravity == 1)
628 self.touch = trigger_gravity_touch;
630 precache_sound(self.noise);
635 self.use = trigger_gravity_use;
636 if(self.spawnflags & 2)
641 //=============================================================================
643 // TODO add a way to do looped sounds with sound(); then complete this entity
644 .float volume, atten;
645 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
647 void spawnfunc_target_speaker()
650 precache_sound (self.noise);
654 self.atten = ATTN_NORM;
655 else if(self.atten < 0)
659 self.use = target_speaker_use;
664 self.atten = ATTN_STATIC;
665 else if(self.atten < 0)
669 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
674 void spawnfunc_func_stardust() {
675 self.effects = EF_STARDUST;
679 .float bgmscriptattack;
680 .float bgmscriptdecay;
681 .float bgmscriptsustain;
682 .float bgmscriptrelease;
683 float pointparticles_SendEntity(entity to, float fl)
685 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
687 // optional features to save space
689 if(self.spawnflags & 2)
690 fl |= 0x10; // absolute count on toggle-on
691 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
692 fl |= 0x20; // 4 bytes - saves CPU
693 if(self.waterlevel || self.count != 1)
694 fl |= 0x40; // 4 bytes - obscure features almost never used
695 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
696 fl |= 0x80; // 14 bytes - saves lots of space
698 WriteByte(MSG_ENTITY, fl);
702 WriteCoord(MSG_ENTITY, self.impulse);
704 WriteCoord(MSG_ENTITY, 0); // off
708 WriteCoord(MSG_ENTITY, self.origin_x);
709 WriteCoord(MSG_ENTITY, self.origin_y);
710 WriteCoord(MSG_ENTITY, self.origin_z);
714 if(self.model != "null")
716 WriteShort(MSG_ENTITY, self.modelindex);
719 WriteCoord(MSG_ENTITY, self.mins_x);
720 WriteCoord(MSG_ENTITY, self.mins_y);
721 WriteCoord(MSG_ENTITY, self.mins_z);
722 WriteCoord(MSG_ENTITY, self.maxs_x);
723 WriteCoord(MSG_ENTITY, self.maxs_y);
724 WriteCoord(MSG_ENTITY, self.maxs_z);
729 WriteShort(MSG_ENTITY, 0);
732 WriteCoord(MSG_ENTITY, self.maxs_x);
733 WriteCoord(MSG_ENTITY, self.maxs_y);
734 WriteCoord(MSG_ENTITY, self.maxs_z);
737 WriteShort(MSG_ENTITY, self.cnt);
740 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
741 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
745 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
746 WriteByte(MSG_ENTITY, self.count * 16.0);
748 WriteString(MSG_ENTITY, self.noise);
751 WriteByte(MSG_ENTITY, floor(self.atten * 64));
752 WriteByte(MSG_ENTITY, floor(self.volume * 255));
754 WriteString(MSG_ENTITY, self.bgmscript);
755 if(self.bgmscript != "")
757 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
758 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
759 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
760 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
766 void pointparticles_use()
768 self.state = !self.state;
772 void pointparticles_think()
774 if(self.origin != self.oldorigin)
777 self.oldorigin = self.origin;
779 self.nextthink = time;
782 void pointparticles_reset()
784 if(self.spawnflags & 1)
790 void spawnfunc_func_pointparticles()
793 setmodel(self, self.model);
795 precache_sound (self.noise);
797 if(!self.bgmscriptsustain)
798 self.bgmscriptsustain = 1;
799 else if(self.bgmscriptsustain < 0)
800 self.bgmscriptsustain = 0;
803 self.atten = ATTN_NORM;
804 else if(self.atten < 0)
815 setorigin(self, self.origin + self.mins);
816 setsize(self, '0 0 0', self.maxs - self.mins);
819 self.cnt = particleeffectnum(self.mdl);
821 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
825 self.use = pointparticles_use;
826 self.reset = pointparticles_reset;
831 self.think = pointparticles_think;
832 self.nextthink = time;
835 void spawnfunc_func_sparks()
837 // self.cnt is the amount of sparks that one burst will spawn
839 self.cnt = 25.0; // nice default value
842 // self.wait is the probability that a sparkthink will spawn a spark shower
843 // range: 0 - 1, but 0 makes little sense, so...
844 if(self.wait < 0.05) {
845 self.wait = 0.25; // nice default value
848 self.count = self.cnt;
851 self.velocity = '0 0 -1';
852 self.mdl = "TE_SPARK";
853 self.impulse = 10 * self.wait; // by default 2.5/sec
855 self.cnt = 0; // use mdl
857 spawnfunc_func_pointparticles();
860 float rainsnow_SendEntity(entity to, float sf)
862 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
863 WriteByte(MSG_ENTITY, self.state);
864 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
865 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
866 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
867 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
868 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
869 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
870 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
871 WriteShort(MSG_ENTITY, self.count);
872 WriteByte(MSG_ENTITY, self.cnt);
876 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
877 This is an invisible area like a trigger, which rain falls inside of.
881 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
883 sets color of rain (default 12 - white)
885 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
887 void spawnfunc_func_rain()
889 self.dest = self.velocity;
890 self.velocity = '0 0 0';
892 self.dest = '0 0 -700';
893 self.angles = '0 0 0';
894 self.movetype = MOVETYPE_NONE;
895 self.solid = SOLID_NOT;
896 SetBrushEntityModel();
901 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
904 if(self.count > 65535)
907 self.state = 1; // 1 is rain, 0 is snow
910 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
914 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
915 This is an invisible area like a trigger, which snow falls inside of.
919 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
921 sets color of rain (default 12 - white)
923 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
925 void spawnfunc_func_snow()
927 self.dest = self.velocity;
928 self.velocity = '0 0 0';
930 self.dest = '0 0 -300';
931 self.angles = '0 0 0';
932 self.movetype = MOVETYPE_NONE;
933 self.solid = SOLID_NOT;
934 SetBrushEntityModel();
939 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
942 if(self.count > 65535)
945 self.state = 0; // 1 is rain, 0 is snow
948 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
952 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
955 void misc_laser_aim()
960 if(self.spawnflags & 2)
962 if(self.enemy.origin != self.mangle)
964 self.mangle = self.enemy.origin;
970 a = vectoangles(self.enemy.origin - self.origin);
981 if(self.angles != self.mangle)
983 self.mangle = self.angles;
987 if(self.origin != self.oldorigin)
990 self.oldorigin = self.origin;
994 void misc_laser_init()
996 if(self.target != "")
997 self.enemy = find(world, targetname, self.target);
1001 void misc_laser_think()
1006 self.nextthink = time;
1015 o = self.enemy.origin;
1016 if not(self.spawnflags & 2)
1017 o = self.origin + normalize(o - self.origin) * 32768;
1021 makevectors(self.mangle);
1022 o = self.origin + v_forward * 32768;
1028 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1030 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1033 if(self.enemy.target != "") // DETECTOR laser
1035 traceline(self.origin, o, MOVE_NORMAL, self);
1036 if(trace_ent.iscreature)
1038 self.pusher = trace_ent;
1045 activator = self.pusher;
1058 activator = self.pusher;
1066 float laser_SendEntity(entity to, float fl)
1068 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1069 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1070 if(self.spawnflags & 2)
1074 if(self.scale != 1 || self.modelscale != 1)
1076 WriteByte(MSG_ENTITY, fl);
1079 WriteCoord(MSG_ENTITY, self.origin_x);
1080 WriteCoord(MSG_ENTITY, self.origin_y);
1081 WriteCoord(MSG_ENTITY, self.origin_z);
1085 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1086 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1087 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1089 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1092 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1093 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1095 WriteShort(MSG_ENTITY, self.cnt + 1);
1101 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1102 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1103 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1107 WriteAngle(MSG_ENTITY, self.mangle_x);
1108 WriteAngle(MSG_ENTITY, self.mangle_y);
1112 WriteByte(MSG_ENTITY, self.state);
1116 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1117 Any object touching the beam will be hurt
1120 spawnfunc_target_position where the laser ends
1122 name of beam end effect to use
1124 color of the beam (default: red)
1126 damage per second (-1 for a laser that kills immediately)
1130 self.state = !self.state;
1131 self.SendFlags |= 4;
1137 if(self.spawnflags & 1)
1143 void spawnfunc_misc_laser()
1147 if(self.mdl == "none")
1151 self.cnt = particleeffectnum(self.mdl);
1154 self.cnt = particleeffectnum("laser_deadly");
1160 self.cnt = particleeffectnum("laser_deadly");
1167 if(self.colormod == '0 0 0')
1169 self.colormod = '1 0 0';
1171 self.message = "saw the light";
1173 self.message2 = "was pushed into a laser by";
1176 if(!self.modelscale)
1177 self.modelscale = 1;
1178 self.think = misc_laser_think;
1179 self.nextthink = time;
1180 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1182 self.mangle = self.angles;
1184 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1188 self.reset = laser_reset;
1190 self.use = laser_use;
1196 // tZorks trigger impulse / gravity
1200 .float lastpushtime;
1202 // targeted (directional) mode
1203 void trigger_impulse_touch1()
1206 float pushdeltatime;
1209 // FIXME: Better checking for what to push and not.
1210 if not(other.iscreature)
1211 if (other.classname != "corpse")
1212 if (other.classname != "body")
1213 if (other.classname != "gib")
1214 if (other.classname != "missile")
1215 if (other.classname != "rocket")
1216 if (other.classname != "casing")
1217 if (other.classname != "grenade")
1218 if (other.classname != "plasma")
1219 if (other.classname != "plasma_prim")
1220 if (other.classname != "plasma_chain")
1221 if (other.classname != "droppedweapon")
1222 if (other.classname != "nexball_basketball")
1223 if (other.classname != "nexball_football")
1226 if (other.deadflag && other.iscreature)
1231 targ = find(world, targetname, self.target);
1234 objerror("trigger_force without a (valid) .target!\n");
1239 if(self.falloff == 1)
1240 str = (str / self.radius) * self.strength;
1241 else if(self.falloff == 2)
1242 str = (1 - (str / self.radius)) * self.strength;
1244 str = self.strength;
1246 pushdeltatime = time - other.lastpushtime;
1247 if (pushdeltatime > 0.15) pushdeltatime = 0;
1248 other.lastpushtime = time;
1249 if(!pushdeltatime) return;
1251 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1252 other.flags &~= FL_ONGROUND;
1255 // Directionless (accelerator/decelerator) mode
1256 void trigger_impulse_touch2()
1258 float pushdeltatime;
1260 // FIXME: Better checking for what to push and not.
1261 if not(other.iscreature)
1262 if (other.classname != "corpse")
1263 if (other.classname != "body")
1264 if (other.classname != "gib")
1265 if (other.classname != "missile")
1266 if (other.classname != "rocket")
1267 if (other.classname != "casing")
1268 if (other.classname != "grenade")
1269 if (other.classname != "plasma")
1270 if (other.classname != "plasma_prim")
1271 if (other.classname != "plasma_chain")
1272 if (other.classname != "droppedweapon")
1273 if (other.classname != "nexball_basketball")
1274 if (other.classname != "nexball_football")
1277 if (other.deadflag && other.iscreature)
1282 pushdeltatime = time - other.lastpushtime;
1283 if (pushdeltatime > 0.15) pushdeltatime = 0;
1284 other.lastpushtime = time;
1285 if(!pushdeltatime) return;
1287 // div0: ticrate independent, 1 = identity (not 20)
1288 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1291 // Spherical (gravity/repulsor) mode
1292 void trigger_impulse_touch3()
1294 float pushdeltatime;
1297 // FIXME: Better checking for what to push and not.
1298 if not(other.iscreature)
1299 if (other.classname != "corpse")
1300 if (other.classname != "body")
1301 if (other.classname != "gib")
1302 if (other.classname != "missile")
1303 if (other.classname != "rocket")
1304 if (other.classname != "casing")
1305 if (other.classname != "grenade")
1306 if (other.classname != "plasma")
1307 if (other.classname != "plasma_prim")
1308 if (other.classname != "plasma_chain")
1309 if (other.classname != "droppedweapon")
1310 if (other.classname != "nexball_basketball")
1311 if (other.classname != "nexball_football")
1314 if (other.deadflag && other.iscreature)
1319 pushdeltatime = time - other.lastpushtime;
1320 if (pushdeltatime > 0.15) pushdeltatime = 0;
1321 other.lastpushtime = time;
1322 if(!pushdeltatime) return;
1324 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1326 str = min(self.radius, vlen(self.origin - other.origin));
1328 if(self.falloff == 1)
1329 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1330 else if(self.falloff == 2)
1331 str = (str / self.radius) * self.strength; // 0 in the inside
1333 str = self.strength;
1335 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1338 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1339 -------- KEYS --------
1340 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1341 If not, this trigger acts like a damper/accelerator field.
1343 strength : This is how mutch force to add in the direction of .target each second
1344 when .target is set. If not, this is hoe mutch to slow down/accelerate
1345 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1347 radius : If set, act as a spherical device rather then a liniar one.
1349 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1351 -------- NOTES --------
1352 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1353 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1356 void spawnfunc_trigger_impulse()
1361 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1362 setorigin(self, self.origin);
1363 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1364 self.touch = trigger_impulse_touch3;
1370 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1371 self.touch = trigger_impulse_touch1;
1375 if(!self.strength) self.strength = 0.9;
1376 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1377 self.touch = trigger_impulse_touch2;
1382 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1383 "Flip-flop" trigger gate... lets only every second trigger event through
1387 self.state = !self.state;
1392 void spawnfunc_trigger_flipflop()
1394 if(self.spawnflags & 1)
1396 self.use = flipflop_use;
1397 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1400 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1401 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1405 self.nextthink = time + self.wait;
1406 self.enemy = activator;
1412 void monoflop_fixed_use()
1416 self.nextthink = time + self.wait;
1418 self.enemy = activator;
1422 void monoflop_think()
1425 activator = self.enemy;
1429 void monoflop_reset()
1435 void spawnfunc_trigger_monoflop()
1439 if(self.spawnflags & 1)
1440 self.use = monoflop_fixed_use;
1442 self.use = monoflop_use;
1443 self.think = monoflop_think;
1445 self.reset = monoflop_reset;
1448 void multivibrator_send()
1453 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1455 newstate = (time < cyclestart + self.wait);
1458 if(self.state != newstate)
1460 self.state = newstate;
1463 self.nextthink = cyclestart + self.wait + 0.01;
1465 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1468 void multivibrator_toggle()
1470 if(self.nextthink == 0)
1472 multivibrator_send();
1485 void multivibrator_reset()
1487 if(!(self.spawnflags & 1))
1488 self.nextthink = 0; // wait for a trigger event
1490 self.nextthink = max(1, time);
1493 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1494 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1495 -------- KEYS --------
1496 target: trigger all entities with this targetname when it goes off
1497 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1498 phase: offset of the timing
1499 wait: "on" cycle time (default: 1)
1500 respawntime: "off" cycle time (default: same as wait)
1501 -------- SPAWNFLAGS --------
1502 START_ON: assume it is already turned on (when targeted)
1504 void spawnfunc_trigger_multivibrator()
1508 if(!self.respawntime)
1509 self.respawntime = self.wait;
1512 self.use = multivibrator_toggle;
1513 self.think = multivibrator_send;
1514 self.nextthink = time;
1517 multivibrator_reset();
1526 if(self.killtarget != "")
1527 src = find(world, targetname, self.killtarget);
1528 if(self.target != "")
1529 dst = find(world, targetname, self.target);
1533 objerror("follow: could not find target/killtarget");
1539 // already done :P entity must stay
1543 else if(!src || !dst)
1545 objerror("follow: could not find target/killtarget");
1548 else if(self.spawnflags & 1)
1551 if(self.spawnflags & 2)
1553 setattachment(dst, src, self.message);
1557 attach_sameorigin(dst, src, self.message);
1564 if(self.spawnflags & 2)
1566 dst.movetype = MOVETYPE_FOLLOW;
1568 // dst.punchangle = '0 0 0'; // keep unchanged
1569 dst.view_ofs = dst.origin;
1570 dst.v_angle = dst.angles;
1574 follow_sameorigin(dst, src);
1581 void spawnfunc_misc_follow()
1583 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1588 void gamestart_use() {
1594 void spawnfunc_trigger_gamestart() {
1595 self.use = gamestart_use;
1596 self.reset2 = spawnfunc_trigger_gamestart;
1600 self.think = self.use;
1601 self.nextthink = game_starttime + self.wait;
1604 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1610 .entity voicescript; // attached voice script
1611 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1612 .float voicescript_nextthink; // time to play next voice
1613 .float voicescript_voiceend; // time when this voice ends
1615 void target_voicescript_clear(entity pl)
1617 pl.voicescript = world;
1620 void target_voicescript_use()
1622 if(activator.voicescript != self)
1624 activator.voicescript = self;
1625 activator.voicescript_index = 0;
1626 activator.voicescript_nextthink = time + self.delay;
1630 void target_voicescript_next(entity pl)
1635 vs = pl.voicescript;
1638 if(vs.message == "")
1640 if(pl.classname != "player")
1645 if(time >= pl.voicescript_voiceend)
1647 if(time >= pl.voicescript_nextthink)
1649 // get the next voice...
1650 n = tokenize_console(vs.message);
1652 if(pl.voicescript_index < vs.cnt)
1653 i = pl.voicescript_index * 2;
1654 else if(n > vs.cnt * 2)
1655 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1661 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1662 dt = stof(argv(i + 1));
1665 pl.voicescript_voiceend = time + dt;
1666 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1670 pl.voicescript_voiceend = time - dt;
1671 pl.voicescript_nextthink = pl.voicescript_voiceend;
1674 pl.voicescript_index += 1;
1678 pl.voicescript = world; // stop trying then
1684 void spawnfunc_target_voicescript()
1686 // netname: directory of the sound files
1687 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1688 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1689 // Here, a - in front of the duration means that no delay is to be
1690 // added after this message
1691 // wait: average time between messages
1692 // delay: initial delay before the first message
1695 self.use = target_voicescript_use;
1697 n = tokenize_console(self.message);
1699 for(i = 0; i+1 < n; i += 2)
1706 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1712 void trigger_relay_teamcheck_use()
1716 if(self.spawnflags & 2)
1718 if(activator.team != self.team)
1723 if(activator.team == self.team)
1729 if(self.spawnflags & 1)
1734 void trigger_relay_teamcheck_reset()
1736 self.team = self.team_saved;
1739 void spawnfunc_trigger_relay_teamcheck()
1741 self.team_saved = self.team;
1742 self.use = trigger_relay_teamcheck_use;
1743 self.reset = trigger_relay_teamcheck_reset;
1748 void trigger_disablerelay_use()
1755 for(e = world; (e = find(e, targetname, self.target)); )
1757 if(e.use == SUB_UseTargets)
1759 e.use = SUB_DontUseTargets;
1762 else if(e.use == SUB_DontUseTargets)
1764 e.use = SUB_UseTargets;
1770 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1773 void spawnfunc_trigger_disablerelay()
1775 self.use = trigger_disablerelay_use;
1778 float magicear_matched;
1779 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1781 float domatch, dotrigger, matchstart, l;
1785 magicear_matched = FALSE;
1787 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1788 domatch = ((ear.spawnflags & 32) || dotrigger);
1794 if(ear.spawnflags & 4)
1800 if(ear.spawnflags & 1)
1803 if(ear.spawnflags & 2)
1806 if(ear.spawnflags & 8)
1811 l = strlen(ear.message);
1813 if(self.spawnflags & 128)
1816 msg = strdecolorize(msgin);
1818 if(substring(ear.message, 0, 1) == "*")
1820 if(substring(ear.message, -1, 1) == "*")
1823 // as we need multi-replacement here...
1824 s = substring(ear.message, 1, -2);
1826 if(strstrofs(msg, s, 0) >= 0)
1827 matchstart = -2; // we use strreplace on s
1832 s = substring(ear.message, 1, -1);
1834 if(substring(msg, -l, l) == s)
1835 matchstart = strlen(msg) - l;
1840 if(substring(ear.message, -1, 1) == "*")
1843 s = substring(ear.message, 0, -2);
1845 if(substring(msg, 0, l) == s)
1852 if(msg == ear.message)
1857 if(matchstart == -1) // no match
1860 magicear_matched = TRUE;
1864 oldself = activator = self;
1870 if(ear.spawnflags & 16)
1874 else if(ear.netname != "")
1877 return strreplace(s, ear.netname, msg);
1880 substring(msg, 0, matchstart),
1882 substring(msg, matchstart + l, -1)
1890 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1894 for(ear = magicears; ear; ear = ear.enemy)
1896 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1897 if not(ear.spawnflags & 64)
1898 if(magicear_matched)
1905 void spawnfunc_trigger_magicear()
1907 self.enemy = magicears;
1910 // actually handled in "say" processing
1913 // 2 = ignore teamsay
1915 // 8 = ignore tell to unknown player
1916 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1917 // 32 = perform the replacement even if outside the radius or dead
1918 // 64 = continue replacing/triggering even if this one matched
1928 // if set, replacement for the matched text
1930 // "hearing distance"