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 = 0;
546 self.owner.trigger_gravity_active = 0;
552 self.nextthink = time;
556 void trigger_gravity_touch()
560 if not(self.spawnflags & 1)
562 if(!other.trigger_gravity_active)
564 other.trigger_gravity_active = 1;
565 other.trigger_gravity_check = spawn();
566 other.trigger_gravity_check.owner = other;
567 other.trigger_gravity_check.think = trigger_gravity_check_think;
568 other.trigger_gravity_check.nextthink = time;
570 other.trigger_gravity_check.cnt = 2;
573 if (other.gravity != self.gravity)
575 other.gravity = self.gravity;
577 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
581 void spawnfunc_trigger_gravity()
586 self.touch = trigger_gravity_touch;
588 precache_sound(self.noise);
591 //=============================================================================
593 // TODO add a way to do looped sounds with sound(); then complete this entity
594 .float volume, atten;
595 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
597 void spawnfunc_target_speaker()
600 precache_sound (self.noise);
604 self.atten = ATTN_NORM;
605 else if(self.atten < 0)
609 self.use = target_speaker_use;
614 self.atten = ATTN_STATIC;
615 else if(self.atten < 0)
619 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
624 void spawnfunc_func_stardust() {
625 self.effects = EF_STARDUST;
629 .float bgmscriptattack;
630 .float bgmscriptdecay;
631 .float bgmscriptsustain;
632 .float bgmscriptrelease;
633 float pointparticles_SendEntity(entity to, float fl)
635 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
637 // optional features to save space
639 if(self.spawnflags & 2)
640 fl |= 0x10; // absolute count on toggle-on
641 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
642 fl |= 0x20; // 4 bytes - saves CPU
643 if(self.waterlevel || self.count != 1)
644 fl |= 0x40; // 4 bytes - obscure features almost never used
645 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
646 fl |= 0x80; // 14 bytes - saves lots of space
648 WriteByte(MSG_ENTITY, fl);
652 WriteCoord(MSG_ENTITY, self.impulse);
654 WriteCoord(MSG_ENTITY, 0); // off
658 WriteCoord(MSG_ENTITY, self.origin_x);
659 WriteCoord(MSG_ENTITY, self.origin_y);
660 WriteCoord(MSG_ENTITY, self.origin_z);
664 if(self.model != "null")
666 WriteShort(MSG_ENTITY, self.modelindex);
669 WriteCoord(MSG_ENTITY, self.mins_x);
670 WriteCoord(MSG_ENTITY, self.mins_y);
671 WriteCoord(MSG_ENTITY, self.mins_z);
672 WriteCoord(MSG_ENTITY, self.maxs_x);
673 WriteCoord(MSG_ENTITY, self.maxs_y);
674 WriteCoord(MSG_ENTITY, self.maxs_z);
679 WriteShort(MSG_ENTITY, 0);
682 WriteCoord(MSG_ENTITY, self.maxs_x);
683 WriteCoord(MSG_ENTITY, self.maxs_y);
684 WriteCoord(MSG_ENTITY, self.maxs_z);
687 WriteShort(MSG_ENTITY, self.cnt);
690 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
691 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
695 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
696 WriteByte(MSG_ENTITY, self.count * 16.0);
698 WriteString(MSG_ENTITY, self.noise);
701 WriteByte(MSG_ENTITY, floor(self.atten * 64));
702 WriteByte(MSG_ENTITY, floor(self.volume * 255));
704 WriteString(MSG_ENTITY, self.bgmscript);
705 if(self.bgmscript != "")
707 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
708 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
709 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
710 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
716 void pointparticles_use()
718 self.state = !self.state;
722 void pointparticles_think()
724 if(self.origin != self.oldorigin)
727 self.oldorigin = self.origin;
729 self.nextthink = time;
732 void pointparticles_reset()
734 if(self.spawnflags & 1)
740 void spawnfunc_func_pointparticles()
743 setmodel(self, self.model);
745 precache_sound (self.noise);
747 if(!self.bgmscriptsustain)
748 self.bgmscriptsustain = 1;
749 else if(self.bgmscriptsustain < 0)
750 self.bgmscriptsustain = 0;
753 self.atten = ATTN_NORM;
754 else if(self.atten < 0)
765 setorigin(self, self.origin + self.mins);
766 setsize(self, '0 0 0', self.maxs - self.mins);
769 self.cnt = particleeffectnum(self.mdl);
771 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
775 self.use = pointparticles_use;
776 self.reset = pointparticles_reset;
781 self.think = pointparticles_think;
782 self.nextthink = time;
785 void spawnfunc_func_sparks()
787 // self.cnt is the amount of sparks that one burst will spawn
789 self.cnt = 25.0; // nice default value
792 // self.wait is the probability that a sparkthink will spawn a spark shower
793 // range: 0 - 1, but 0 makes little sense, so...
794 if(self.wait < 0.05) {
795 self.wait = 0.25; // nice default value
798 self.count = self.cnt;
801 self.velocity = '0 0 -1';
802 self.mdl = "TE_SPARK";
803 self.impulse = 10 * self.wait; // by default 2.5/sec
805 self.cnt = 0; // use mdl
807 spawnfunc_func_pointparticles();
810 float rainsnow_SendEntity(entity to, float sf)
812 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
813 WriteByte(MSG_ENTITY, self.state);
814 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
815 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
816 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
817 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
818 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
819 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
820 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
821 WriteShort(MSG_ENTITY, self.count);
822 WriteByte(MSG_ENTITY, self.cnt);
826 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
827 This is an invisible area like a trigger, which rain falls inside of.
831 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
833 sets color of rain (default 12 - white)
835 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
837 void spawnfunc_func_rain()
839 self.dest = self.velocity;
840 self.velocity = '0 0 0';
842 self.dest = '0 0 -700';
843 self.angles = '0 0 0';
844 self.movetype = MOVETYPE_NONE;
845 self.solid = SOLID_NOT;
846 SetBrushEntityModel();
851 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
854 if(self.count > 65535)
857 self.state = 1; // 1 is rain, 0 is snow
860 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
864 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
865 This is an invisible area like a trigger, which snow falls inside of.
869 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
871 sets color of rain (default 12 - white)
873 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
875 void spawnfunc_func_snow()
877 self.dest = self.velocity;
878 self.velocity = '0 0 0';
880 self.dest = '0 0 -300';
881 self.angles = '0 0 0';
882 self.movetype = MOVETYPE_NONE;
883 self.solid = SOLID_NOT;
884 SetBrushEntityModel();
889 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
892 if(self.count > 65535)
895 self.state = 0; // 1 is rain, 0 is snow
898 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
902 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
905 void misc_laser_aim()
910 if(self.spawnflags & 2)
912 if(self.enemy.origin != self.mangle)
914 self.mangle = self.enemy.origin;
920 a = vectoangles(self.enemy.origin - self.origin);
931 if(self.angles != self.mangle)
933 self.mangle = self.angles;
937 if(self.origin != self.oldorigin)
940 self.oldorigin = self.origin;
944 void misc_laser_init()
946 if(self.target != "")
947 self.enemy = find(world, targetname, self.target);
951 void misc_laser_think()
956 self.nextthink = time;
965 o = self.enemy.origin;
966 if not(self.spawnflags & 2)
967 o = self.origin + normalize(o - self.origin) * 32768;
971 makevectors(self.mangle);
972 o = self.origin + v_forward * 32768;
978 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
980 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
983 if(self.enemy.target != "") // DETECTOR laser
985 traceline(self.origin, o, MOVE_NORMAL, self);
986 if(trace_ent.iscreature)
988 self.pusher = trace_ent;
995 activator = self.pusher;
1008 activator = self.pusher;
1016 float laser_SendEntity(entity to, float fl)
1018 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1019 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1020 if(self.spawnflags & 2)
1024 if(self.scale != 1 || self.modelscale != 1)
1026 WriteByte(MSG_ENTITY, fl);
1029 WriteCoord(MSG_ENTITY, self.origin_x);
1030 WriteCoord(MSG_ENTITY, self.origin_y);
1031 WriteCoord(MSG_ENTITY, self.origin_z);
1035 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1036 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1037 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1039 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1042 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1043 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1045 WriteShort(MSG_ENTITY, self.cnt + 1);
1051 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1052 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1053 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1057 WriteAngle(MSG_ENTITY, self.mangle_x);
1058 WriteAngle(MSG_ENTITY, self.mangle_y);
1062 WriteByte(MSG_ENTITY, self.state);
1066 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1067 Any object touching the beam will be hurt
1070 spawnfunc_target_position where the laser ends
1072 name of beam end effect to use
1074 color of the beam (default: red)
1076 damage per second (-1 for a laser that kills immediately)
1080 self.state = !self.state;
1081 self.SendFlags |= 4;
1087 if(self.spawnflags & 1)
1093 void spawnfunc_misc_laser()
1097 if(self.mdl == "none")
1101 self.cnt = particleeffectnum(self.mdl);
1104 self.cnt = particleeffectnum("laser_deadly");
1110 self.cnt = particleeffectnum("laser_deadly");
1117 if(self.colormod == '0 0 0')
1119 self.colormod = '1 0 0';
1121 self.message = "saw the light";
1123 self.message2 = "was pushed into a laser by";
1126 if(!self.modelscale)
1127 self.modelscale = 1;
1128 self.think = misc_laser_think;
1129 self.nextthink = time;
1130 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1132 self.mangle = self.angles;
1134 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1138 self.reset = laser_reset;
1140 self.use = laser_use;
1146 // tZorks trigger impulse / gravity
1150 .float lastpushtime;
1152 // targeted (directional) mode
1153 void trigger_impulse_touch1()
1156 float pushdeltatime;
1159 // FIXME: Better checking for what to push and not.
1160 if not(other.iscreature)
1161 if (other.classname != "corpse")
1162 if (other.classname != "body")
1163 if (other.classname != "gib")
1164 if (other.classname != "missile")
1165 if (other.classname != "rocket")
1166 if (other.classname != "casing")
1167 if (other.classname != "grenade")
1168 if (other.classname != "plasma")
1169 if (other.classname != "plasma_prim")
1170 if (other.classname != "plasma_chain")
1171 if (other.classname != "droppedweapon")
1172 if (other.classname != "nexball_basketball")
1173 if (other.classname != "nexball_football")
1176 if (other.deadflag && other.iscreature)
1181 targ = find(world, targetname, self.target);
1184 objerror("trigger_force without a (valid) .target!\n");
1189 if(self.falloff == 1)
1190 str = (str / self.radius) * self.strength;
1191 else if(self.falloff == 2)
1192 str = (1 - (str / self.radius)) * self.strength;
1194 str = self.strength;
1196 pushdeltatime = time - other.lastpushtime;
1197 if (pushdeltatime > 0.15) pushdeltatime = 0;
1198 other.lastpushtime = time;
1199 if(!pushdeltatime) return;
1201 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1202 other.flags &~= FL_ONGROUND;
1205 // Directionless (accelerator/decelerator) mode
1206 void trigger_impulse_touch2()
1208 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 pushdeltatime = time - other.lastpushtime;
1233 if (pushdeltatime > 0.15) pushdeltatime = 0;
1234 other.lastpushtime = time;
1235 if(!pushdeltatime) return;
1237 // div0: ticrate independent, 1 = identity (not 20)
1238 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1241 // Spherical (gravity/repulsor) mode
1242 void trigger_impulse_touch3()
1244 float pushdeltatime;
1247 // FIXME: Better checking for what to push and not.
1248 if not(other.iscreature)
1249 if (other.classname != "corpse")
1250 if (other.classname != "body")
1251 if (other.classname != "gib")
1252 if (other.classname != "missile")
1253 if (other.classname != "rocket")
1254 if (other.classname != "casing")
1255 if (other.classname != "grenade")
1256 if (other.classname != "plasma")
1257 if (other.classname != "plasma_prim")
1258 if (other.classname != "plasma_chain")
1259 if (other.classname != "droppedweapon")
1260 if (other.classname != "nexball_basketball")
1261 if (other.classname != "nexball_football")
1264 if (other.deadflag && other.iscreature)
1269 pushdeltatime = time - other.lastpushtime;
1270 if (pushdeltatime > 0.15) pushdeltatime = 0;
1271 other.lastpushtime = time;
1272 if(!pushdeltatime) return;
1274 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1276 str = min(self.radius, vlen(self.origin - other.origin));
1278 if(self.falloff == 1)
1279 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1280 else if(self.falloff == 2)
1281 str = (str / self.radius) * self.strength; // 0 in the inside
1283 str = self.strength;
1285 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1288 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1289 -------- KEYS --------
1290 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1291 If not, this trigger acts like a damper/accelerator field.
1293 strength : This is how mutch force to add in the direction of .target each second
1294 when .target is set. If not, this is hoe mutch to slow down/accelerate
1295 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1297 radius : If set, act as a spherical device rather then a liniar one.
1299 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1301 -------- NOTES --------
1302 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1303 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1306 void spawnfunc_trigger_impulse()
1311 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1312 setorigin(self, self.origin);
1313 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1314 self.touch = trigger_impulse_touch3;
1320 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1321 self.touch = trigger_impulse_touch1;
1325 if(!self.strength) self.strength = 0.9;
1326 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1327 self.touch = trigger_impulse_touch2;
1332 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1333 "Flip-flop" trigger gate... lets only every second trigger event through
1337 self.state = !self.state;
1342 void spawnfunc_trigger_flipflop()
1344 if(self.spawnflags & 1)
1346 self.use = flipflop_use;
1347 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1350 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1351 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1355 self.nextthink = time + self.wait;
1356 self.enemy = activator;
1362 void monoflop_fixed_use()
1366 self.nextthink = time + self.wait;
1368 self.enemy = activator;
1372 void monoflop_think()
1375 activator = self.enemy;
1379 void monoflop_reset()
1385 void spawnfunc_trigger_monoflop()
1389 if(self.spawnflags & 1)
1390 self.use = monoflop_fixed_use;
1392 self.use = monoflop_use;
1393 self.think = monoflop_think;
1395 self.reset = monoflop_reset;
1398 void multivibrator_send()
1403 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1405 newstate = (time < cyclestart + self.wait);
1408 if(self.state != newstate)
1410 self.state = newstate;
1413 self.nextthink = cyclestart + self.wait + 0.01;
1415 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1418 void multivibrator_toggle()
1420 if(self.nextthink == 0)
1422 multivibrator_send();
1435 void multivibrator_reset()
1437 if(!(self.spawnflags & 1))
1438 self.nextthink = 0; // wait for a trigger event
1440 self.nextthink = max(1, time);
1443 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1444 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1445 -------- KEYS --------
1446 target: trigger all entities with this targetname when it goes off
1447 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1448 phase: offset of the timing
1449 wait: "on" cycle time (default: 1)
1450 respawntime: "off" cycle time (default: same as wait)
1451 -------- SPAWNFLAGS --------
1452 START_ON: assume it is already turned on (when targeted)
1454 void spawnfunc_trigger_multivibrator()
1458 if(!self.respawntime)
1459 self.respawntime = self.wait;
1462 self.use = multivibrator_toggle;
1463 self.think = multivibrator_send;
1464 self.nextthink = time;
1467 multivibrator_reset();
1476 if(self.killtarget != "")
1477 src = find(world, targetname, self.killtarget);
1478 if(self.target != "")
1479 dst = find(world, targetname, self.target);
1483 objerror("follow: could not find target/killtarget");
1489 // already done :P entity must stay
1493 else if(!src || !dst)
1495 objerror("follow: could not find target/killtarget");
1498 else if(self.spawnflags & 1)
1501 if(self.spawnflags & 2)
1503 setattachment(dst, src, self.message);
1507 attach_sameorigin(dst, src, self.message);
1514 if(self.spawnflags & 2)
1516 dst.movetype = MOVETYPE_FOLLOW;
1518 // dst.punchangle = '0 0 0'; // keep unchanged
1519 dst.view_ofs = dst.origin;
1520 dst.v_angle = dst.angles;
1524 follow_sameorigin(dst, src);
1531 void spawnfunc_misc_follow()
1533 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1538 void gamestart_use() {
1544 void spawnfunc_trigger_gamestart() {
1545 self.use = gamestart_use;
1546 self.reset2 = spawnfunc_trigger_gamestart;
1550 self.think = self.use;
1551 self.nextthink = game_starttime + self.wait;
1554 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1560 .entity voicescript; // attached voice script
1561 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1562 .float voicescript_nextthink; // time to play next voice
1563 .float voicescript_voiceend; // time when this voice ends
1565 void target_voicescript_clear(entity pl)
1567 pl.voicescript = world;
1570 void target_voicescript_use()
1572 if(activator.voicescript != self)
1574 activator.voicescript = self;
1575 activator.voicescript_index = 0;
1576 activator.voicescript_nextthink = time + self.delay;
1580 void target_voicescript_next(entity pl)
1585 vs = pl.voicescript;
1588 if(vs.message == "")
1590 if(pl.classname != "player")
1595 if(time >= pl.voicescript_voiceend)
1597 if(time >= pl.voicescript_nextthink)
1599 // get the next voice...
1600 n = tokenize_console(vs.message);
1602 if(pl.voicescript_index < vs.cnt)
1603 i = pl.voicescript_index * 2;
1604 else if(n > vs.cnt * 2)
1605 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1611 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1612 dt = stof(argv(i + 1));
1615 pl.voicescript_voiceend = time + dt;
1616 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1620 pl.voicescript_voiceend = time - dt;
1621 pl.voicescript_nextthink = pl.voicescript_voiceend;
1624 pl.voicescript_index += 1;
1628 pl.voicescript = world; // stop trying then
1634 void spawnfunc_target_voicescript()
1636 // netname: directory of the sound files
1637 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1638 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1639 // Here, a - in front of the duration means that no delay is to be
1640 // added after this message
1641 // wait: average time between messages
1642 // delay: initial delay before the first message
1645 self.use = target_voicescript_use;
1647 n = tokenize_console(self.message);
1649 for(i = 0; i+1 < n; i += 2)
1656 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1662 void trigger_relay_teamcheck_use()
1666 if(self.spawnflags & 2)
1668 if(activator.team != self.team)
1673 if(activator.team == self.team)
1679 if(self.spawnflags & 1)
1684 void trigger_relay_teamcheck_reset()
1686 self.team = self.team_saved;
1689 void spawnfunc_trigger_relay_teamcheck()
1691 self.team_saved = self.team;
1692 self.use = trigger_relay_teamcheck_use;
1693 self.reset = trigger_relay_teamcheck_reset;
1698 void trigger_disablerelay_use()
1705 for(e = world; (e = find(e, targetname, self.target)); )
1707 if(e.use == SUB_UseTargets)
1709 e.use = SUB_DontUseTargets;
1712 else if(e.use == SUB_DontUseTargets)
1714 e.use = SUB_UseTargets;
1720 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1723 void spawnfunc_trigger_disablerelay()
1725 self.use = trigger_disablerelay_use;
1728 float magicear_matched;
1729 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1731 float domatch, dotrigger, matchstart, l;
1735 magicear_matched = FALSE;
1737 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1738 domatch = ((ear.spawnflags & 32) || dotrigger);
1744 if(ear.spawnflags & 4)
1750 if(ear.spawnflags & 1)
1753 if(ear.spawnflags & 2)
1756 if(ear.spawnflags & 8)
1761 l = strlen(ear.message);
1763 if(self.spawnflags & 128)
1766 msg = strdecolorize(msgin);
1768 if(substring(ear.message, 0, 1) == "*")
1770 if(substring(ear.message, -1, 1) == "*")
1773 // as we need multi-replacement here...
1774 s = substring(ear.message, 1, -2);
1776 if(strstrofs(msg, s, 0) >= 0)
1777 matchstart = -2; // we use strreplace on s
1782 s = substring(ear.message, 1, -1);
1784 if(substring(msg, -l, l) == s)
1785 matchstart = strlen(msg) - l;
1790 if(substring(ear.message, -1, 1) == "*")
1793 s = substring(ear.message, 0, -2);
1795 if(substring(msg, 0, l) == s)
1802 if(msg == ear.message)
1807 if(matchstart == -1) // no match
1810 magicear_matched = TRUE;
1814 oldself = activator = self;
1820 if(ear.spawnflags & 16)
1824 else if(ear.netname != "")
1827 return strreplace(s, ear.netname, msg);
1830 substring(msg, 0, matchstart),
1832 substring(msg, matchstart + l, -1)
1840 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1844 for(ear = magicears; ear; ear = ear.enemy)
1846 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1847 if not(ear.spawnflags & 64)
1848 if(magicear_matched)
1855 void spawnfunc_trigger_magicear()
1857 self.enemy = magicears;
1860 // actually handled in "say" processing
1863 // 2 = ignore teamsay
1865 // 8 = ignore tell to unknown player
1866 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1867 // 32 = perform the replacement even if outside the radius or dead
1868 // 64 = continue replacing/triggering even if this one matched
1878 // if set, replacement for the matched text
1880 // "hearing distance"