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 .float trigger_gravity_active;
537 .entity trigger_gravity_check;
538 void trigger_gravity_check_think()
540 // This spawns when a player enters the gravity zone and checks if he left.
541 // Each frame, self.cnt is set to 2 by trigger_gravity_touch() and decreased by 1 here.
542 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
545 self.owner.gravity = 1;
546 self.owner.trigger_gravity_active = FALSE;
552 self.nextthink = time;
556 void trigger_gravity_use()
558 self.state = !self.state;
561 void trigger_gravity_touch()
563 if(self.state != TRUE)
568 if not(self.spawnflags & 1)
570 if(!other.trigger_gravity_active)
572 other.trigger_gravity_active = TRUE;
573 other.trigger_gravity_check = spawn();
574 other.trigger_gravity_check.owner = other;
575 other.trigger_gravity_check.think = trigger_gravity_check_think;
576 other.trigger_gravity_check.nextthink = time;
578 other.trigger_gravity_check.cnt = 2;
581 if (other.gravity != self.gravity)
583 other.gravity = self.gravity;
585 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
589 void spawnfunc_trigger_gravity()
591 if(self.gravity == 1)
595 self.touch = trigger_gravity_touch;
597 precache_sound(self.noise);
602 self.use = trigger_gravity_use;
603 if(self.spawnflags & 2)
608 //=============================================================================
610 // TODO add a way to do looped sounds with sound(); then complete this entity
611 .float volume, atten;
612 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
614 void spawnfunc_target_speaker()
617 precache_sound (self.noise);
621 self.atten = ATTN_NORM;
622 else if(self.atten < 0)
626 self.use = target_speaker_use;
631 self.atten = ATTN_STATIC;
632 else if(self.atten < 0)
636 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
641 void spawnfunc_func_stardust() {
642 self.effects = EF_STARDUST;
646 .float bgmscriptattack;
647 .float bgmscriptdecay;
648 .float bgmscriptsustain;
649 .float bgmscriptrelease;
650 float pointparticles_SendEntity(entity to, float fl)
652 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
654 // optional features to save space
656 if(self.spawnflags & 2)
657 fl |= 0x10; // absolute count on toggle-on
658 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
659 fl |= 0x20; // 4 bytes - saves CPU
660 if(self.waterlevel || self.count != 1)
661 fl |= 0x40; // 4 bytes - obscure features almost never used
662 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
663 fl |= 0x80; // 14 bytes - saves lots of space
665 WriteByte(MSG_ENTITY, fl);
669 WriteCoord(MSG_ENTITY, self.impulse);
671 WriteCoord(MSG_ENTITY, 0); // off
675 WriteCoord(MSG_ENTITY, self.origin_x);
676 WriteCoord(MSG_ENTITY, self.origin_y);
677 WriteCoord(MSG_ENTITY, self.origin_z);
681 if(self.model != "null")
683 WriteShort(MSG_ENTITY, self.modelindex);
686 WriteCoord(MSG_ENTITY, self.mins_x);
687 WriteCoord(MSG_ENTITY, self.mins_y);
688 WriteCoord(MSG_ENTITY, self.mins_z);
689 WriteCoord(MSG_ENTITY, self.maxs_x);
690 WriteCoord(MSG_ENTITY, self.maxs_y);
691 WriteCoord(MSG_ENTITY, self.maxs_z);
696 WriteShort(MSG_ENTITY, 0);
699 WriteCoord(MSG_ENTITY, self.maxs_x);
700 WriteCoord(MSG_ENTITY, self.maxs_y);
701 WriteCoord(MSG_ENTITY, self.maxs_z);
704 WriteShort(MSG_ENTITY, self.cnt);
707 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
708 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
712 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
713 WriteByte(MSG_ENTITY, self.count * 16.0);
715 WriteString(MSG_ENTITY, self.noise);
718 WriteByte(MSG_ENTITY, floor(self.atten * 64));
719 WriteByte(MSG_ENTITY, floor(self.volume * 255));
721 WriteString(MSG_ENTITY, self.bgmscript);
722 if(self.bgmscript != "")
724 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
725 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
726 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
727 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
733 void pointparticles_use()
735 self.state = !self.state;
739 void pointparticles_think()
741 if(self.origin != self.oldorigin)
744 self.oldorigin = self.origin;
746 self.nextthink = time;
749 void pointparticles_reset()
751 if(self.spawnflags & 1)
757 void spawnfunc_func_pointparticles()
760 setmodel(self, self.model);
762 precache_sound (self.noise);
764 if(!self.bgmscriptsustain)
765 self.bgmscriptsustain = 1;
766 else if(self.bgmscriptsustain < 0)
767 self.bgmscriptsustain = 0;
770 self.atten = ATTN_NORM;
771 else if(self.atten < 0)
782 setorigin(self, self.origin + self.mins);
783 setsize(self, '0 0 0', self.maxs - self.mins);
786 self.cnt = particleeffectnum(self.mdl);
788 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
792 self.use = pointparticles_use;
793 self.reset = pointparticles_reset;
798 self.think = pointparticles_think;
799 self.nextthink = time;
802 void spawnfunc_func_sparks()
804 // self.cnt is the amount of sparks that one burst will spawn
806 self.cnt = 25.0; // nice default value
809 // self.wait is the probability that a sparkthink will spawn a spark shower
810 // range: 0 - 1, but 0 makes little sense, so...
811 if(self.wait < 0.05) {
812 self.wait = 0.25; // nice default value
815 self.count = self.cnt;
818 self.velocity = '0 0 -1';
819 self.mdl = "TE_SPARK";
820 self.impulse = 10 * self.wait; // by default 2.5/sec
822 self.cnt = 0; // use mdl
824 spawnfunc_func_pointparticles();
827 float rainsnow_SendEntity(entity to, float sf)
829 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
830 WriteByte(MSG_ENTITY, self.state);
831 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
832 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
833 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
834 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
835 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
836 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
837 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
838 WriteShort(MSG_ENTITY, self.count);
839 WriteByte(MSG_ENTITY, self.cnt);
843 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
844 This is an invisible area like a trigger, which rain falls inside of.
848 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
850 sets color of rain (default 12 - white)
852 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
854 void spawnfunc_func_rain()
856 self.dest = self.velocity;
857 self.velocity = '0 0 0';
859 self.dest = '0 0 -700';
860 self.angles = '0 0 0';
861 self.movetype = MOVETYPE_NONE;
862 self.solid = SOLID_NOT;
863 SetBrushEntityModel();
868 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
871 if(self.count > 65535)
874 self.state = 1; // 1 is rain, 0 is snow
877 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
881 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
882 This is an invisible area like a trigger, which snow falls inside of.
886 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
888 sets color of rain (default 12 - white)
890 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
892 void spawnfunc_func_snow()
894 self.dest = self.velocity;
895 self.velocity = '0 0 0';
897 self.dest = '0 0 -300';
898 self.angles = '0 0 0';
899 self.movetype = MOVETYPE_NONE;
900 self.solid = SOLID_NOT;
901 SetBrushEntityModel();
906 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
909 if(self.count > 65535)
912 self.state = 0; // 1 is rain, 0 is snow
915 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
919 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
922 void misc_laser_aim()
927 if(self.spawnflags & 2)
929 if(self.enemy.origin != self.mangle)
931 self.mangle = self.enemy.origin;
937 a = vectoangles(self.enemy.origin - self.origin);
948 if(self.angles != self.mangle)
950 self.mangle = self.angles;
954 if(self.origin != self.oldorigin)
957 self.oldorigin = self.origin;
961 void misc_laser_init()
963 if(self.target != "")
964 self.enemy = find(world, targetname, self.target);
968 void misc_laser_think()
973 self.nextthink = time;
982 o = self.enemy.origin;
983 if not(self.spawnflags & 2)
984 o = self.origin + normalize(o - self.origin) * 32768;
988 makevectors(self.mangle);
989 o = self.origin + v_forward * 32768;
995 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
997 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1000 if(self.enemy.target != "") // DETECTOR laser
1002 traceline(self.origin, o, MOVE_NORMAL, self);
1003 if(trace_ent.iscreature)
1005 self.pusher = trace_ent;
1012 activator = self.pusher;
1025 activator = self.pusher;
1033 float laser_SendEntity(entity to, float fl)
1035 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1036 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1037 if(self.spawnflags & 2)
1041 if(self.scale != 1 || self.modelscale != 1)
1043 WriteByte(MSG_ENTITY, fl);
1046 WriteCoord(MSG_ENTITY, self.origin_x);
1047 WriteCoord(MSG_ENTITY, self.origin_y);
1048 WriteCoord(MSG_ENTITY, self.origin_z);
1052 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1053 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1054 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1056 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1059 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1060 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1062 WriteShort(MSG_ENTITY, self.cnt + 1);
1068 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1069 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1070 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1074 WriteAngle(MSG_ENTITY, self.mangle_x);
1075 WriteAngle(MSG_ENTITY, self.mangle_y);
1079 WriteByte(MSG_ENTITY, self.state);
1083 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1084 Any object touching the beam will be hurt
1087 spawnfunc_target_position where the laser ends
1089 name of beam end effect to use
1091 color of the beam (default: red)
1093 damage per second (-1 for a laser that kills immediately)
1097 self.state = !self.state;
1098 self.SendFlags |= 4;
1104 if(self.spawnflags & 1)
1110 void spawnfunc_misc_laser()
1114 if(self.mdl == "none")
1118 self.cnt = particleeffectnum(self.mdl);
1121 self.cnt = particleeffectnum("laser_deadly");
1127 self.cnt = particleeffectnum("laser_deadly");
1134 if(self.colormod == '0 0 0')
1136 self.colormod = '1 0 0';
1138 self.message = "saw the light";
1140 self.message2 = "was pushed into a laser by";
1143 if(!self.modelscale)
1144 self.modelscale = 1;
1145 self.think = misc_laser_think;
1146 self.nextthink = time;
1147 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1149 self.mangle = self.angles;
1151 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1155 self.reset = laser_reset;
1157 self.use = laser_use;
1163 // tZorks trigger impulse / gravity
1167 .float lastpushtime;
1169 // targeted (directional) mode
1170 void trigger_impulse_touch1()
1173 float pushdeltatime;
1176 // FIXME: Better checking for what to push and not.
1177 if not(other.iscreature)
1178 if (other.classname != "corpse")
1179 if (other.classname != "body")
1180 if (other.classname != "gib")
1181 if (other.classname != "missile")
1182 if (other.classname != "rocket")
1183 if (other.classname != "casing")
1184 if (other.classname != "grenade")
1185 if (other.classname != "plasma")
1186 if (other.classname != "plasma_prim")
1187 if (other.classname != "plasma_chain")
1188 if (other.classname != "droppedweapon")
1189 if (other.classname != "nexball_basketball")
1190 if (other.classname != "nexball_football")
1193 if (other.deadflag && other.iscreature)
1198 targ = find(world, targetname, self.target);
1201 objerror("trigger_force without a (valid) .target!\n");
1206 if(self.falloff == 1)
1207 str = (str / self.radius) * self.strength;
1208 else if(self.falloff == 2)
1209 str = (1 - (str / self.radius)) * self.strength;
1211 str = self.strength;
1213 pushdeltatime = time - other.lastpushtime;
1214 if (pushdeltatime > 0.15) pushdeltatime = 0;
1215 other.lastpushtime = time;
1216 if(!pushdeltatime) return;
1218 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1219 other.flags &~= FL_ONGROUND;
1222 // Directionless (accelerator/decelerator) mode
1223 void trigger_impulse_touch2()
1225 float pushdeltatime;
1227 // FIXME: Better checking for what to push and not.
1228 if not(other.iscreature)
1229 if (other.classname != "corpse")
1230 if (other.classname != "body")
1231 if (other.classname != "gib")
1232 if (other.classname != "missile")
1233 if (other.classname != "rocket")
1234 if (other.classname != "casing")
1235 if (other.classname != "grenade")
1236 if (other.classname != "plasma")
1237 if (other.classname != "plasma_prim")
1238 if (other.classname != "plasma_chain")
1239 if (other.classname != "droppedweapon")
1240 if (other.classname != "nexball_basketball")
1241 if (other.classname != "nexball_football")
1244 if (other.deadflag && other.iscreature)
1249 pushdeltatime = time - other.lastpushtime;
1250 if (pushdeltatime > 0.15) pushdeltatime = 0;
1251 other.lastpushtime = time;
1252 if(!pushdeltatime) return;
1254 // div0: ticrate independent, 1 = identity (not 20)
1255 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1258 // Spherical (gravity/repulsor) mode
1259 void trigger_impulse_touch3()
1261 float pushdeltatime;
1264 // FIXME: Better checking for what to push and not.
1265 if not(other.iscreature)
1266 if (other.classname != "corpse")
1267 if (other.classname != "body")
1268 if (other.classname != "gib")
1269 if (other.classname != "missile")
1270 if (other.classname != "rocket")
1271 if (other.classname != "casing")
1272 if (other.classname != "grenade")
1273 if (other.classname != "plasma")
1274 if (other.classname != "plasma_prim")
1275 if (other.classname != "plasma_chain")
1276 if (other.classname != "droppedweapon")
1277 if (other.classname != "nexball_basketball")
1278 if (other.classname != "nexball_football")
1281 if (other.deadflag && other.iscreature)
1286 pushdeltatime = time - other.lastpushtime;
1287 if (pushdeltatime > 0.15) pushdeltatime = 0;
1288 other.lastpushtime = time;
1289 if(!pushdeltatime) return;
1291 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1293 str = min(self.radius, vlen(self.origin - other.origin));
1295 if(self.falloff == 1)
1296 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1297 else if(self.falloff == 2)
1298 str = (str / self.radius) * self.strength; // 0 in the inside
1300 str = self.strength;
1302 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1305 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1306 -------- KEYS --------
1307 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1308 If not, this trigger acts like a damper/accelerator field.
1310 strength : This is how mutch force to add in the direction of .target each second
1311 when .target is set. If not, this is hoe mutch to slow down/accelerate
1312 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1314 radius : If set, act as a spherical device rather then a liniar one.
1316 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1318 -------- NOTES --------
1319 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1320 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1323 void spawnfunc_trigger_impulse()
1328 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1329 setorigin(self, self.origin);
1330 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1331 self.touch = trigger_impulse_touch3;
1337 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1338 self.touch = trigger_impulse_touch1;
1342 if(!self.strength) self.strength = 0.9;
1343 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1344 self.touch = trigger_impulse_touch2;
1349 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1350 "Flip-flop" trigger gate... lets only every second trigger event through
1354 self.state = !self.state;
1359 void spawnfunc_trigger_flipflop()
1361 if(self.spawnflags & 1)
1363 self.use = flipflop_use;
1364 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1367 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1368 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1372 self.nextthink = time + self.wait;
1373 self.enemy = activator;
1379 void monoflop_fixed_use()
1383 self.nextthink = time + self.wait;
1385 self.enemy = activator;
1389 void monoflop_think()
1392 activator = self.enemy;
1396 void monoflop_reset()
1402 void spawnfunc_trigger_monoflop()
1406 if(self.spawnflags & 1)
1407 self.use = monoflop_fixed_use;
1409 self.use = monoflop_use;
1410 self.think = monoflop_think;
1412 self.reset = monoflop_reset;
1415 void multivibrator_send()
1420 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1422 newstate = (time < cyclestart + self.wait);
1425 if(self.state != newstate)
1427 self.state = newstate;
1430 self.nextthink = cyclestart + self.wait + 0.01;
1432 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1435 void multivibrator_toggle()
1437 if(self.nextthink == 0)
1439 multivibrator_send();
1452 void multivibrator_reset()
1454 if(!(self.spawnflags & 1))
1455 self.nextthink = 0; // wait for a trigger event
1457 self.nextthink = max(1, time);
1460 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1461 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1462 -------- KEYS --------
1463 target: trigger all entities with this targetname when it goes off
1464 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1465 phase: offset of the timing
1466 wait: "on" cycle time (default: 1)
1467 respawntime: "off" cycle time (default: same as wait)
1468 -------- SPAWNFLAGS --------
1469 START_ON: assume it is already turned on (when targeted)
1471 void spawnfunc_trigger_multivibrator()
1475 if(!self.respawntime)
1476 self.respawntime = self.wait;
1479 self.use = multivibrator_toggle;
1480 self.think = multivibrator_send;
1481 self.nextthink = time;
1484 multivibrator_reset();
1493 if(self.killtarget != "")
1494 src = find(world, targetname, self.killtarget);
1495 if(self.target != "")
1496 dst = find(world, targetname, self.target);
1500 objerror("follow: could not find target/killtarget");
1506 // already done :P entity must stay
1510 else if(!src || !dst)
1512 objerror("follow: could not find target/killtarget");
1515 else if(self.spawnflags & 1)
1518 if(self.spawnflags & 2)
1520 setattachment(dst, src, self.message);
1524 attach_sameorigin(dst, src, self.message);
1531 if(self.spawnflags & 2)
1533 dst.movetype = MOVETYPE_FOLLOW;
1535 // dst.punchangle = '0 0 0'; // keep unchanged
1536 dst.view_ofs = dst.origin;
1537 dst.v_angle = dst.angles;
1541 follow_sameorigin(dst, src);
1548 void spawnfunc_misc_follow()
1550 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1555 void gamestart_use() {
1561 void spawnfunc_trigger_gamestart() {
1562 self.use = gamestart_use;
1563 self.reset2 = spawnfunc_trigger_gamestart;
1567 self.think = self.use;
1568 self.nextthink = game_starttime + self.wait;
1571 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1577 .entity voicescript; // attached voice script
1578 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1579 .float voicescript_nextthink; // time to play next voice
1580 .float voicescript_voiceend; // time when this voice ends
1582 void target_voicescript_clear(entity pl)
1584 pl.voicescript = world;
1587 void target_voicescript_use()
1589 if(activator.voicescript != self)
1591 activator.voicescript = self;
1592 activator.voicescript_index = 0;
1593 activator.voicescript_nextthink = time + self.delay;
1597 void target_voicescript_next(entity pl)
1602 vs = pl.voicescript;
1605 if(vs.message == "")
1607 if(pl.classname != "player")
1612 if(time >= pl.voicescript_voiceend)
1614 if(time >= pl.voicescript_nextthink)
1616 // get the next voice...
1617 n = tokenize_console(vs.message);
1619 if(pl.voicescript_index < vs.cnt)
1620 i = pl.voicescript_index * 2;
1621 else if(n > vs.cnt * 2)
1622 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1628 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1629 dt = stof(argv(i + 1));
1632 pl.voicescript_voiceend = time + dt;
1633 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1637 pl.voicescript_voiceend = time - dt;
1638 pl.voicescript_nextthink = pl.voicescript_voiceend;
1641 pl.voicescript_index += 1;
1645 pl.voicescript = world; // stop trying then
1651 void spawnfunc_target_voicescript()
1653 // netname: directory of the sound files
1654 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1655 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1656 // Here, a - in front of the duration means that no delay is to be
1657 // added after this message
1658 // wait: average time between messages
1659 // delay: initial delay before the first message
1662 self.use = target_voicescript_use;
1664 n = tokenize_console(self.message);
1666 for(i = 0; i+1 < n; i += 2)
1673 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1679 void trigger_relay_teamcheck_use()
1683 if(self.spawnflags & 2)
1685 if(activator.team != self.team)
1690 if(activator.team == self.team)
1696 if(self.spawnflags & 1)
1701 void trigger_relay_teamcheck_reset()
1703 self.team = self.team_saved;
1706 void spawnfunc_trigger_relay_teamcheck()
1708 self.team_saved = self.team;
1709 self.use = trigger_relay_teamcheck_use;
1710 self.reset = trigger_relay_teamcheck_reset;
1715 void trigger_disablerelay_use()
1722 for(e = world; (e = find(e, targetname, self.target)); )
1724 if(e.use == SUB_UseTargets)
1726 e.use = SUB_DontUseTargets;
1729 else if(e.use == SUB_DontUseTargets)
1731 e.use = SUB_UseTargets;
1737 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1740 void spawnfunc_trigger_disablerelay()
1742 self.use = trigger_disablerelay_use;
1745 float magicear_matched;
1746 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1748 float domatch, dotrigger, matchstart, l;
1752 magicear_matched = FALSE;
1754 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1755 domatch = ((ear.spawnflags & 32) || dotrigger);
1761 if(ear.spawnflags & 4)
1767 if(ear.spawnflags & 1)
1770 if(ear.spawnflags & 2)
1773 if(ear.spawnflags & 8)
1778 l = strlen(ear.message);
1780 if(self.spawnflags & 128)
1783 msg = strdecolorize(msgin);
1785 if(substring(ear.message, 0, 1) == "*")
1787 if(substring(ear.message, -1, 1) == "*")
1790 // as we need multi-replacement here...
1791 s = substring(ear.message, 1, -2);
1793 if(strstrofs(msg, s, 0) >= 0)
1794 matchstart = -2; // we use strreplace on s
1799 s = substring(ear.message, 1, -1);
1801 if(substring(msg, -l, l) == s)
1802 matchstart = strlen(msg) - l;
1807 if(substring(ear.message, -1, 1) == "*")
1810 s = substring(ear.message, 0, -2);
1812 if(substring(msg, 0, l) == s)
1819 if(msg == ear.message)
1824 if(matchstart == -1) // no match
1827 magicear_matched = TRUE;
1831 oldself = activator = self;
1837 if(ear.spawnflags & 16)
1841 else if(ear.netname != "")
1844 return strreplace(s, ear.netname, msg);
1847 substring(msg, 0, matchstart),
1849 substring(msg, matchstart + l, -1)
1857 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1861 for(ear = magicears; ear; ear = ear.enemy)
1863 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1864 if not(ear.spawnflags & 64)
1865 if(magicear_matched)
1872 void spawnfunc_trigger_magicear()
1874 self.enemy = magicears;
1877 // actually handled in "say" processing
1880 // 2 = ignore teamsay
1882 // 8 = ignore tell to unknown player
1883 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1884 // 32 = perform the replacement even if outside the radius or dead
1885 // 64 = continue replacing/triggering even if this one matched
1895 // if set, replacement for the matched text
1897 // "hearing distance"