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 triggergravitytime;
537 void trigger_gravity_touch()
539 if(sv_gravity != 800)
541 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
542 if (other.triggergravitytime < time)
545 other.triggergravitytime = time + 1;
547 if (other.gravity != self.gravity)
549 other.gravity = self.gravity;
551 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
556 void spawnfunc_trigger_gravity()
561 self.touch = trigger_gravity_touch;
563 precache_sound(self.noise);
566 // TODO add a way to do looped sounds with sound(); then complete this entity
567 .float volume, atten;
568 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
570 void spawnfunc_target_speaker()
573 precache_sound (self.noise);
577 self.atten = ATTN_NORM;
578 else if(self.atten < 0)
582 self.use = target_speaker_use;
587 self.atten = ATTN_STATIC;
588 else if(self.atten < 0)
592 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
597 void spawnfunc_func_stardust() {
598 self.effects = EF_STARDUST;
602 .float bgmscriptattack;
603 .float bgmscriptdecay;
604 .float bgmscriptsustain;
605 .float bgmscriptrelease;
606 float pointparticles_SendEntity(entity to, float fl)
608 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
610 // optional features to save space
612 if(self.spawnflags & 2)
613 fl |= 0x10; // absolute count on toggle-on
614 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
615 fl |= 0x20; // 4 bytes - saves CPU
616 if(self.waterlevel || self.count != 1)
617 fl |= 0x40; // 4 bytes - obscure features almost never used
618 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
619 fl |= 0x80; // 14 bytes - saves lots of space
621 WriteByte(MSG_ENTITY, fl);
625 WriteCoord(MSG_ENTITY, self.impulse);
627 WriteCoord(MSG_ENTITY, 0); // off
631 WriteCoord(MSG_ENTITY, self.origin_x);
632 WriteCoord(MSG_ENTITY, self.origin_y);
633 WriteCoord(MSG_ENTITY, self.origin_z);
637 if(self.model != "null")
639 WriteShort(MSG_ENTITY, self.modelindex);
642 WriteCoord(MSG_ENTITY, self.mins_x);
643 WriteCoord(MSG_ENTITY, self.mins_y);
644 WriteCoord(MSG_ENTITY, self.mins_z);
645 WriteCoord(MSG_ENTITY, self.maxs_x);
646 WriteCoord(MSG_ENTITY, self.maxs_y);
647 WriteCoord(MSG_ENTITY, self.maxs_z);
652 WriteShort(MSG_ENTITY, 0);
655 WriteCoord(MSG_ENTITY, self.maxs_x);
656 WriteCoord(MSG_ENTITY, self.maxs_y);
657 WriteCoord(MSG_ENTITY, self.maxs_z);
660 WriteShort(MSG_ENTITY, self.cnt);
663 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
664 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
668 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
669 WriteByte(MSG_ENTITY, self.count * 16.0);
671 WriteString(MSG_ENTITY, self.noise);
674 WriteByte(MSG_ENTITY, floor(self.atten * 64));
675 WriteByte(MSG_ENTITY, floor(self.volume * 255));
677 WriteString(MSG_ENTITY, self.bgmscript);
678 if(self.bgmscript != "")
680 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
681 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
682 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
683 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
689 void pointparticles_use()
691 self.state = !self.state;
695 void pointparticles_think()
697 if(self.origin != self.oldorigin)
700 self.oldorigin = self.origin;
702 self.nextthink = time;
705 void pointparticles_reset()
707 if(self.spawnflags & 1)
713 void spawnfunc_func_pointparticles()
716 setmodel(self, self.model);
718 precache_sound (self.noise);
720 if(!self.bgmscriptsustain)
721 self.bgmscriptsustain = 1;
722 else if(self.bgmscriptsustain < 0)
723 self.bgmscriptsustain = 0;
726 self.atten = ATTN_NORM;
727 else if(self.atten < 0)
738 setorigin(self, self.origin + self.mins);
739 setsize(self, '0 0 0', self.maxs - self.mins);
742 self.cnt = particleeffectnum(self.mdl);
744 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
748 self.use = pointparticles_use;
749 self.reset = pointparticles_reset;
754 self.think = pointparticles_think;
755 self.nextthink = time;
758 void spawnfunc_func_sparks()
760 // self.cnt is the amount of sparks that one burst will spawn
762 self.cnt = 25.0; // nice default value
765 // self.wait is the probability that a sparkthink will spawn a spark shower
766 // range: 0 - 1, but 0 makes little sense, so...
767 if(self.wait < 0.05) {
768 self.wait = 0.25; // nice default value
771 self.count = self.cnt;
774 self.velocity = '0 0 -1';
775 self.mdl = "TE_SPARK";
776 self.impulse = 10 * self.wait; // by default 2.5/sec
778 self.cnt = 0; // use mdl
780 spawnfunc_func_pointparticles();
783 float rainsnow_SendEntity(entity to, float sf)
785 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
786 WriteByte(MSG_ENTITY, self.state);
787 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
788 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
789 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
790 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
791 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
792 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
793 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
794 WriteShort(MSG_ENTITY, self.count);
795 WriteByte(MSG_ENTITY, self.cnt);
799 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
800 This is an invisible area like a trigger, which rain falls inside of.
804 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
806 sets color of rain (default 12 - white)
808 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
810 void spawnfunc_func_rain()
812 self.dest = self.velocity;
813 self.velocity = '0 0 0';
815 self.dest = '0 0 -700';
816 self.angles = '0 0 0';
817 self.movetype = MOVETYPE_NONE;
818 self.solid = SOLID_NOT;
819 SetBrushEntityModel();
824 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
827 if(self.count > 65535)
830 self.state = 1; // 1 is rain, 0 is snow
833 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
837 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
838 This is an invisible area like a trigger, which snow falls inside of.
842 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
844 sets color of rain (default 12 - white)
846 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
848 void spawnfunc_func_snow()
850 self.dest = self.velocity;
851 self.velocity = '0 0 0';
853 self.dest = '0 0 -300';
854 self.angles = '0 0 0';
855 self.movetype = MOVETYPE_NONE;
856 self.solid = SOLID_NOT;
857 SetBrushEntityModel();
862 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
865 if(self.count > 65535)
868 self.state = 0; // 1 is rain, 0 is snow
871 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
875 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
878 void misc_laser_aim()
883 if(self.spawnflags & 2)
885 if(self.enemy.origin != self.mangle)
887 self.mangle = self.enemy.origin;
893 a = vectoangles(self.enemy.origin - self.origin);
904 if(self.angles != self.mangle)
906 self.mangle = self.angles;
910 if(self.origin != self.oldorigin)
913 self.oldorigin = self.origin;
917 void misc_laser_init()
919 if(self.target != "")
920 self.enemy = find(world, targetname, self.target);
924 void misc_laser_think()
929 self.nextthink = time;
938 o = self.enemy.origin;
939 if not(self.spawnflags & 2)
940 o = self.origin + normalize(o - self.origin) * 32768;
944 makevectors(self.mangle);
945 o = self.origin + v_forward * 32768;
951 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
953 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
956 if(self.enemy.target != "") // DETECTOR laser
958 traceline(self.origin, o, MOVE_NORMAL, self);
959 if(trace_ent.iscreature)
961 self.pusher = trace_ent;
968 activator = self.pusher;
981 activator = self.pusher;
989 float laser_SendEntity(entity to, float fl)
991 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
992 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
993 if(self.spawnflags & 2)
997 if(self.scale != 1 || self.modelscale != 1)
999 WriteByte(MSG_ENTITY, fl);
1002 WriteCoord(MSG_ENTITY, self.origin_x);
1003 WriteCoord(MSG_ENTITY, self.origin_y);
1004 WriteCoord(MSG_ENTITY, self.origin_z);
1008 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1009 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1010 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1012 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1015 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1016 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1018 WriteShort(MSG_ENTITY, self.cnt + 1);
1024 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1025 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1026 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1030 WriteAngle(MSG_ENTITY, self.mangle_x);
1031 WriteAngle(MSG_ENTITY, self.mangle_y);
1035 WriteByte(MSG_ENTITY, self.state);
1039 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1040 Any object touching the beam will be hurt
1043 spawnfunc_target_position where the laser ends
1045 name of beam end effect to use
1047 color of the beam (default: red)
1049 damage per second (-1 for a laser that kills immediately)
1053 self.state = !self.state;
1054 self.SendFlags |= 4;
1060 if(self.spawnflags & 1)
1066 void spawnfunc_misc_laser()
1070 if(self.mdl == "none")
1074 self.cnt = particleeffectnum(self.mdl);
1077 self.cnt = particleeffectnum("laser_deadly");
1083 self.cnt = particleeffectnum("laser_deadly");
1090 if(self.colormod == '0 0 0')
1092 self.colormod = '1 0 0';
1094 self.message = "saw the light";
1096 self.message2 = "was pushed into a laser by";
1099 if(!self.modelscale)
1100 self.modelscale = 1;
1101 self.think = misc_laser_think;
1102 self.nextthink = time;
1103 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1105 self.mangle = self.angles;
1107 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1111 self.reset = laser_reset;
1113 self.use = laser_use;
1119 // tZorks trigger impulse / gravity
1123 .float lastpushtime;
1125 // targeted (directional) mode
1126 void trigger_impulse_touch1()
1129 float pushdeltatime;
1132 // FIXME: Better checking for what to push and not.
1133 if not(other.iscreature)
1134 if (other.classname != "corpse")
1135 if (other.classname != "body")
1136 if (other.classname != "gib")
1137 if (other.classname != "missile")
1138 if (other.classname != "rocket")
1139 if (other.classname != "casing")
1140 if (other.classname != "grenade")
1141 if (other.classname != "plasma")
1142 if (other.classname != "plasma_prim")
1143 if (other.classname != "plasma_chain")
1144 if (other.classname != "droppedweapon")
1145 if (other.classname != "nexball_basketball")
1146 if (other.classname != "nexball_football")
1149 if (other.deadflag && other.iscreature)
1154 targ = find(world, targetname, self.target);
1157 objerror("trigger_force without a (valid) .target!\n");
1162 if(self.falloff == 1)
1163 str = (str / self.radius) * self.strength;
1164 else if(self.falloff == 2)
1165 str = (1 - (str / self.radius)) * self.strength;
1167 str = self.strength;
1169 pushdeltatime = time - other.lastpushtime;
1170 if (pushdeltatime > 0.15) pushdeltatime = 0;
1171 other.lastpushtime = time;
1172 if(!pushdeltatime) return;
1174 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1175 other.flags &~= FL_ONGROUND;
1178 // Directionless (accelerator/decelerator) mode
1179 void trigger_impulse_touch2()
1181 float pushdeltatime;
1183 // FIXME: Better checking for what to push and not.
1184 if not(other.iscreature)
1185 if (other.classname != "corpse")
1186 if (other.classname != "body")
1187 if (other.classname != "gib")
1188 if (other.classname != "missile")
1189 if (other.classname != "rocket")
1190 if (other.classname != "casing")
1191 if (other.classname != "grenade")
1192 if (other.classname != "plasma")
1193 if (other.classname != "plasma_prim")
1194 if (other.classname != "plasma_chain")
1195 if (other.classname != "droppedweapon")
1196 if (other.classname != "nexball_basketball")
1197 if (other.classname != "nexball_football")
1200 if (other.deadflag && other.iscreature)
1205 pushdeltatime = time - other.lastpushtime;
1206 if (pushdeltatime > 0.15) pushdeltatime = 0;
1207 other.lastpushtime = time;
1208 if(!pushdeltatime) return;
1210 // div0: ticrate independent, 1 = identity (not 20)
1211 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1214 // Spherical (gravity/repulsor) mode
1215 void trigger_impulse_touch3()
1217 float pushdeltatime;
1220 // FIXME: Better checking for what to push and not.
1221 if not(other.iscreature)
1222 if (other.classname != "corpse")
1223 if (other.classname != "body")
1224 if (other.classname != "gib")
1225 if (other.classname != "missile")
1226 if (other.classname != "rocket")
1227 if (other.classname != "casing")
1228 if (other.classname != "grenade")
1229 if (other.classname != "plasma")
1230 if (other.classname != "plasma_prim")
1231 if (other.classname != "plasma_chain")
1232 if (other.classname != "droppedweapon")
1233 if (other.classname != "nexball_basketball")
1234 if (other.classname != "nexball_football")
1237 if (other.deadflag && other.iscreature)
1242 pushdeltatime = time - other.lastpushtime;
1243 if (pushdeltatime > 0.15) pushdeltatime = 0;
1244 other.lastpushtime = time;
1245 if(!pushdeltatime) return;
1247 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1249 str = min(self.radius, vlen(self.origin - other.origin));
1251 if(self.falloff == 1)
1252 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1253 else if(self.falloff == 2)
1254 str = (str / self.radius) * self.strength; // 0 in the inside
1256 str = self.strength;
1258 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1261 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1262 -------- KEYS --------
1263 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1264 If not, this trigger acts like a damper/accelerator field.
1266 strength : This is how mutch force to add in the direction of .target each second
1267 when .target is set. If not, this is hoe mutch to slow down/accelerate
1268 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1270 radius : If set, act as a spherical device rather then a liniar one.
1272 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1274 -------- NOTES --------
1275 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1276 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1279 void spawnfunc_trigger_impulse()
1284 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1285 setorigin(self, self.origin);
1286 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1287 self.touch = trigger_impulse_touch3;
1293 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1294 self.touch = trigger_impulse_touch1;
1298 if(!self.strength) self.strength = 0.9;
1299 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1300 self.touch = trigger_impulse_touch2;
1305 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1306 "Flip-flop" trigger gate... lets only every second trigger event through
1310 self.state = !self.state;
1315 void spawnfunc_trigger_flipflop()
1317 if(self.spawnflags & 1)
1319 self.use = flipflop_use;
1320 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1323 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1324 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1328 self.nextthink = time + self.wait;
1329 self.enemy = activator;
1335 void monoflop_fixed_use()
1339 self.nextthink = time + self.wait;
1341 self.enemy = activator;
1345 void monoflop_think()
1348 activator = self.enemy;
1352 void monoflop_reset()
1358 void spawnfunc_trigger_monoflop()
1362 if(self.spawnflags & 1)
1363 self.use = monoflop_fixed_use;
1365 self.use = monoflop_use;
1366 self.think = monoflop_think;
1368 self.reset = monoflop_reset;
1371 void multivibrator_send()
1376 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1378 newstate = (time < cyclestart + self.wait);
1381 if(self.state != newstate)
1383 self.state = newstate;
1386 self.nextthink = cyclestart + self.wait + 0.01;
1388 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1391 void multivibrator_toggle()
1393 if(self.nextthink == 0)
1395 multivibrator_send();
1408 void multivibrator_reset()
1410 if(!(self.spawnflags & 1))
1411 self.nextthink = 0; // wait for a trigger event
1413 self.nextthink = max(1, time);
1416 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1417 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1418 -------- KEYS --------
1419 target: trigger all entities with this targetname when it goes off
1420 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1421 phase: offset of the timing
1422 wait: "on" cycle time (default: 1)
1423 respawntime: "off" cycle time (default: same as wait)
1424 -------- SPAWNFLAGS --------
1425 START_ON: assume it is already turned on (when targeted)
1427 void spawnfunc_trigger_multivibrator()
1431 if(!self.respawntime)
1432 self.respawntime = self.wait;
1435 self.use = multivibrator_toggle;
1436 self.think = multivibrator_send;
1437 self.nextthink = time;
1440 multivibrator_reset();
1449 if(self.killtarget != "")
1450 src = find(world, targetname, self.killtarget);
1451 if(self.target != "")
1452 dst = find(world, targetname, self.target);
1456 objerror("follow: could not find target/killtarget");
1462 // already done :P entity must stay
1466 else if(!src || !dst)
1468 objerror("follow: could not find target/killtarget");
1471 else if(self.spawnflags & 1)
1474 if(self.spawnflags & 2)
1476 setattachment(dst, src, self.message);
1480 attach_sameorigin(dst, src, self.message);
1487 if(self.spawnflags & 2)
1489 dst.movetype = MOVETYPE_FOLLOW;
1491 // dst.punchangle = '0 0 0'; // keep unchanged
1492 dst.view_ofs = dst.origin;
1493 dst.v_angle = dst.angles;
1497 follow_sameorigin(dst, src);
1504 void spawnfunc_misc_follow()
1506 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1511 void gamestart_use() {
1517 void spawnfunc_trigger_gamestart() {
1518 self.use = gamestart_use;
1519 self.reset2 = spawnfunc_trigger_gamestart;
1523 self.think = self.use;
1524 self.nextthink = game_starttime + self.wait;
1527 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1533 .entity voicescript; // attached voice script
1534 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1535 .float voicescript_nextthink; // time to play next voice
1536 .float voicescript_voiceend; // time when this voice ends
1538 void target_voicescript_clear(entity pl)
1540 pl.voicescript = world;
1543 void target_voicescript_use()
1545 if(activator.voicescript != self)
1547 activator.voicescript = self;
1548 activator.voicescript_index = 0;
1549 activator.voicescript_nextthink = time + self.delay;
1553 void target_voicescript_next(entity pl)
1558 vs = pl.voicescript;
1561 if(vs.message == "")
1563 if(pl.classname != "player")
1568 if(time >= pl.voicescript_voiceend)
1570 if(time >= pl.voicescript_nextthink)
1572 // get the next voice...
1573 n = tokenize_console(vs.message);
1575 if(pl.voicescript_index < vs.cnt)
1576 i = pl.voicescript_index * 2;
1577 else if(n > vs.cnt * 2)
1578 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1584 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1585 dt = stof(argv(i + 1));
1588 pl.voicescript_voiceend = time + dt;
1589 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1593 pl.voicescript_voiceend = time - dt;
1594 pl.voicescript_nextthink = pl.voicescript_voiceend;
1597 pl.voicescript_index += 1;
1601 pl.voicescript = world; // stop trying then
1607 void spawnfunc_target_voicescript()
1609 // netname: directory of the sound files
1610 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1611 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1612 // Here, a - in front of the duration means that no delay is to be
1613 // added after this message
1614 // wait: average time between messages
1615 // delay: initial delay before the first message
1618 self.use = target_voicescript_use;
1620 n = tokenize_console(self.message);
1622 for(i = 0; i+1 < n; i += 2)
1629 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1635 void trigger_relay_teamcheck_use()
1639 if(self.spawnflags & 2)
1641 if(activator.team != self.team)
1646 if(activator.team == self.team)
1652 if(self.spawnflags & 1)
1657 void trigger_relay_teamcheck_reset()
1659 self.team = self.team_saved;
1662 void spawnfunc_trigger_relay_teamcheck()
1664 self.team_saved = self.team;
1665 self.use = trigger_relay_teamcheck_use;
1666 self.reset = trigger_relay_teamcheck_reset;
1671 void trigger_disablerelay_use()
1678 for(e = world; (e = find(e, targetname, self.target)); )
1680 if(e.use == SUB_UseTargets)
1682 e.use = SUB_DontUseTargets;
1685 else if(e.use == SUB_DontUseTargets)
1687 e.use = SUB_UseTargets;
1693 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1696 void spawnfunc_trigger_disablerelay()
1698 self.use = trigger_disablerelay_use;
1701 float magicear_matched;
1702 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1704 float domatch, dotrigger, matchstart, l;
1708 magicear_matched = FALSE;
1710 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1711 domatch = ((ear.spawnflags & 32) || dotrigger);
1717 if(ear.spawnflags & 4)
1723 if(ear.spawnflags & 1)
1726 if(ear.spawnflags & 2)
1729 if(ear.spawnflags & 8)
1734 l = strlen(ear.message);
1736 if(self.spawnflags & 128)
1739 msg = strdecolorize(msgin);
1741 if(substring(ear.message, 0, 1) == "*")
1743 if(substring(ear.message, -1, 1) == "*")
1746 // as we need multi-replacement here...
1747 s = substring(ear.message, 1, -2);
1749 if(strstrofs(msg, s, 0) >= 0)
1750 matchstart = -2; // we use strreplace on s
1755 s = substring(ear.message, 1, -1);
1757 if(substring(msg, -l, l) == s)
1758 matchstart = strlen(msg) - l;
1763 if(substring(ear.message, -1, 1) == "*")
1766 s = substring(ear.message, 0, -2);
1768 if(substring(msg, 0, l) == s)
1775 if(msg == ear.message)
1780 if(matchstart == -1) // no match
1783 magicear_matched = TRUE;
1787 oldself = activator = self;
1793 if(ear.spawnflags & 16)
1797 else if(ear.netname != "")
1800 return strreplace(s, ear.netname, msg);
1803 substring(msg, 0, matchstart),
1805 substring(msg, matchstart + l, -1)
1813 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1817 for(ear = magicears; ear; ear = ear.enemy)
1819 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1820 if not(ear.spawnflags & 64)
1821 if(magicear_matched)
1828 void spawnfunc_trigger_magicear()
1830 self.enemy = magicears;
1833 // actually handled in "say" processing
1836 // 2 = ignore teamsay
1838 // 8 = ignore tell to unknown player
1839 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1840 // 32 = perform the replacement even if outside the radius or dead
1841 // 64 = continue replacing/triggering even if this one matched
1851 // if set, replacement for the matched text
1853 // "hearing distance"