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";
465 // self.message = "someone like %s always gets wrongplaced";
467 if(!trigger_hurt_first)
468 trigger_hurt_first = self;
469 if(trigger_hurt_last)
470 trigger_hurt_last.trigger_hurt_next = self;
471 trigger_hurt_last = self;
474 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
478 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
479 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
485 //////////////////////////////////////////////////////////////
489 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
491 //////////////////////////////////////////////////////////////
493 .float triggerhealtime;
494 void trigger_heal_touch()
496 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
497 if (other.iscreature)
499 if (other.takedamage)
500 if (other.triggerhealtime < time)
503 other.triggerhealtime = time + 1;
505 if (other.health < self.max_health)
507 other.health = min(other.health + self.health, self.max_health);
508 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
509 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
515 void spawnfunc_trigger_heal()
518 self.touch = trigger_heal_touch;
521 if (!self.max_health)
522 self.max_health = 200; //Max health topoff for field
524 self.noise = "misc/mediumhealth.wav";
525 precache_sound(self.noise);
529 //////////////////////////////////////////////////////////////
535 //////////////////////////////////////////////////////////////
537 .entity trigger_gravity_check;
538 void trigger_gravity_remove(entity own)
540 if(own.trigger_gravity_check.owner == own)
542 UpdateCSQCProjectile(own);
543 own.gravity = own.trigger_gravity_check.gravity;
544 remove(own.trigger_gravity_check);
547 backtrace("Removing a trigger_gravity_check with no valid owner");
548 own.trigger_gravity_check = world;
550 void trigger_gravity_check_think()
552 // This spawns when a player enters the gravity zone and checks if he left.
553 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
554 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
557 if(self.owner.trigger_gravity_check == self)
558 trigger_gravity_remove(self.owner);
566 self.nextthink = time;
570 void trigger_gravity_use()
572 self.state = !self.state;
575 void trigger_gravity_touch()
579 if(self.state != TRUE)
586 if not(self.spawnflags & 1)
588 if(other.trigger_gravity_check)
590 if(self == other.trigger_gravity_check.enemy)
593 other.trigger_gravity_check.count = 2; // gravity one more frame...
598 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
599 trigger_gravity_remove(other);
603 other.trigger_gravity_check = spawn();
604 other.trigger_gravity_check.enemy = self;
605 other.trigger_gravity_check.owner = other;
606 other.trigger_gravity_check.gravity = other.gravity;
607 other.trigger_gravity_check.think = trigger_gravity_check_think;
608 other.trigger_gravity_check.nextthink = time;
609 other.trigger_gravity_check.count = 2;
614 if (other.gravity != g)
618 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
619 UpdateCSQCProjectile(self.owner);
623 void spawnfunc_trigger_gravity()
625 if(self.gravity == 1)
629 self.touch = trigger_gravity_touch;
631 precache_sound(self.noise);
636 self.use = trigger_gravity_use;
637 if(self.spawnflags & 2)
642 //=============================================================================
644 // TODO add a way to do looped sounds with sound(); then complete this entity
645 .float volume, atten;
646 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
648 void spawnfunc_target_speaker()
651 precache_sound (self.noise);
655 self.atten = ATTN_NORM;
656 else if(self.atten < 0)
660 self.use = target_speaker_use;
665 self.atten = ATTN_STATIC;
666 else if(self.atten < 0)
670 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
675 void spawnfunc_func_stardust() {
676 self.effects = EF_STARDUST;
680 .float bgmscriptattack;
681 .float bgmscriptdecay;
682 .float bgmscriptsustain;
683 .float bgmscriptrelease;
684 float pointparticles_SendEntity(entity to, float fl)
686 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
688 // optional features to save space
690 if(self.spawnflags & 2)
691 fl |= 0x10; // absolute count on toggle-on
692 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
693 fl |= 0x20; // 4 bytes - saves CPU
694 if(self.waterlevel || self.count != 1)
695 fl |= 0x40; // 4 bytes - obscure features almost never used
696 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
697 fl |= 0x80; // 14 bytes - saves lots of space
699 WriteByte(MSG_ENTITY, fl);
703 WriteCoord(MSG_ENTITY, self.impulse);
705 WriteCoord(MSG_ENTITY, 0); // off
709 WriteCoord(MSG_ENTITY, self.origin_x);
710 WriteCoord(MSG_ENTITY, self.origin_y);
711 WriteCoord(MSG_ENTITY, self.origin_z);
715 if(self.model != "null")
717 WriteShort(MSG_ENTITY, self.modelindex);
720 WriteCoord(MSG_ENTITY, self.mins_x);
721 WriteCoord(MSG_ENTITY, self.mins_y);
722 WriteCoord(MSG_ENTITY, self.mins_z);
723 WriteCoord(MSG_ENTITY, self.maxs_x);
724 WriteCoord(MSG_ENTITY, self.maxs_y);
725 WriteCoord(MSG_ENTITY, self.maxs_z);
730 WriteShort(MSG_ENTITY, 0);
733 WriteCoord(MSG_ENTITY, self.maxs_x);
734 WriteCoord(MSG_ENTITY, self.maxs_y);
735 WriteCoord(MSG_ENTITY, self.maxs_z);
738 WriteShort(MSG_ENTITY, self.cnt);
741 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
742 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
746 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
747 WriteByte(MSG_ENTITY, self.count * 16.0);
749 WriteString(MSG_ENTITY, self.noise);
752 WriteByte(MSG_ENTITY, floor(self.atten * 64));
753 WriteByte(MSG_ENTITY, floor(self.volume * 255));
755 WriteString(MSG_ENTITY, self.bgmscript);
756 if(self.bgmscript != "")
758 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
759 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
760 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
761 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
767 void pointparticles_use()
769 self.state = !self.state;
773 void pointparticles_think()
775 if(self.origin != self.oldorigin)
778 self.oldorigin = self.origin;
780 self.nextthink = time;
783 void pointparticles_reset()
785 if(self.spawnflags & 1)
791 void spawnfunc_func_pointparticles()
794 setmodel(self, self.model);
796 precache_sound (self.noise);
798 if(!self.bgmscriptsustain)
799 self.bgmscriptsustain = 1;
800 else if(self.bgmscriptsustain < 0)
801 self.bgmscriptsustain = 0;
804 self.atten = ATTN_NORM;
805 else if(self.atten < 0)
816 setorigin(self, self.origin + self.mins);
817 setsize(self, '0 0 0', self.maxs - self.mins);
820 self.cnt = particleeffectnum(self.mdl);
822 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
826 self.use = pointparticles_use;
827 self.reset = pointparticles_reset;
832 self.think = pointparticles_think;
833 self.nextthink = time;
836 void spawnfunc_func_sparks()
838 // self.cnt is the amount of sparks that one burst will spawn
840 self.cnt = 25.0; // nice default value
843 // self.wait is the probability that a sparkthink will spawn a spark shower
844 // range: 0 - 1, but 0 makes little sense, so...
845 if(self.wait < 0.05) {
846 self.wait = 0.25; // nice default value
849 self.count = self.cnt;
852 self.velocity = '0 0 -1';
853 self.mdl = "TE_SPARK";
854 self.impulse = 10 * self.wait; // by default 2.5/sec
856 self.cnt = 0; // use mdl
858 spawnfunc_func_pointparticles();
861 float rainsnow_SendEntity(entity to, float sf)
863 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
864 WriteByte(MSG_ENTITY, self.state);
865 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
866 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
867 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
868 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
869 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
870 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
871 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
872 WriteShort(MSG_ENTITY, self.count);
873 WriteByte(MSG_ENTITY, self.cnt);
877 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
878 This is an invisible area like a trigger, which rain falls inside of.
882 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
884 sets color of rain (default 12 - white)
886 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
888 void spawnfunc_func_rain()
890 self.dest = self.velocity;
891 self.velocity = '0 0 0';
893 self.dest = '0 0 -700';
894 self.angles = '0 0 0';
895 self.movetype = MOVETYPE_NONE;
896 self.solid = SOLID_NOT;
897 SetBrushEntityModel();
902 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
905 if(self.count > 65535)
908 self.state = 1; // 1 is rain, 0 is snow
911 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
915 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
916 This is an invisible area like a trigger, which snow falls inside of.
920 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
922 sets color of rain (default 12 - white)
924 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
926 void spawnfunc_func_snow()
928 self.dest = self.velocity;
929 self.velocity = '0 0 0';
931 self.dest = '0 0 -300';
932 self.angles = '0 0 0';
933 self.movetype = MOVETYPE_NONE;
934 self.solid = SOLID_NOT;
935 SetBrushEntityModel();
940 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
943 if(self.count > 65535)
946 self.state = 0; // 1 is rain, 0 is snow
949 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
953 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
956 void misc_laser_aim()
961 if(self.spawnflags & 2)
963 if(self.enemy.origin != self.mangle)
965 self.mangle = self.enemy.origin;
971 a = vectoangles(self.enemy.origin - self.origin);
982 if(self.angles != self.mangle)
984 self.mangle = self.angles;
988 if(self.origin != self.oldorigin)
991 self.oldorigin = self.origin;
995 void misc_laser_init()
997 if(self.target != "")
998 self.enemy = find(world, targetname, self.target);
1002 void misc_laser_think()
1007 self.nextthink = time;
1016 o = self.enemy.origin;
1017 if not(self.spawnflags & 2)
1018 o = self.origin + normalize(o - self.origin) * 32768;
1022 makevectors(self.mangle);
1023 o = self.origin + v_forward * 32768;
1029 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1031 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1034 if(self.enemy.target != "") // DETECTOR laser
1036 traceline(self.origin, o, MOVE_NORMAL, self);
1037 if(trace_ent.iscreature)
1039 self.pusher = trace_ent;
1046 activator = self.pusher;
1059 activator = self.pusher;
1067 float laser_SendEntity(entity to, float fl)
1069 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1070 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1071 if(self.spawnflags & 2)
1075 if(self.scale != 1 || self.modelscale != 1)
1077 WriteByte(MSG_ENTITY, fl);
1080 WriteCoord(MSG_ENTITY, self.origin_x);
1081 WriteCoord(MSG_ENTITY, self.origin_y);
1082 WriteCoord(MSG_ENTITY, self.origin_z);
1086 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1087 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1088 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1090 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1093 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1094 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1096 WriteShort(MSG_ENTITY, self.cnt + 1);
1102 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1103 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1104 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1108 WriteAngle(MSG_ENTITY, self.mangle_x);
1109 WriteAngle(MSG_ENTITY, self.mangle_y);
1113 WriteByte(MSG_ENTITY, self.state);
1117 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1118 Any object touching the beam will be hurt
1121 spawnfunc_target_position where the laser ends
1123 name of beam end effect to use
1125 color of the beam (default: red)
1127 damage per second (-1 for a laser that kills immediately)
1131 self.state = !self.state;
1132 self.SendFlags |= 4;
1138 if(self.spawnflags & 1)
1144 void spawnfunc_misc_laser()
1148 if(self.mdl == "none")
1152 self.cnt = particleeffectnum(self.mdl);
1155 self.cnt = particleeffectnum("laser_deadly");
1161 self.cnt = particleeffectnum("laser_deadly");
1168 if(self.colormod == '0 0 0')
1170 self.colormod = '1 0 0';
1172 self.message = "saw the light";
1174 self.message2 = "was pushed into a laser by";
1177 if(!self.modelscale)
1178 self.modelscale = 1;
1179 self.think = misc_laser_think;
1180 self.nextthink = time;
1181 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1183 self.mangle = self.angles;
1185 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1189 self.reset = laser_reset;
1191 self.use = laser_use;
1197 // tZorks trigger impulse / gravity
1201 .float lastpushtime;
1203 // targeted (directional) mode
1204 void trigger_impulse_touch1()
1207 float pushdeltatime;
1210 // FIXME: Better checking for what to push and not.
1211 if not(other.iscreature)
1212 if (other.classname != "corpse")
1213 if (other.classname != "body")
1214 if (other.classname != "gib")
1215 if (other.classname != "missile")
1216 if (other.classname != "rocket")
1217 if (other.classname != "casing")
1218 if (other.classname != "grenade")
1219 if (other.classname != "plasma")
1220 if (other.classname != "plasma_prim")
1221 if (other.classname != "plasma_chain")
1222 if (other.classname != "droppedweapon")
1223 if (other.classname != "nexball_basketball")
1224 if (other.classname != "nexball_football")
1227 if (other.deadflag && other.iscreature)
1232 targ = find(world, targetname, self.target);
1235 objerror("trigger_force without a (valid) .target!\n");
1240 if(self.falloff == 1)
1241 str = (str / self.radius) * self.strength;
1242 else if(self.falloff == 2)
1243 str = (1 - (str / self.radius)) * self.strength;
1245 str = self.strength;
1247 pushdeltatime = time - other.lastpushtime;
1248 if (pushdeltatime > 0.15) pushdeltatime = 0;
1249 other.lastpushtime = time;
1250 if(!pushdeltatime) return;
1252 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1253 other.flags &~= FL_ONGROUND;
1256 // Directionless (accelerator/decelerator) mode
1257 void trigger_impulse_touch2()
1259 float pushdeltatime;
1261 // FIXME: Better checking for what to push and not.
1262 if not(other.iscreature)
1263 if (other.classname != "corpse")
1264 if (other.classname != "body")
1265 if (other.classname != "gib")
1266 if (other.classname != "missile")
1267 if (other.classname != "rocket")
1268 if (other.classname != "casing")
1269 if (other.classname != "grenade")
1270 if (other.classname != "plasma")
1271 if (other.classname != "plasma_prim")
1272 if (other.classname != "plasma_chain")
1273 if (other.classname != "droppedweapon")
1274 if (other.classname != "nexball_basketball")
1275 if (other.classname != "nexball_football")
1278 if (other.deadflag && other.iscreature)
1283 pushdeltatime = time - other.lastpushtime;
1284 if (pushdeltatime > 0.15) pushdeltatime = 0;
1285 other.lastpushtime = time;
1286 if(!pushdeltatime) return;
1288 // div0: ticrate independent, 1 = identity (not 20)
1289 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1292 // Spherical (gravity/repulsor) mode
1293 void trigger_impulse_touch3()
1295 float pushdeltatime;
1298 // FIXME: Better checking for what to push and not.
1299 if not(other.iscreature)
1300 if (other.classname != "corpse")
1301 if (other.classname != "body")
1302 if (other.classname != "gib")
1303 if (other.classname != "missile")
1304 if (other.classname != "rocket")
1305 if (other.classname != "casing")
1306 if (other.classname != "grenade")
1307 if (other.classname != "plasma")
1308 if (other.classname != "plasma_prim")
1309 if (other.classname != "plasma_chain")
1310 if (other.classname != "droppedweapon")
1311 if (other.classname != "nexball_basketball")
1312 if (other.classname != "nexball_football")
1315 if (other.deadflag && other.iscreature)
1320 pushdeltatime = time - other.lastpushtime;
1321 if (pushdeltatime > 0.15) pushdeltatime = 0;
1322 other.lastpushtime = time;
1323 if(!pushdeltatime) return;
1325 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1327 str = min(self.radius, vlen(self.origin - other.origin));
1329 if(self.falloff == 1)
1330 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1331 else if(self.falloff == 2)
1332 str = (str / self.radius) * self.strength; // 0 in the inside
1334 str = self.strength;
1336 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1339 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1340 -------- KEYS --------
1341 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1342 If not, this trigger acts like a damper/accelerator field.
1344 strength : This is how mutch force to add in the direction of .target each second
1345 when .target is set. If not, this is hoe mutch to slow down/accelerate
1346 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1348 radius : If set, act as a spherical device rather then a liniar one.
1350 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1352 -------- NOTES --------
1353 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1354 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1357 void spawnfunc_trigger_impulse()
1362 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1363 setorigin(self, self.origin);
1364 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1365 self.touch = trigger_impulse_touch3;
1371 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1372 self.touch = trigger_impulse_touch1;
1376 if(!self.strength) self.strength = 0.9;
1377 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1378 self.touch = trigger_impulse_touch2;
1383 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1384 "Flip-flop" trigger gate... lets only every second trigger event through
1388 self.state = !self.state;
1393 void spawnfunc_trigger_flipflop()
1395 if(self.spawnflags & 1)
1397 self.use = flipflop_use;
1398 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1401 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1402 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1406 self.nextthink = time + self.wait;
1407 self.enemy = activator;
1413 void monoflop_fixed_use()
1417 self.nextthink = time + self.wait;
1419 self.enemy = activator;
1423 void monoflop_think()
1426 activator = self.enemy;
1430 void monoflop_reset()
1436 void spawnfunc_trigger_monoflop()
1440 if(self.spawnflags & 1)
1441 self.use = monoflop_fixed_use;
1443 self.use = monoflop_use;
1444 self.think = monoflop_think;
1446 self.reset = monoflop_reset;
1449 void multivibrator_send()
1454 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1456 newstate = (time < cyclestart + self.wait);
1459 if(self.state != newstate)
1461 self.state = newstate;
1464 self.nextthink = cyclestart + self.wait + 0.01;
1466 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1469 void multivibrator_toggle()
1471 if(self.nextthink == 0)
1473 multivibrator_send();
1486 void multivibrator_reset()
1488 if(!(self.spawnflags & 1))
1489 self.nextthink = 0; // wait for a trigger event
1491 self.nextthink = max(1, time);
1494 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1495 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1496 -------- KEYS --------
1497 target: trigger all entities with this targetname when it goes off
1498 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1499 phase: offset of the timing
1500 wait: "on" cycle time (default: 1)
1501 respawntime: "off" cycle time (default: same as wait)
1502 -------- SPAWNFLAGS --------
1503 START_ON: assume it is already turned on (when targeted)
1505 void spawnfunc_trigger_multivibrator()
1509 if(!self.respawntime)
1510 self.respawntime = self.wait;
1513 self.use = multivibrator_toggle;
1514 self.think = multivibrator_send;
1515 self.nextthink = time;
1518 multivibrator_reset();
1527 if(self.killtarget != "")
1528 src = find(world, targetname, self.killtarget);
1529 if(self.target != "")
1530 dst = find(world, targetname, self.target);
1534 objerror("follow: could not find target/killtarget");
1540 // already done :P entity must stay
1544 else if(!src || !dst)
1546 objerror("follow: could not find target/killtarget");
1549 else if(self.spawnflags & 1)
1552 if(self.spawnflags & 2)
1554 setattachment(dst, src, self.message);
1558 attach_sameorigin(dst, src, self.message);
1565 if(self.spawnflags & 2)
1567 dst.movetype = MOVETYPE_FOLLOW;
1569 // dst.punchangle = '0 0 0'; // keep unchanged
1570 dst.view_ofs = dst.origin;
1571 dst.v_angle = dst.angles;
1575 follow_sameorigin(dst, src);
1582 void spawnfunc_misc_follow()
1584 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1589 void gamestart_use() {
1595 void spawnfunc_trigger_gamestart() {
1596 self.use = gamestart_use;
1597 self.reset2 = spawnfunc_trigger_gamestart;
1601 self.think = self.use;
1602 self.nextthink = game_starttime + self.wait;
1605 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1611 .entity voicescript; // attached voice script
1612 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1613 .float voicescript_nextthink; // time to play next voice
1614 .float voicescript_voiceend; // time when this voice ends
1616 void target_voicescript_clear(entity pl)
1618 pl.voicescript = world;
1621 void target_voicescript_use()
1623 if(activator.voicescript != self)
1625 activator.voicescript = self;
1626 activator.voicescript_index = 0;
1627 activator.voicescript_nextthink = time + self.delay;
1631 void target_voicescript_next(entity pl)
1636 vs = pl.voicescript;
1639 if(vs.message == "")
1641 if(pl.classname != "player")
1646 if(time >= pl.voicescript_voiceend)
1648 if(time >= pl.voicescript_nextthink)
1650 // get the next voice...
1651 n = tokenize_console(vs.message);
1653 if(pl.voicescript_index < vs.cnt)
1654 i = pl.voicescript_index * 2;
1655 else if(n > vs.cnt * 2)
1656 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1662 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1663 dt = stof(argv(i + 1));
1666 pl.voicescript_voiceend = time + dt;
1667 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1671 pl.voicescript_voiceend = time - dt;
1672 pl.voicescript_nextthink = pl.voicescript_voiceend;
1675 pl.voicescript_index += 1;
1679 pl.voicescript = world; // stop trying then
1685 void spawnfunc_target_voicescript()
1687 // netname: directory of the sound files
1688 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1689 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1690 // Here, a - in front of the duration means that no delay is to be
1691 // added after this message
1692 // wait: average time between messages
1693 // delay: initial delay before the first message
1696 self.use = target_voicescript_use;
1698 n = tokenize_console(self.message);
1700 for(i = 0; i+1 < n; i += 2)
1707 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1713 void trigger_relay_teamcheck_use()
1717 if(self.spawnflags & 2)
1719 if(activator.team != self.team)
1724 if(activator.team == self.team)
1730 if(self.spawnflags & 1)
1735 void trigger_relay_teamcheck_reset()
1737 self.team = self.team_saved;
1740 void spawnfunc_trigger_relay_teamcheck()
1742 self.team_saved = self.team;
1743 self.use = trigger_relay_teamcheck_use;
1744 self.reset = trigger_relay_teamcheck_reset;
1749 void trigger_disablerelay_use()
1756 for(e = world; (e = find(e, targetname, self.target)); )
1758 if(e.use == SUB_UseTargets)
1760 e.use = SUB_DontUseTargets;
1763 else if(e.use == SUB_DontUseTargets)
1765 e.use = SUB_UseTargets;
1771 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1774 void spawnfunc_trigger_disablerelay()
1776 self.use = trigger_disablerelay_use;
1779 float magicear_matched;
1780 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1782 float domatch, dotrigger, matchstart, l;
1786 magicear_matched = FALSE;
1788 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1789 domatch = ((ear.spawnflags & 32) || dotrigger);
1795 if(ear.spawnflags & 4)
1801 if(ear.spawnflags & 1)
1804 if(ear.spawnflags & 2)
1807 if(ear.spawnflags & 8)
1812 l = strlen(ear.message);
1814 if(self.spawnflags & 128)
1817 msg = strdecolorize(msgin);
1819 if(substring(ear.message, 0, 1) == "*")
1821 if(substring(ear.message, -1, 1) == "*")
1824 // as we need multi-replacement here...
1825 s = substring(ear.message, 1, -2);
1827 if(strstrofs(msg, s, 0) >= 0)
1828 matchstart = -2; // we use strreplace on s
1833 s = substring(ear.message, 1, -1);
1835 if(substring(msg, -l, l) == s)
1836 matchstart = strlen(msg) - l;
1841 if(substring(ear.message, -1, 1) == "*")
1844 s = substring(ear.message, 0, -2);
1846 if(substring(msg, 0, l) == s)
1853 if(msg == ear.message)
1858 if(matchstart == -1) // no match
1861 magicear_matched = TRUE;
1865 oldself = activator = self;
1871 if(ear.spawnflags & 16)
1875 else if(ear.netname != "")
1878 return strreplace(s, ear.netname, msg);
1881 substring(msg, 0, matchstart),
1883 substring(msg, matchstart + l, -1)
1891 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1895 for(ear = magicears; ear; ear = ear.enemy)
1897 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1898 if not(ear.spawnflags & 64)
1899 if(magicear_matched)
1906 void spawnfunc_trigger_magicear()
1908 self.enemy = magicears;
1911 // actually handled in "say" processing
1914 // 2 = ignore teamsay
1916 // 8 = ignore tell to unknown player
1917 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1918 // 32 = perform the replacement even if outside the radius or dead
1919 // 64 = continue replacing/triggering even if this one matched
1929 // if set, replacement for the matched text
1931 // "hearing distance"
1936 void relay_activateors_use()
1942 for(trg = world; (trg = find(trg, targetname, os.target)); )
1946 trg.setactive(os.cnt);
1949 if(os.cnt == ACTIVE_TOGGLE)
1951 trg.active = ACTIVE_NOT;
1953 trg.active = ACTIVE_ACTIVE;
1955 trg.active = os.cnt;
1961 void spawnfunc_relay_activate()
1963 self.cnt = ACTIVE_ACTIVE;
1964 self.use = relay_activateors_use;
1967 void spawnfunc_relay_deactivate()
1969 self.cnt = ACTIVE_NOT;
1970 self.use = relay_activateors_use;
1973 void spawnfunc_relay_activatetoggle()
1975 self.cnt = ACTIVE_TOGGLE;
1976 self.use = relay_activateors_use;