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;
169 { // we can't just remove (self) here, because this is a touch function
170 // called wheil C code is looping through area links...
171 self.touch = SUB_Null;
177 self.goalentity = other;
178 self.enemy = activator;
184 if not(self.spawnflags & 2)
186 if not(other.iscreature)
190 if(self.team == other.team)
194 // if the trigger has an angles field, check player's facing direction
195 if (self.movedir != '0 0 0')
197 makevectors (other.angles);
198 if (v_forward * self.movedir < 0)
199 return; // not facing the right way
205 self.goalentity = other;
209 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
211 if (!self.takedamage)
213 self.health = self.health - damage;
214 if (self.health <= 0)
216 self.enemy = attacker;
217 self.goalentity = inflictor;
224 self.touch = multi_touch;
225 self.health = self.max_health;
226 self.takedamage = DAMAGE_YES;
227 self.solid = SOLID_BBOX;
228 self.think = SUB_Null;
229 self.team = self.team_saved;
232 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
233 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.
234 If "delay" is set, the trigger waits some time after activating before firing.
235 "wait" : Seconds between triggerings. (.2 default)
236 If notouch is set, the trigger is only fired by other entities, not by touching.
237 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
243 set "message" to text string
245 void spawnfunc_trigger_multiple()
247 self.reset = multi_reset;
248 if (self.sounds == 1)
250 precache_sound ("misc/secret.wav");
251 self.noise = "misc/secret.wav";
253 else if (self.sounds == 2)
255 precache_sound ("misc/talk.wav");
256 self.noise = "misc/talk.wav";
258 else if (self.sounds == 3)
260 precache_sound ("misc/trigger1.wav");
261 self.noise = "misc/trigger1.wav";
266 self.use = multi_use;
270 self.team_saved = self.team;
274 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
275 objerror ("health and notouch don't make sense\n");
276 self.max_health = self.health;
277 self.event_damage = multi_eventdamage;
278 self.takedamage = DAMAGE_YES;
279 self.solid = SOLID_BBOX;
280 setorigin (self, self.origin); // make sure it links into the world
284 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
286 self.touch = multi_touch;
287 setorigin (self, self.origin); // make sure it links into the world
293 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
294 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
295 "targetname". If "health" is set, the trigger must be killed to activate.
296 If notouch is set, the trigger is only fired by other entities, not by touching.
297 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
298 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.
304 set "message" to text string
306 void spawnfunc_trigger_once()
309 spawnfunc_trigger_multiple();
312 //=============================================================================
314 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
315 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
317 void spawnfunc_trigger_relay()
319 self.use = SUB_UseTargets;
320 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
325 self.think = SUB_UseTargets;
326 self.nextthink = self.wait;
331 self.think = SUB_Null;
334 void spawnfunc_trigger_delay()
339 self.use = delay_use;
340 self.reset = delay_reset;
343 //=============================================================================
348 self.count = self.count - 1;
354 if (activator.classname == "player"
355 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
358 centerprint (activator, "There are more to go...");
359 else if (self.count == 3)
360 centerprint (activator, "Only 3 more to go...");
361 else if (self.count == 2)
362 centerprint (activator, "Only 2 more to go...");
364 centerprint (activator, "Only 1 more to go...");
369 if (activator.classname == "player"
370 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
371 centerprint(activator, "Sequence completed!");
372 self.enemy = activator;
378 self.count = self.cnt;
382 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
383 Acts as an intermediary for an action that takes multiple inputs.
385 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
387 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
389 void spawnfunc_trigger_counter()
394 self.cnt = self.count;
396 self.use = counter_use;
397 self.reset = counter_reset;
400 .float triggerhurttime;
401 void trigger_hurt_touch()
403 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
404 if (other.iscreature)
406 if (other.takedamage)
407 if (other.triggerhurttime < time)
410 other.triggerhurttime = time + 1;
411 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
418 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
421 other.pain_finished = min(other.pain_finished, time + 2);
423 else if (other.classname == "rune") // reset runes
426 other.nextthink = min(other.nextthink, time + 1);
434 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
435 Any object touching this will be hurt
436 set dmg to damage amount
439 .entity trigger_hurt_next;
440 entity trigger_hurt_last;
441 entity trigger_hurt_first;
442 void spawnfunc_trigger_hurt()
445 self.touch = trigger_hurt_touch;
449 self.message = "was in the wrong place";
451 self.message2 = "was thrown into a world of hurt by";
453 if(!trigger_hurt_first)
454 trigger_hurt_first = self;
455 if(trigger_hurt_last)
456 trigger_hurt_last.trigger_hurt_next = self;
457 trigger_hurt_last = self;
460 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
464 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
465 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
471 //////////////////////////////////////////////////////////////
475 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
477 //////////////////////////////////////////////////////////////
479 .float triggerhealtime;
480 void trigger_heal_touch()
482 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
483 if (other.iscreature)
485 if (other.takedamage)
486 if (other.triggerhealtime < time)
489 other.triggerhealtime = time + 1;
491 if (other.health < self.max_health)
493 other.health = min(other.health + self.health, self.max_health);
494 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
495 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
501 void spawnfunc_trigger_heal()
504 self.touch = trigger_heal_touch;
507 if (!self.max_health)
508 self.max_health = 200; //Max health topoff for field
510 self.noise = "misc/mediumhealth.wav";
511 precache_sound(self.noise);
515 //////////////////////////////////////////////////////////////
521 //////////////////////////////////////////////////////////////
525 // TODO add a way to do looped sounds with sound(); then complete this entity
526 .float volume, atten;
527 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
529 void spawnfunc_target_speaker()
532 precache_sound (self.noise);
536 self.atten = ATTN_NORM;
537 else if(self.atten < 0)
541 self.use = target_speaker_use;
546 self.atten = ATTN_STATIC;
547 else if(self.atten < 0)
551 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
556 void spawnfunc_func_stardust() {
557 self.effects = EF_STARDUST;
561 .float bgmscriptattack;
562 .float bgmscriptdecay;
563 .float bgmscriptsustain;
564 .float bgmscriptrelease;
565 float pointparticles_SendEntity(entity to, float fl)
567 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
569 // optional features to save space
571 if(self.spawnflags & 2)
572 fl |= 0x10; // absolute count on toggle-on
573 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
574 fl |= 0x20; // 4 bytes - saves CPU
575 if(self.waterlevel || self.count != 1)
576 fl |= 0x40; // 4 bytes - obscure features almost never used
577 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
578 fl |= 0x80; // 14 bytes - saves lots of space
580 WriteByte(MSG_ENTITY, fl);
584 WriteCoord(MSG_ENTITY, self.impulse);
586 WriteCoord(MSG_ENTITY, 0); // off
590 WriteCoord(MSG_ENTITY, self.origin_x);
591 WriteCoord(MSG_ENTITY, self.origin_y);
592 WriteCoord(MSG_ENTITY, self.origin_z);
596 if(self.model != "null")
598 WriteShort(MSG_ENTITY, self.modelindex);
601 WriteCoord(MSG_ENTITY, self.mins_x);
602 WriteCoord(MSG_ENTITY, self.mins_y);
603 WriteCoord(MSG_ENTITY, self.mins_z);
604 WriteCoord(MSG_ENTITY, self.maxs_x);
605 WriteCoord(MSG_ENTITY, self.maxs_y);
606 WriteCoord(MSG_ENTITY, self.maxs_z);
611 WriteShort(MSG_ENTITY, 0);
614 WriteCoord(MSG_ENTITY, self.maxs_x);
615 WriteCoord(MSG_ENTITY, self.maxs_y);
616 WriteCoord(MSG_ENTITY, self.maxs_z);
619 WriteShort(MSG_ENTITY, self.cnt);
622 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
623 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
627 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
628 WriteByte(MSG_ENTITY, self.count * 16.0);
630 WriteString(MSG_ENTITY, self.noise);
633 WriteByte(MSG_ENTITY, floor(self.atten * 64));
634 WriteByte(MSG_ENTITY, floor(self.volume * 255));
636 WriteString(MSG_ENTITY, self.bgmscript);
637 if(self.bgmscript != "")
639 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
640 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
641 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
642 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
648 void pointparticles_use()
650 self.state = !self.state;
654 void pointparticles_think()
656 if(self.origin != self.oldorigin)
659 self.oldorigin = self.origin;
661 self.nextthink = time;
664 void pointparticles_reset()
666 if(self.spawnflags & 1)
672 void spawnfunc_func_pointparticles()
675 setmodel(self, self.model);
677 precache_sound (self.noise);
679 if(!self.bgmscriptsustain)
680 self.bgmscriptsustain = 1;
681 else if(self.bgmscriptsustain < 0)
682 self.bgmscriptsustain = 0;
685 self.atten = ATTN_NORM;
686 else if(self.atten < 0)
697 setorigin(self, self.origin + self.mins);
698 setsize(self, '0 0 0', self.maxs - self.mins);
701 self.cnt = particleeffectnum(self.mdl);
703 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
707 self.use = pointparticles_use;
708 self.reset = pointparticles_reset;
713 self.think = pointparticles_think;
714 self.nextthink = time;
717 void spawnfunc_func_sparks()
719 // self.cnt is the amount of sparks that one burst will spawn
721 self.cnt = 25.0; // nice default value
724 // self.wait is the probability that a sparkthink will spawn a spark shower
725 // range: 0 - 1, but 0 makes little sense, so...
726 if(self.wait < 0.05) {
727 self.wait = 0.25; // nice default value
730 self.count = self.cnt;
733 self.velocity = '0 0 -1';
734 self.mdl = "TE_SPARK";
735 self.impulse = 10 * self.wait; // by default 2.5/sec
737 self.cnt = 0; // use mdl
739 spawnfunc_func_pointparticles();
742 float rainsnow_SendEntity(entity to, float sf)
744 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
745 WriteByte(MSG_ENTITY, self.state);
746 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
747 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
748 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
749 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
750 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
751 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
752 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
753 WriteShort(MSG_ENTITY, self.count);
754 WriteByte(MSG_ENTITY, self.cnt);
758 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
759 This is an invisible area like a trigger, which rain falls inside of.
763 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
765 sets color of rain (default 12 - white)
767 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
769 void spawnfunc_func_rain()
771 self.dest = self.velocity;
772 self.velocity = '0 0 0';
774 self.dest = '0 0 -700';
775 self.angles = '0 0 0';
776 self.movetype = MOVETYPE_NONE;
777 self.solid = SOLID_NOT;
778 SetBrushEntityModel();
783 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
786 if(self.count > 65535)
789 self.state = 1; // 1 is rain, 0 is snow
792 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
796 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
797 This is an invisible area like a trigger, which snow falls inside of.
801 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
803 sets color of rain (default 12 - white)
805 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
807 void spawnfunc_func_snow()
809 self.dest = self.velocity;
810 self.velocity = '0 0 0';
812 self.dest = '0 0 -300';
813 self.angles = '0 0 0';
814 self.movetype = MOVETYPE_NONE;
815 self.solid = SOLID_NOT;
816 SetBrushEntityModel();
821 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
824 if(self.count > 65535)
827 self.state = 0; // 1 is rain, 0 is snow
830 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
834 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
837 void misc_laser_aim()
842 if(self.spawnflags & 2)
844 if(self.enemy.origin != self.mangle)
846 self.mangle = self.enemy.origin;
852 a = vectoangles(self.enemy.origin - self.origin);
863 if(self.angles != self.mangle)
865 self.mangle = self.angles;
869 if(self.origin != self.oldorigin)
872 self.oldorigin = self.origin;
876 void misc_laser_init()
878 if(self.target != "")
879 self.enemy = find(world, targetname, self.target);
883 void misc_laser_think()
888 self.nextthink = time;
897 o = self.enemy.origin;
898 if not(self.spawnflags & 2)
899 o = self.origin + normalize(o - self.origin) * 32768;
903 makevectors(self.mangle);
904 o = self.origin + v_forward * 32768;
910 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
912 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
915 if(self.enemy.target != "") // DETECTOR laser
917 traceline(self.origin, o, MOVE_NORMAL, self);
918 if(trace_ent.iscreature)
920 self.pusher = trace_ent;
927 activator = self.pusher;
940 activator = self.pusher;
948 float laser_SendEntity(entity to, float fl)
950 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
951 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
952 if(self.spawnflags & 2)
956 if(self.scale != 1 || self.modelscale != 1)
958 WriteByte(MSG_ENTITY, fl);
961 WriteCoord(MSG_ENTITY, self.origin_x);
962 WriteCoord(MSG_ENTITY, self.origin_y);
963 WriteCoord(MSG_ENTITY, self.origin_z);
967 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
968 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
969 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
971 WriteByte(MSG_ENTITY, self.alpha * 255.0);
974 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
975 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
977 WriteShort(MSG_ENTITY, self.cnt + 1);
983 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
984 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
985 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
989 WriteAngle(MSG_ENTITY, self.mangle_x);
990 WriteAngle(MSG_ENTITY, self.mangle_y);
994 WriteByte(MSG_ENTITY, self.state);
998 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
999 Any object touching the beam will be hurt
1002 spawnfunc_target_position where the laser ends
1004 name of beam end effect to use
1006 color of the beam (default: red)
1008 damage per second (-1 for a laser that kills immediately)
1012 self.state = !self.state;
1013 self.SendFlags |= 4;
1019 if(self.spawnflags & 1)
1025 void spawnfunc_misc_laser()
1029 if(self.mdl == "none")
1033 self.cnt = particleeffectnum(self.mdl);
1036 self.cnt = particleeffectnum("laser_deadly");
1042 self.cnt = particleeffectnum("laser_deadly");
1049 if(self.colormod == '0 0 0')
1051 self.colormod = '1 0 0';
1053 self.message = "saw the light";
1055 self.message2 = "was pushed into a laser by";
1058 if(!self.modelscale)
1059 self.modelscale = 1;
1060 self.think = misc_laser_think;
1061 self.nextthink = time;
1062 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1064 self.mangle = self.angles;
1066 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1070 self.reset = laser_reset;
1072 self.use = laser_use;
1078 // tZorks trigger impulse / gravity
1082 .float lastpushtime;
1084 // targeted (directional) mode
1085 void trigger_impulse_touch1()
1088 float pushdeltatime;
1091 // FIXME: Better checking for what to push and not.
1092 if not(other.iscreature)
1093 if (other.classname != "corpse")
1094 if (other.classname != "body")
1095 if (other.classname != "gib")
1096 if (other.classname != "missile")
1097 if (other.classname != "rocket")
1098 if (other.classname != "casing")
1099 if (other.classname != "grenade")
1100 if (other.classname != "plasma")
1101 if (other.classname != "plasma_prim")
1102 if (other.classname != "plasma_chain")
1103 if (other.classname != "droppedweapon")
1104 if (other.classname != "nexball_basketball")
1105 if (other.classname != "nexball_football")
1108 if (other.deadflag && other.iscreature)
1113 targ = find(world, targetname, self.target);
1116 objerror("trigger_force without a (valid) .target!\n");
1121 if(self.falloff == 1)
1122 str = (str / self.radius) * self.strength;
1123 else if(self.falloff == 2)
1124 str = (1 - (str / self.radius)) * self.strength;
1126 str = self.strength;
1128 pushdeltatime = time - other.lastpushtime;
1129 if (pushdeltatime > 0.15) pushdeltatime = 0;
1130 other.lastpushtime = time;
1131 if(!pushdeltatime) return;
1133 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1134 other.flags &~= FL_ONGROUND;
1137 // Directionless (accelerator/decelerator) mode
1138 void trigger_impulse_touch2()
1140 float pushdeltatime;
1142 // FIXME: Better checking for what to push and not.
1143 if not(other.iscreature)
1144 if (other.classname != "corpse")
1145 if (other.classname != "body")
1146 if (other.classname != "gib")
1147 if (other.classname != "missile")
1148 if (other.classname != "rocket")
1149 if (other.classname != "casing")
1150 if (other.classname != "grenade")
1151 if (other.classname != "plasma")
1152 if (other.classname != "plasma_prim")
1153 if (other.classname != "plasma_chain")
1154 if (other.classname != "droppedweapon")
1155 if (other.classname != "nexball_basketball")
1156 if (other.classname != "nexball_football")
1159 if (other.deadflag && other.iscreature)
1164 pushdeltatime = time - other.lastpushtime;
1165 if (pushdeltatime > 0.15) pushdeltatime = 0;
1166 other.lastpushtime = time;
1167 if(!pushdeltatime) return;
1169 //if(self.strength > 1)
1170 other.velocity = other.velocity * (self.strength * pushdeltatime);
1172 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1175 // Spherical (gravity/repulsor) mode
1176 void trigger_impulse_touch3()
1178 float pushdeltatime;
1181 // FIXME: Better checking for what to push and not.
1182 if not(other.iscreature)
1183 if (other.classname != "corpse")
1184 if (other.classname != "body")
1185 if (other.classname != "gib")
1186 if (other.classname != "missile")
1187 if (other.classname != "rocket")
1188 if (other.classname != "casing")
1189 if (other.classname != "grenade")
1190 if (other.classname != "plasma")
1191 if (other.classname != "plasma_prim")
1192 if (other.classname != "plasma_chain")
1193 if (other.classname != "droppedweapon")
1194 if (other.classname != "nexball_basketball")
1195 if (other.classname != "nexball_football")
1198 if (other.deadflag && other.iscreature)
1203 pushdeltatime = time - other.lastpushtime;
1204 if (pushdeltatime > 0.15) pushdeltatime = 0;
1205 other.lastpushtime = time;
1206 if(!pushdeltatime) return;
1208 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1210 str = min(self.radius, vlen(self.origin - other.origin));
1212 if(self.falloff == 1)
1213 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1214 else if(self.falloff == 2)
1215 str = (str / self.radius) * self.strength; // 0 in the inside
1217 str = self.strength;
1219 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1222 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1223 -------- KEYS --------
1224 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1225 If not, this trigger acts like a damper/accelerator field.
1227 strength : This is how mutch force to add in the direction of .target each second
1228 when .target is set. If not, this is hoe mutch to slow down/accelerate
1229 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1231 radius : If set, act as a spherical device rather then a liniar one.
1233 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1235 -------- NOTES --------
1236 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1237 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1240 void spawnfunc_trigger_impulse()
1245 if(!self.strength) self.strength = 2000;
1246 setorigin(self, self.origin);
1247 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1248 self.touch = trigger_impulse_touch3;
1254 if(!self.strength) self.strength = 950;
1255 self.touch = trigger_impulse_touch1;
1259 if(!self.strength) self.strength = 0.9;
1260 self.touch = trigger_impulse_touch2;
1265 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1266 "Flip-flop" trigger gate... lets only every second trigger event through
1270 self.state = !self.state;
1275 void spawnfunc_trigger_flipflop()
1277 if(self.spawnflags & 1)
1279 self.use = flipflop_use;
1280 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1283 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1284 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1288 self.nextthink = time + self.wait;
1289 self.enemy = activator;
1295 void monoflop_fixed_use()
1299 self.nextthink = time + self.wait;
1301 self.enemy = activator;
1305 void monoflop_think()
1308 activator = self.enemy;
1312 void monoflop_reset()
1318 void spawnfunc_trigger_monoflop()
1322 if(self.spawnflags & 1)
1323 self.use = monoflop_fixed_use;
1325 self.use = monoflop_use;
1326 self.think = monoflop_think;
1328 self.reset = monoflop_reset;
1331 void multivibrator_send()
1336 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1338 newstate = (time < cyclestart + self.wait);
1341 if(self.state != newstate)
1343 self.state = newstate;
1346 self.nextthink = cyclestart + self.wait + 0.01;
1348 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1351 void multivibrator_toggle()
1353 if(self.nextthink == 0)
1355 multivibrator_send();
1368 void multivibrator_reset()
1370 if(!(self.spawnflags & 1))
1371 self.nextthink = 0; // wait for a trigger event
1373 self.nextthink = max(1, time);
1376 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1377 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1378 -------- KEYS --------
1379 target: trigger all entities with this targetname when it goes off
1380 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1381 phase: offset of the timing
1382 wait: "on" cycle time (default: 1)
1383 respawntime: "off" cycle time (default: same as wait)
1384 -------- SPAWNFLAGS --------
1385 START_ON: assume it is already turned on (when targeted)
1387 void spawnfunc_trigger_multivibrator()
1391 if(!self.respawntime)
1392 self.respawntime = self.wait;
1395 self.use = multivibrator_toggle;
1396 self.think = multivibrator_send;
1397 self.nextthink = time;
1400 multivibrator_reset();
1407 src = find(world, targetname, self.killtarget);
1408 dst = find(world, targetname, self.target);
1412 objerror("follow: could not find target/killtarget");
1416 if(self.spawnflags & 1)
1419 if(self.spawnflags & 2)
1421 setattachment(dst, src, self.message);
1425 attach_sameorigin(dst, src, self.message);
1430 if(self.spawnflags & 2)
1432 dst.movetype = MOVETYPE_FOLLOW;
1434 // dst.punchangle = '0 0 0'; // keep unchanged
1435 dst.view_ofs = dst.origin;
1436 dst.v_angle = dst.angles;
1440 follow_sameorigin(dst, src);
1447 void spawnfunc_misc_follow()
1449 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1454 void gamestart_use() {
1460 void spawnfunc_trigger_gamestart() {
1461 self.use = gamestart_use;
1462 self.reset2 = spawnfunc_trigger_gamestart;
1466 self.think = self.use;
1467 self.nextthink = game_starttime + self.wait;
1470 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1476 .entity voicescript; // attached voice script
1477 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1478 .float voicescript_nextthink; // time to play next voice
1479 .float voicescript_voiceend; // time when this voice ends
1481 void target_voicescript_clear(entity pl)
1483 pl.voicescript = world;
1486 void target_voicescript_use()
1488 if(activator.voicescript != self)
1490 activator.voicescript = self;
1491 activator.voicescript_index = 0;
1492 activator.voicescript_nextthink = time + self.delay;
1496 void target_voicescript_next(entity pl)
1501 vs = pl.voicescript;
1504 if(vs.message == "")
1506 if(pl.classname != "player")
1511 if(time >= pl.voicescript_voiceend)
1513 if(time >= pl.voicescript_nextthink)
1515 // get the next voice...
1516 n = tokenize_console(vs.message);
1518 if(pl.voicescript_index < vs.cnt)
1519 i = pl.voicescript_index * 2;
1520 else if(n > vs.cnt * 2)
1521 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1527 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1528 pl.voicescript_voiceend = time + stof(argv(i + 1));
1531 pl.voicescript = world;
1533 pl.voicescript_index += 1;
1534 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1539 void spawnfunc_target_voicescript()
1541 // netname: directory of the sound files
1542 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1543 // foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1544 // wait: average time between messages
1545 // delay: initial delay before the first message
1548 self.use = target_voicescript_use;
1550 n = tokenize_console(self.message);
1552 for(i = 0; i+1 < n; i += 2)
1559 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1565 void trigger_relay_teamcheck_use()
1569 if(self.spawnflags & 2)
1571 if(activator.team != self.team)
1576 if(activator.team == self.team)
1582 if(self.spawnflags & 1)
1587 void trigger_relay_teamcheck_reset()
1589 self.team = self.team_saved;
1592 void spawnfunc_trigger_relay_teamcheck()
1594 self.team_saved = self.team;
1595 self.use = trigger_relay_teamcheck_use;
1596 self.reset = trigger_relay_teamcheck_reset;
1601 void trigger_disablerelay_use()
1608 for(e = world; (e = find(e, targetname, self.target)); )
1610 if(e.use == SUB_UseTargets)
1612 e.use = SUB_DontUseTargets;
1615 else if(e.use == SUB_DontUseTargets)
1617 e.use = SUB_UseTargets;
1623 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1626 void spawnfunc_trigger_disablerelay()
1628 self.use = trigger_disablerelay_use;