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 triggergravity;
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 dprint("XXXXXXXXXXXXXXXXXXXXXXXXXX "); // temporary execution test
546 self.owner.gravity = 0;
547 self.owner.triggergravity = 0;
553 self.nextthink = time;
557 void trigger_gravity_touch()
561 if(!other.triggergravity)
563 other.triggergravity = 1;
564 other.trigger_gravity_check = spawn();
565 other.trigger_gravity_check.owner = other;
566 other.trigger_gravity_check.think = trigger_gravity_check_think;
567 other.trigger_gravity_check.nextthink = time;
569 other.trigger_gravity_check.cnt = 2;
571 if (other.gravity != self.gravity)
573 other.gravity = self.gravity;
575 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
579 void spawnfunc_trigger_gravity()
584 self.touch = trigger_gravity_touch;
586 precache_sound(self.noise);
589 // TODO add a way to do looped sounds with sound(); then complete this entity
590 .float volume, atten;
591 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
593 void spawnfunc_target_speaker()
596 precache_sound (self.noise);
600 self.atten = ATTN_NORM;
601 else if(self.atten < 0)
605 self.use = target_speaker_use;
610 self.atten = ATTN_STATIC;
611 else if(self.atten < 0)
615 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
620 void spawnfunc_func_stardust() {
621 self.effects = EF_STARDUST;
625 .float bgmscriptattack;
626 .float bgmscriptdecay;
627 .float bgmscriptsustain;
628 .float bgmscriptrelease;
629 float pointparticles_SendEntity(entity to, float fl)
631 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
633 // optional features to save space
635 if(self.spawnflags & 2)
636 fl |= 0x10; // absolute count on toggle-on
637 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
638 fl |= 0x20; // 4 bytes - saves CPU
639 if(self.waterlevel || self.count != 1)
640 fl |= 0x40; // 4 bytes - obscure features almost never used
641 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
642 fl |= 0x80; // 14 bytes - saves lots of space
644 WriteByte(MSG_ENTITY, fl);
648 WriteCoord(MSG_ENTITY, self.impulse);
650 WriteCoord(MSG_ENTITY, 0); // off
654 WriteCoord(MSG_ENTITY, self.origin_x);
655 WriteCoord(MSG_ENTITY, self.origin_y);
656 WriteCoord(MSG_ENTITY, self.origin_z);
660 if(self.model != "null")
662 WriteShort(MSG_ENTITY, self.modelindex);
665 WriteCoord(MSG_ENTITY, self.mins_x);
666 WriteCoord(MSG_ENTITY, self.mins_y);
667 WriteCoord(MSG_ENTITY, self.mins_z);
668 WriteCoord(MSG_ENTITY, self.maxs_x);
669 WriteCoord(MSG_ENTITY, self.maxs_y);
670 WriteCoord(MSG_ENTITY, self.maxs_z);
675 WriteShort(MSG_ENTITY, 0);
678 WriteCoord(MSG_ENTITY, self.maxs_x);
679 WriteCoord(MSG_ENTITY, self.maxs_y);
680 WriteCoord(MSG_ENTITY, self.maxs_z);
683 WriteShort(MSG_ENTITY, self.cnt);
686 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
687 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
691 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
692 WriteByte(MSG_ENTITY, self.count * 16.0);
694 WriteString(MSG_ENTITY, self.noise);
697 WriteByte(MSG_ENTITY, floor(self.atten * 64));
698 WriteByte(MSG_ENTITY, floor(self.volume * 255));
700 WriteString(MSG_ENTITY, self.bgmscript);
701 if(self.bgmscript != "")
703 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
704 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
705 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
706 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
712 void pointparticles_use()
714 self.state = !self.state;
718 void pointparticles_think()
720 if(self.origin != self.oldorigin)
723 self.oldorigin = self.origin;
725 self.nextthink = time;
728 void pointparticles_reset()
730 if(self.spawnflags & 1)
736 void spawnfunc_func_pointparticles()
739 setmodel(self, self.model);
741 precache_sound (self.noise);
743 if(!self.bgmscriptsustain)
744 self.bgmscriptsustain = 1;
745 else if(self.bgmscriptsustain < 0)
746 self.bgmscriptsustain = 0;
749 self.atten = ATTN_NORM;
750 else if(self.atten < 0)
761 setorigin(self, self.origin + self.mins);
762 setsize(self, '0 0 0', self.maxs - self.mins);
765 self.cnt = particleeffectnum(self.mdl);
767 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
771 self.use = pointparticles_use;
772 self.reset = pointparticles_reset;
777 self.think = pointparticles_think;
778 self.nextthink = time;
781 void spawnfunc_func_sparks()
783 // self.cnt is the amount of sparks that one burst will spawn
785 self.cnt = 25.0; // nice default value
788 // self.wait is the probability that a sparkthink will spawn a spark shower
789 // range: 0 - 1, but 0 makes little sense, so...
790 if(self.wait < 0.05) {
791 self.wait = 0.25; // nice default value
794 self.count = self.cnt;
797 self.velocity = '0 0 -1';
798 self.mdl = "TE_SPARK";
799 self.impulse = 10 * self.wait; // by default 2.5/sec
801 self.cnt = 0; // use mdl
803 spawnfunc_func_pointparticles();
806 float rainsnow_SendEntity(entity to, float sf)
808 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
809 WriteByte(MSG_ENTITY, self.state);
810 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
811 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
812 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
813 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
814 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
815 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
816 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
817 WriteShort(MSG_ENTITY, self.count);
818 WriteByte(MSG_ENTITY, self.cnt);
822 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
823 This is an invisible area like a trigger, which rain falls inside of.
827 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
829 sets color of rain (default 12 - white)
831 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
833 void spawnfunc_func_rain()
835 self.dest = self.velocity;
836 self.velocity = '0 0 0';
838 self.dest = '0 0 -700';
839 self.angles = '0 0 0';
840 self.movetype = MOVETYPE_NONE;
841 self.solid = SOLID_NOT;
842 SetBrushEntityModel();
847 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
850 if(self.count > 65535)
853 self.state = 1; // 1 is rain, 0 is snow
856 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
860 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
861 This is an invisible area like a trigger, which snow falls inside of.
865 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
867 sets color of rain (default 12 - white)
869 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
871 void spawnfunc_func_snow()
873 self.dest = self.velocity;
874 self.velocity = '0 0 0';
876 self.dest = '0 0 -300';
877 self.angles = '0 0 0';
878 self.movetype = MOVETYPE_NONE;
879 self.solid = SOLID_NOT;
880 SetBrushEntityModel();
885 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
888 if(self.count > 65535)
891 self.state = 0; // 1 is rain, 0 is snow
894 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
898 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
901 void misc_laser_aim()
906 if(self.spawnflags & 2)
908 if(self.enemy.origin != self.mangle)
910 self.mangle = self.enemy.origin;
916 a = vectoangles(self.enemy.origin - self.origin);
927 if(self.angles != self.mangle)
929 self.mangle = self.angles;
933 if(self.origin != self.oldorigin)
936 self.oldorigin = self.origin;
940 void misc_laser_init()
942 if(self.target != "")
943 self.enemy = find(world, targetname, self.target);
947 void misc_laser_think()
952 self.nextthink = time;
961 o = self.enemy.origin;
962 if not(self.spawnflags & 2)
963 o = self.origin + normalize(o - self.origin) * 32768;
967 makevectors(self.mangle);
968 o = self.origin + v_forward * 32768;
974 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
976 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
979 if(self.enemy.target != "") // DETECTOR laser
981 traceline(self.origin, o, MOVE_NORMAL, self);
982 if(trace_ent.iscreature)
984 self.pusher = trace_ent;
991 activator = self.pusher;
1004 activator = self.pusher;
1012 float laser_SendEntity(entity to, float fl)
1014 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1015 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1016 if(self.spawnflags & 2)
1020 if(self.scale != 1 || self.modelscale != 1)
1022 WriteByte(MSG_ENTITY, fl);
1025 WriteCoord(MSG_ENTITY, self.origin_x);
1026 WriteCoord(MSG_ENTITY, self.origin_y);
1027 WriteCoord(MSG_ENTITY, self.origin_z);
1031 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1032 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1033 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1035 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1038 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1039 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1041 WriteShort(MSG_ENTITY, self.cnt + 1);
1047 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1048 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1049 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1053 WriteAngle(MSG_ENTITY, self.mangle_x);
1054 WriteAngle(MSG_ENTITY, self.mangle_y);
1058 WriteByte(MSG_ENTITY, self.state);
1062 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1063 Any object touching the beam will be hurt
1066 spawnfunc_target_position where the laser ends
1068 name of beam end effect to use
1070 color of the beam (default: red)
1072 damage per second (-1 for a laser that kills immediately)
1076 self.state = !self.state;
1077 self.SendFlags |= 4;
1083 if(self.spawnflags & 1)
1089 void spawnfunc_misc_laser()
1093 if(self.mdl == "none")
1097 self.cnt = particleeffectnum(self.mdl);
1100 self.cnt = particleeffectnum("laser_deadly");
1106 self.cnt = particleeffectnum("laser_deadly");
1113 if(self.colormod == '0 0 0')
1115 self.colormod = '1 0 0';
1117 self.message = "saw the light";
1119 self.message2 = "was pushed into a laser by";
1122 if(!self.modelscale)
1123 self.modelscale = 1;
1124 self.think = misc_laser_think;
1125 self.nextthink = time;
1126 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1128 self.mangle = self.angles;
1130 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1134 self.reset = laser_reset;
1136 self.use = laser_use;
1142 // tZorks trigger impulse / gravity
1146 .float lastpushtime;
1148 // targeted (directional) mode
1149 void trigger_impulse_touch1()
1152 float pushdeltatime;
1155 // FIXME: Better checking for what to push and not.
1156 if not(other.iscreature)
1157 if (other.classname != "corpse")
1158 if (other.classname != "body")
1159 if (other.classname != "gib")
1160 if (other.classname != "missile")
1161 if (other.classname != "rocket")
1162 if (other.classname != "casing")
1163 if (other.classname != "grenade")
1164 if (other.classname != "plasma")
1165 if (other.classname != "plasma_prim")
1166 if (other.classname != "plasma_chain")
1167 if (other.classname != "droppedweapon")
1168 if (other.classname != "nexball_basketball")
1169 if (other.classname != "nexball_football")
1172 if (other.deadflag && other.iscreature)
1177 targ = find(world, targetname, self.target);
1180 objerror("trigger_force without a (valid) .target!\n");
1185 if(self.falloff == 1)
1186 str = (str / self.radius) * self.strength;
1187 else if(self.falloff == 2)
1188 str = (1 - (str / self.radius)) * self.strength;
1190 str = self.strength;
1192 pushdeltatime = time - other.lastpushtime;
1193 if (pushdeltatime > 0.15) pushdeltatime = 0;
1194 other.lastpushtime = time;
1195 if(!pushdeltatime) return;
1197 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1198 other.flags &~= FL_ONGROUND;
1201 // Directionless (accelerator/decelerator) mode
1202 void trigger_impulse_touch2()
1204 float pushdeltatime;
1206 // FIXME: Better checking for what to push and not.
1207 if not(other.iscreature)
1208 if (other.classname != "corpse")
1209 if (other.classname != "body")
1210 if (other.classname != "gib")
1211 if (other.classname != "missile")
1212 if (other.classname != "rocket")
1213 if (other.classname != "casing")
1214 if (other.classname != "grenade")
1215 if (other.classname != "plasma")
1216 if (other.classname != "plasma_prim")
1217 if (other.classname != "plasma_chain")
1218 if (other.classname != "droppedweapon")
1219 if (other.classname != "nexball_basketball")
1220 if (other.classname != "nexball_football")
1223 if (other.deadflag && other.iscreature)
1228 pushdeltatime = time - other.lastpushtime;
1229 if (pushdeltatime > 0.15) pushdeltatime = 0;
1230 other.lastpushtime = time;
1231 if(!pushdeltatime) return;
1233 // div0: ticrate independent, 1 = identity (not 20)
1234 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1237 // Spherical (gravity/repulsor) mode
1238 void trigger_impulse_touch3()
1240 float pushdeltatime;
1243 // FIXME: Better checking for what to push and not.
1244 if not(other.iscreature)
1245 if (other.classname != "corpse")
1246 if (other.classname != "body")
1247 if (other.classname != "gib")
1248 if (other.classname != "missile")
1249 if (other.classname != "rocket")
1250 if (other.classname != "casing")
1251 if (other.classname != "grenade")
1252 if (other.classname != "plasma")
1253 if (other.classname != "plasma_prim")
1254 if (other.classname != "plasma_chain")
1255 if (other.classname != "droppedweapon")
1256 if (other.classname != "nexball_basketball")
1257 if (other.classname != "nexball_football")
1260 if (other.deadflag && other.iscreature)
1265 pushdeltatime = time - other.lastpushtime;
1266 if (pushdeltatime > 0.15) pushdeltatime = 0;
1267 other.lastpushtime = time;
1268 if(!pushdeltatime) return;
1270 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1272 str = min(self.radius, vlen(self.origin - other.origin));
1274 if(self.falloff == 1)
1275 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1276 else if(self.falloff == 2)
1277 str = (str / self.radius) * self.strength; // 0 in the inside
1279 str = self.strength;
1281 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1284 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1285 -------- KEYS --------
1286 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1287 If not, this trigger acts like a damper/accelerator field.
1289 strength : This is how mutch force to add in the direction of .target each second
1290 when .target is set. If not, this is hoe mutch to slow down/accelerate
1291 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1293 radius : If set, act as a spherical device rather then a liniar one.
1295 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1297 -------- NOTES --------
1298 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1299 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1302 void spawnfunc_trigger_impulse()
1307 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1308 setorigin(self, self.origin);
1309 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1310 self.touch = trigger_impulse_touch3;
1316 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1317 self.touch = trigger_impulse_touch1;
1321 if(!self.strength) self.strength = 0.9;
1322 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1323 self.touch = trigger_impulse_touch2;
1328 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1329 "Flip-flop" trigger gate... lets only every second trigger event through
1333 self.state = !self.state;
1338 void spawnfunc_trigger_flipflop()
1340 if(self.spawnflags & 1)
1342 self.use = flipflop_use;
1343 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1346 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1347 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1351 self.nextthink = time + self.wait;
1352 self.enemy = activator;
1358 void monoflop_fixed_use()
1362 self.nextthink = time + self.wait;
1364 self.enemy = activator;
1368 void monoflop_think()
1371 activator = self.enemy;
1375 void monoflop_reset()
1381 void spawnfunc_trigger_monoflop()
1385 if(self.spawnflags & 1)
1386 self.use = monoflop_fixed_use;
1388 self.use = monoflop_use;
1389 self.think = monoflop_think;
1391 self.reset = monoflop_reset;
1394 void multivibrator_send()
1399 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1401 newstate = (time < cyclestart + self.wait);
1404 if(self.state != newstate)
1406 self.state = newstate;
1409 self.nextthink = cyclestart + self.wait + 0.01;
1411 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1414 void multivibrator_toggle()
1416 if(self.nextthink == 0)
1418 multivibrator_send();
1431 void multivibrator_reset()
1433 if(!(self.spawnflags & 1))
1434 self.nextthink = 0; // wait for a trigger event
1436 self.nextthink = max(1, time);
1439 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1440 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1441 -------- KEYS --------
1442 target: trigger all entities with this targetname when it goes off
1443 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1444 phase: offset of the timing
1445 wait: "on" cycle time (default: 1)
1446 respawntime: "off" cycle time (default: same as wait)
1447 -------- SPAWNFLAGS --------
1448 START_ON: assume it is already turned on (when targeted)
1450 void spawnfunc_trigger_multivibrator()
1454 if(!self.respawntime)
1455 self.respawntime = self.wait;
1458 self.use = multivibrator_toggle;
1459 self.think = multivibrator_send;
1460 self.nextthink = time;
1463 multivibrator_reset();
1472 if(self.killtarget != "")
1473 src = find(world, targetname, self.killtarget);
1474 if(self.target != "")
1475 dst = find(world, targetname, self.target);
1479 objerror("follow: could not find target/killtarget");
1485 // already done :P entity must stay
1489 else if(!src || !dst)
1491 objerror("follow: could not find target/killtarget");
1494 else if(self.spawnflags & 1)
1497 if(self.spawnflags & 2)
1499 setattachment(dst, src, self.message);
1503 attach_sameorigin(dst, src, self.message);
1510 if(self.spawnflags & 2)
1512 dst.movetype = MOVETYPE_FOLLOW;
1514 // dst.punchangle = '0 0 0'; // keep unchanged
1515 dst.view_ofs = dst.origin;
1516 dst.v_angle = dst.angles;
1520 follow_sameorigin(dst, src);
1527 void spawnfunc_misc_follow()
1529 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1534 void gamestart_use() {
1540 void spawnfunc_trigger_gamestart() {
1541 self.use = gamestart_use;
1542 self.reset2 = spawnfunc_trigger_gamestart;
1546 self.think = self.use;
1547 self.nextthink = game_starttime + self.wait;
1550 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1556 .entity voicescript; // attached voice script
1557 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1558 .float voicescript_nextthink; // time to play next voice
1559 .float voicescript_voiceend; // time when this voice ends
1561 void target_voicescript_clear(entity pl)
1563 pl.voicescript = world;
1566 void target_voicescript_use()
1568 if(activator.voicescript != self)
1570 activator.voicescript = self;
1571 activator.voicescript_index = 0;
1572 activator.voicescript_nextthink = time + self.delay;
1576 void target_voicescript_next(entity pl)
1581 vs = pl.voicescript;
1584 if(vs.message == "")
1586 if(pl.classname != "player")
1591 if(time >= pl.voicescript_voiceend)
1593 if(time >= pl.voicescript_nextthink)
1595 // get the next voice...
1596 n = tokenize_console(vs.message);
1598 if(pl.voicescript_index < vs.cnt)
1599 i = pl.voicescript_index * 2;
1600 else if(n > vs.cnt * 2)
1601 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1607 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1608 dt = stof(argv(i + 1));
1611 pl.voicescript_voiceend = time + dt;
1612 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1616 pl.voicescript_voiceend = time - dt;
1617 pl.voicescript_nextthink = pl.voicescript_voiceend;
1620 pl.voicescript_index += 1;
1624 pl.voicescript = world; // stop trying then
1630 void spawnfunc_target_voicescript()
1632 // netname: directory of the sound files
1633 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1634 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1635 // Here, a - in front of the duration means that no delay is to be
1636 // added after this message
1637 // wait: average time between messages
1638 // delay: initial delay before the first message
1641 self.use = target_voicescript_use;
1643 n = tokenize_console(self.message);
1645 for(i = 0; i+1 < n; i += 2)
1652 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1658 void trigger_relay_teamcheck_use()
1662 if(self.spawnflags & 2)
1664 if(activator.team != self.team)
1669 if(activator.team == self.team)
1675 if(self.spawnflags & 1)
1680 void trigger_relay_teamcheck_reset()
1682 self.team = self.team_saved;
1685 void spawnfunc_trigger_relay_teamcheck()
1687 self.team_saved = self.team;
1688 self.use = trigger_relay_teamcheck_use;
1689 self.reset = trigger_relay_teamcheck_reset;
1694 void trigger_disablerelay_use()
1701 for(e = world; (e = find(e, targetname, self.target)); )
1703 if(e.use == SUB_UseTargets)
1705 e.use = SUB_DontUseTargets;
1708 else if(e.use == SUB_DontUseTargets)
1710 e.use = SUB_UseTargets;
1716 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1719 void spawnfunc_trigger_disablerelay()
1721 self.use = trigger_disablerelay_use;
1724 float magicear_matched;
1725 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1727 float domatch, dotrigger, matchstart, l;
1731 magicear_matched = FALSE;
1733 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1734 domatch = ((ear.spawnflags & 32) || dotrigger);
1740 if(ear.spawnflags & 4)
1746 if(ear.spawnflags & 1)
1749 if(ear.spawnflags & 2)
1752 if(ear.spawnflags & 8)
1757 l = strlen(ear.message);
1759 if(self.spawnflags & 128)
1762 msg = strdecolorize(msgin);
1764 if(substring(ear.message, 0, 1) == "*")
1766 if(substring(ear.message, -1, 1) == "*")
1769 // as we need multi-replacement here...
1770 s = substring(ear.message, 1, -2);
1772 if(strstrofs(msg, s, 0) >= 0)
1773 matchstart = -2; // we use strreplace on s
1778 s = substring(ear.message, 1, -1);
1780 if(substring(msg, -l, l) == s)
1781 matchstart = strlen(msg) - l;
1786 if(substring(ear.message, -1, 1) == "*")
1789 s = substring(ear.message, 0, -2);
1791 if(substring(msg, 0, l) == s)
1798 if(msg == ear.message)
1803 if(matchstart == -1) // no match
1806 magicear_matched = TRUE;
1810 oldself = activator = self;
1816 if(ear.spawnflags & 16)
1820 else if(ear.netname != "")
1823 return strreplace(s, ear.netname, msg);
1826 substring(msg, 0, matchstart),
1828 substring(msg, matchstart + l, -1)
1836 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1840 for(ear = magicears; ear; ear = ear.enemy)
1842 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1843 if not(ear.spawnflags & 64)
1844 if(magicear_matched)
1851 void spawnfunc_trigger_magicear()
1853 self.enemy = magicears;
1856 // actually handled in "say" processing
1859 // 2 = ignore teamsay
1861 // 8 = ignore tell to unknown player
1862 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1863 // 32 = perform the replacement even if outside the radius or dead
1864 // 64 = continue replacing/triggering even if this one matched
1874 // if set, replacement for the matched text
1876 // "hearing distance"