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(!other.trigger_gravity_active)
562 other.trigger_gravity_active = 1;
563 other.trigger_gravity_check = spawn();
564 other.trigger_gravity_check.owner = other;
565 other.trigger_gravity_check.think = trigger_gravity_check_think;
566 other.trigger_gravity_check.nextthink = time;
568 other.trigger_gravity_check.cnt = 2;
570 if (other.gravity != self.gravity)
572 other.gravity = self.gravity;
574 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
578 void spawnfunc_trigger_gravity()
583 self.touch = trigger_gravity_touch;
585 precache_sound(self.noise);
588 // TODO add a way to do looped sounds with sound(); then complete this entity
589 .float volume, atten;
590 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
592 void spawnfunc_target_speaker()
595 precache_sound (self.noise);
599 self.atten = ATTN_NORM;
600 else if(self.atten < 0)
604 self.use = target_speaker_use;
609 self.atten = ATTN_STATIC;
610 else if(self.atten < 0)
614 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
619 void spawnfunc_func_stardust() {
620 self.effects = EF_STARDUST;
624 .float bgmscriptattack;
625 .float bgmscriptdecay;
626 .float bgmscriptsustain;
627 .float bgmscriptrelease;
628 float pointparticles_SendEntity(entity to, float fl)
630 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
632 // optional features to save space
634 if(self.spawnflags & 2)
635 fl |= 0x10; // absolute count on toggle-on
636 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
637 fl |= 0x20; // 4 bytes - saves CPU
638 if(self.waterlevel || self.count != 1)
639 fl |= 0x40; // 4 bytes - obscure features almost never used
640 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
641 fl |= 0x80; // 14 bytes - saves lots of space
643 WriteByte(MSG_ENTITY, fl);
647 WriteCoord(MSG_ENTITY, self.impulse);
649 WriteCoord(MSG_ENTITY, 0); // off
653 WriteCoord(MSG_ENTITY, self.origin_x);
654 WriteCoord(MSG_ENTITY, self.origin_y);
655 WriteCoord(MSG_ENTITY, self.origin_z);
659 if(self.model != "null")
661 WriteShort(MSG_ENTITY, self.modelindex);
664 WriteCoord(MSG_ENTITY, self.mins_x);
665 WriteCoord(MSG_ENTITY, self.mins_y);
666 WriteCoord(MSG_ENTITY, self.mins_z);
667 WriteCoord(MSG_ENTITY, self.maxs_x);
668 WriteCoord(MSG_ENTITY, self.maxs_y);
669 WriteCoord(MSG_ENTITY, self.maxs_z);
674 WriteShort(MSG_ENTITY, 0);
677 WriteCoord(MSG_ENTITY, self.maxs_x);
678 WriteCoord(MSG_ENTITY, self.maxs_y);
679 WriteCoord(MSG_ENTITY, self.maxs_z);
682 WriteShort(MSG_ENTITY, self.cnt);
685 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
686 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
690 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
691 WriteByte(MSG_ENTITY, self.count * 16.0);
693 WriteString(MSG_ENTITY, self.noise);
696 WriteByte(MSG_ENTITY, floor(self.atten * 64));
697 WriteByte(MSG_ENTITY, floor(self.volume * 255));
699 WriteString(MSG_ENTITY, self.bgmscript);
700 if(self.bgmscript != "")
702 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
703 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
704 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
705 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
711 void pointparticles_use()
713 self.state = !self.state;
717 void pointparticles_think()
719 if(self.origin != self.oldorigin)
722 self.oldorigin = self.origin;
724 self.nextthink = time;
727 void pointparticles_reset()
729 if(self.spawnflags & 1)
735 void spawnfunc_func_pointparticles()
738 setmodel(self, self.model);
740 precache_sound (self.noise);
742 if(!self.bgmscriptsustain)
743 self.bgmscriptsustain = 1;
744 else if(self.bgmscriptsustain < 0)
745 self.bgmscriptsustain = 0;
748 self.atten = ATTN_NORM;
749 else if(self.atten < 0)
760 setorigin(self, self.origin + self.mins);
761 setsize(self, '0 0 0', self.maxs - self.mins);
764 self.cnt = particleeffectnum(self.mdl);
766 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
770 self.use = pointparticles_use;
771 self.reset = pointparticles_reset;
776 self.think = pointparticles_think;
777 self.nextthink = time;
780 void spawnfunc_func_sparks()
782 // self.cnt is the amount of sparks that one burst will spawn
784 self.cnt = 25.0; // nice default value
787 // self.wait is the probability that a sparkthink will spawn a spark shower
788 // range: 0 - 1, but 0 makes little sense, so...
789 if(self.wait < 0.05) {
790 self.wait = 0.25; // nice default value
793 self.count = self.cnt;
796 self.velocity = '0 0 -1';
797 self.mdl = "TE_SPARK";
798 self.impulse = 10 * self.wait; // by default 2.5/sec
800 self.cnt = 0; // use mdl
802 spawnfunc_func_pointparticles();
805 float rainsnow_SendEntity(entity to, float sf)
807 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
808 WriteByte(MSG_ENTITY, self.state);
809 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
810 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
811 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
812 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
813 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
814 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
815 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
816 WriteShort(MSG_ENTITY, self.count);
817 WriteByte(MSG_ENTITY, self.cnt);
821 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
822 This is an invisible area like a trigger, which rain falls inside of.
826 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
828 sets color of rain (default 12 - white)
830 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
832 void spawnfunc_func_rain()
834 self.dest = self.velocity;
835 self.velocity = '0 0 0';
837 self.dest = '0 0 -700';
838 self.angles = '0 0 0';
839 self.movetype = MOVETYPE_NONE;
840 self.solid = SOLID_NOT;
841 SetBrushEntityModel();
846 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
849 if(self.count > 65535)
852 self.state = 1; // 1 is rain, 0 is snow
855 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
859 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
860 This is an invisible area like a trigger, which snow falls inside of.
864 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
866 sets color of rain (default 12 - white)
868 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
870 void spawnfunc_func_snow()
872 self.dest = self.velocity;
873 self.velocity = '0 0 0';
875 self.dest = '0 0 -300';
876 self.angles = '0 0 0';
877 self.movetype = MOVETYPE_NONE;
878 self.solid = SOLID_NOT;
879 SetBrushEntityModel();
884 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
887 if(self.count > 65535)
890 self.state = 0; // 1 is rain, 0 is snow
893 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
897 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
900 void misc_laser_aim()
905 if(self.spawnflags & 2)
907 if(self.enemy.origin != self.mangle)
909 self.mangle = self.enemy.origin;
915 a = vectoangles(self.enemy.origin - self.origin);
926 if(self.angles != self.mangle)
928 self.mangle = self.angles;
932 if(self.origin != self.oldorigin)
935 self.oldorigin = self.origin;
939 void misc_laser_init()
941 if(self.target != "")
942 self.enemy = find(world, targetname, self.target);
946 void misc_laser_think()
951 self.nextthink = time;
960 o = self.enemy.origin;
961 if not(self.spawnflags & 2)
962 o = self.origin + normalize(o - self.origin) * 32768;
966 makevectors(self.mangle);
967 o = self.origin + v_forward * 32768;
973 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
975 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
978 if(self.enemy.target != "") // DETECTOR laser
980 traceline(self.origin, o, MOVE_NORMAL, self);
981 if(trace_ent.iscreature)
983 self.pusher = trace_ent;
990 activator = self.pusher;
1003 activator = self.pusher;
1011 float laser_SendEntity(entity to, float fl)
1013 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1014 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1015 if(self.spawnflags & 2)
1019 if(self.scale != 1 || self.modelscale != 1)
1021 WriteByte(MSG_ENTITY, fl);
1024 WriteCoord(MSG_ENTITY, self.origin_x);
1025 WriteCoord(MSG_ENTITY, self.origin_y);
1026 WriteCoord(MSG_ENTITY, self.origin_z);
1030 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1031 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1032 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1034 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1037 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1038 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1040 WriteShort(MSG_ENTITY, self.cnt + 1);
1046 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1047 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1048 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1052 WriteAngle(MSG_ENTITY, self.mangle_x);
1053 WriteAngle(MSG_ENTITY, self.mangle_y);
1057 WriteByte(MSG_ENTITY, self.state);
1061 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1062 Any object touching the beam will be hurt
1065 spawnfunc_target_position where the laser ends
1067 name of beam end effect to use
1069 color of the beam (default: red)
1071 damage per second (-1 for a laser that kills immediately)
1075 self.state = !self.state;
1076 self.SendFlags |= 4;
1082 if(self.spawnflags & 1)
1088 void spawnfunc_misc_laser()
1092 if(self.mdl == "none")
1096 self.cnt = particleeffectnum(self.mdl);
1099 self.cnt = particleeffectnum("laser_deadly");
1105 self.cnt = particleeffectnum("laser_deadly");
1112 if(self.colormod == '0 0 0')
1114 self.colormod = '1 0 0';
1116 self.message = "saw the light";
1118 self.message2 = "was pushed into a laser by";
1121 if(!self.modelscale)
1122 self.modelscale = 1;
1123 self.think = misc_laser_think;
1124 self.nextthink = time;
1125 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1127 self.mangle = self.angles;
1129 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1133 self.reset = laser_reset;
1135 self.use = laser_use;
1141 // tZorks trigger impulse / gravity
1145 .float lastpushtime;
1147 // targeted (directional) mode
1148 void trigger_impulse_touch1()
1151 float pushdeltatime;
1154 // FIXME: Better checking for what to push and not.
1155 if not(other.iscreature)
1156 if (other.classname != "corpse")
1157 if (other.classname != "body")
1158 if (other.classname != "gib")
1159 if (other.classname != "missile")
1160 if (other.classname != "rocket")
1161 if (other.classname != "casing")
1162 if (other.classname != "grenade")
1163 if (other.classname != "plasma")
1164 if (other.classname != "plasma_prim")
1165 if (other.classname != "plasma_chain")
1166 if (other.classname != "droppedweapon")
1167 if (other.classname != "nexball_basketball")
1168 if (other.classname != "nexball_football")
1171 if (other.deadflag && other.iscreature)
1176 targ = find(world, targetname, self.target);
1179 objerror("trigger_force without a (valid) .target!\n");
1184 if(self.falloff == 1)
1185 str = (str / self.radius) * self.strength;
1186 else if(self.falloff == 2)
1187 str = (1 - (str / self.radius)) * self.strength;
1189 str = self.strength;
1191 pushdeltatime = time - other.lastpushtime;
1192 if (pushdeltatime > 0.15) pushdeltatime = 0;
1193 other.lastpushtime = time;
1194 if(!pushdeltatime) return;
1196 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1197 other.flags &~= FL_ONGROUND;
1200 // Directionless (accelerator/decelerator) mode
1201 void trigger_impulse_touch2()
1203 float pushdeltatime;
1205 // FIXME: Better checking for what to push and not.
1206 if not(other.iscreature)
1207 if (other.classname != "corpse")
1208 if (other.classname != "body")
1209 if (other.classname != "gib")
1210 if (other.classname != "missile")
1211 if (other.classname != "rocket")
1212 if (other.classname != "casing")
1213 if (other.classname != "grenade")
1214 if (other.classname != "plasma")
1215 if (other.classname != "plasma_prim")
1216 if (other.classname != "plasma_chain")
1217 if (other.classname != "droppedweapon")
1218 if (other.classname != "nexball_basketball")
1219 if (other.classname != "nexball_football")
1222 if (other.deadflag && other.iscreature)
1227 pushdeltatime = time - other.lastpushtime;
1228 if (pushdeltatime > 0.15) pushdeltatime = 0;
1229 other.lastpushtime = time;
1230 if(!pushdeltatime) return;
1232 // div0: ticrate independent, 1 = identity (not 20)
1233 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1236 // Spherical (gravity/repulsor) mode
1237 void trigger_impulse_touch3()
1239 float pushdeltatime;
1242 // FIXME: Better checking for what to push and not.
1243 if not(other.iscreature)
1244 if (other.classname != "corpse")
1245 if (other.classname != "body")
1246 if (other.classname != "gib")
1247 if (other.classname != "missile")
1248 if (other.classname != "rocket")
1249 if (other.classname != "casing")
1250 if (other.classname != "grenade")
1251 if (other.classname != "plasma")
1252 if (other.classname != "plasma_prim")
1253 if (other.classname != "plasma_chain")
1254 if (other.classname != "droppedweapon")
1255 if (other.classname != "nexball_basketball")
1256 if (other.classname != "nexball_football")
1259 if (other.deadflag && other.iscreature)
1264 pushdeltatime = time - other.lastpushtime;
1265 if (pushdeltatime > 0.15) pushdeltatime = 0;
1266 other.lastpushtime = time;
1267 if(!pushdeltatime) return;
1269 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1271 str = min(self.radius, vlen(self.origin - other.origin));
1273 if(self.falloff == 1)
1274 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1275 else if(self.falloff == 2)
1276 str = (str / self.radius) * self.strength; // 0 in the inside
1278 str = self.strength;
1280 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1283 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1284 -------- KEYS --------
1285 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1286 If not, this trigger acts like a damper/accelerator field.
1288 strength : This is how mutch force to add in the direction of .target each second
1289 when .target is set. If not, this is hoe mutch to slow down/accelerate
1290 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1292 radius : If set, act as a spherical device rather then a liniar one.
1294 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1296 -------- NOTES --------
1297 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1298 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1301 void spawnfunc_trigger_impulse()
1306 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1307 setorigin(self, self.origin);
1308 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1309 self.touch = trigger_impulse_touch3;
1315 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1316 self.touch = trigger_impulse_touch1;
1320 if(!self.strength) self.strength = 0.9;
1321 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1322 self.touch = trigger_impulse_touch2;
1327 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1328 "Flip-flop" trigger gate... lets only every second trigger event through
1332 self.state = !self.state;
1337 void spawnfunc_trigger_flipflop()
1339 if(self.spawnflags & 1)
1341 self.use = flipflop_use;
1342 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1345 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1346 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1350 self.nextthink = time + self.wait;
1351 self.enemy = activator;
1357 void monoflop_fixed_use()
1361 self.nextthink = time + self.wait;
1363 self.enemy = activator;
1367 void monoflop_think()
1370 activator = self.enemy;
1374 void monoflop_reset()
1380 void spawnfunc_trigger_monoflop()
1384 if(self.spawnflags & 1)
1385 self.use = monoflop_fixed_use;
1387 self.use = monoflop_use;
1388 self.think = monoflop_think;
1390 self.reset = monoflop_reset;
1393 void multivibrator_send()
1398 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1400 newstate = (time < cyclestart + self.wait);
1403 if(self.state != newstate)
1405 self.state = newstate;
1408 self.nextthink = cyclestart + self.wait + 0.01;
1410 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1413 void multivibrator_toggle()
1415 if(self.nextthink == 0)
1417 multivibrator_send();
1430 void multivibrator_reset()
1432 if(!(self.spawnflags & 1))
1433 self.nextthink = 0; // wait for a trigger event
1435 self.nextthink = max(1, time);
1438 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1439 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1440 -------- KEYS --------
1441 target: trigger all entities with this targetname when it goes off
1442 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1443 phase: offset of the timing
1444 wait: "on" cycle time (default: 1)
1445 respawntime: "off" cycle time (default: same as wait)
1446 -------- SPAWNFLAGS --------
1447 START_ON: assume it is already turned on (when targeted)
1449 void spawnfunc_trigger_multivibrator()
1453 if(!self.respawntime)
1454 self.respawntime = self.wait;
1457 self.use = multivibrator_toggle;
1458 self.think = multivibrator_send;
1459 self.nextthink = time;
1462 multivibrator_reset();
1471 if(self.killtarget != "")
1472 src = find(world, targetname, self.killtarget);
1473 if(self.target != "")
1474 dst = find(world, targetname, self.target);
1478 objerror("follow: could not find target/killtarget");
1484 // already done :P entity must stay
1488 else if(!src || !dst)
1490 objerror("follow: could not find target/killtarget");
1493 else if(self.spawnflags & 1)
1496 if(self.spawnflags & 2)
1498 setattachment(dst, src, self.message);
1502 attach_sameorigin(dst, src, self.message);
1509 if(self.spawnflags & 2)
1511 dst.movetype = MOVETYPE_FOLLOW;
1513 // dst.punchangle = '0 0 0'; // keep unchanged
1514 dst.view_ofs = dst.origin;
1515 dst.v_angle = dst.angles;
1519 follow_sameorigin(dst, src);
1526 void spawnfunc_misc_follow()
1528 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1533 void gamestart_use() {
1539 void spawnfunc_trigger_gamestart() {
1540 self.use = gamestart_use;
1541 self.reset2 = spawnfunc_trigger_gamestart;
1545 self.think = self.use;
1546 self.nextthink = game_starttime + self.wait;
1549 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1555 .entity voicescript; // attached voice script
1556 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1557 .float voicescript_nextthink; // time to play next voice
1558 .float voicescript_voiceend; // time when this voice ends
1560 void target_voicescript_clear(entity pl)
1562 pl.voicescript = world;
1565 void target_voicescript_use()
1567 if(activator.voicescript != self)
1569 activator.voicescript = self;
1570 activator.voicescript_index = 0;
1571 activator.voicescript_nextthink = time + self.delay;
1575 void target_voicescript_next(entity pl)
1580 vs = pl.voicescript;
1583 if(vs.message == "")
1585 if(pl.classname != "player")
1590 if(time >= pl.voicescript_voiceend)
1592 if(time >= pl.voicescript_nextthink)
1594 // get the next voice...
1595 n = tokenize_console(vs.message);
1597 if(pl.voicescript_index < vs.cnt)
1598 i = pl.voicescript_index * 2;
1599 else if(n > vs.cnt * 2)
1600 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1606 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1607 dt = stof(argv(i + 1));
1610 pl.voicescript_voiceend = time + dt;
1611 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1615 pl.voicescript_voiceend = time - dt;
1616 pl.voicescript_nextthink = pl.voicescript_voiceend;
1619 pl.voicescript_index += 1;
1623 pl.voicescript = world; // stop trying then
1629 void spawnfunc_target_voicescript()
1631 // netname: directory of the sound files
1632 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1633 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1634 // Here, a - in front of the duration means that no delay is to be
1635 // added after this message
1636 // wait: average time between messages
1637 // delay: initial delay before the first message
1640 self.use = target_voicescript_use;
1642 n = tokenize_console(self.message);
1644 for(i = 0; i+1 < n; i += 2)
1651 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1657 void trigger_relay_teamcheck_use()
1661 if(self.spawnflags & 2)
1663 if(activator.team != self.team)
1668 if(activator.team == self.team)
1674 if(self.spawnflags & 1)
1679 void trigger_relay_teamcheck_reset()
1681 self.team = self.team_saved;
1684 void spawnfunc_trigger_relay_teamcheck()
1686 self.team_saved = self.team;
1687 self.use = trigger_relay_teamcheck_use;
1688 self.reset = trigger_relay_teamcheck_reset;
1693 void trigger_disablerelay_use()
1700 for(e = world; (e = find(e, targetname, self.target)); )
1702 if(e.use == SUB_UseTargets)
1704 e.use = SUB_DontUseTargets;
1707 else if(e.use == SUB_DontUseTargets)
1709 e.use = SUB_UseTargets;
1715 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1718 void spawnfunc_trigger_disablerelay()
1720 self.use = trigger_disablerelay_use;
1723 float magicear_matched;
1724 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1726 float domatch, dotrigger, matchstart, l;
1730 magicear_matched = FALSE;
1732 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1733 domatch = ((ear.spawnflags & 32) || dotrigger);
1739 if(ear.spawnflags & 4)
1745 if(ear.spawnflags & 1)
1748 if(ear.spawnflags & 2)
1751 if(ear.spawnflags & 8)
1756 l = strlen(ear.message);
1758 if(self.spawnflags & 128)
1761 msg = strdecolorize(msgin);
1763 if(substring(ear.message, 0, 1) == "*")
1765 if(substring(ear.message, -1, 1) == "*")
1768 // as we need multi-replacement here...
1769 s = substring(ear.message, 1, -2);
1771 if(strstrofs(msg, s, 0) >= 0)
1772 matchstart = -2; // we use strreplace on s
1777 s = substring(ear.message, 1, -1);
1779 if(substring(msg, -l, l) == s)
1780 matchstart = strlen(msg) - l;
1785 if(substring(ear.message, -1, 1) == "*")
1788 s = substring(ear.message, 0, -2);
1790 if(substring(msg, 0, l) == s)
1797 if(msg == ear.message)
1802 if(matchstart == -1) // no match
1805 magicear_matched = TRUE;
1809 oldself = activator = self;
1815 if(ear.spawnflags & 16)
1819 else if(ear.netname != "")
1822 return strreplace(s, ear.netname, msg);
1825 substring(msg, 0, matchstart),
1827 substring(msg, matchstart + l, -1)
1835 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1839 for(ear = magicears; ear; ear = ear.enemy)
1841 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1842 if not(ear.spawnflags & 64)
1843 if(magicear_matched)
1850 void spawnfunc_trigger_magicear()
1852 self.enemy = magicears;
1855 // actually handled in "say" processing
1858 // 2 = ignore teamsay
1860 // 8 = ignore tell to unknown player
1861 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1862 // 32 = perform the replacement even if outside the radius or dead
1863 // 64 = continue replacing/triggering even if this one matched
1873 // if set, replacement for the matched text
1875 // "hearing distance"