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;
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.enemy = activator;
183 if not(self.spawnflags & 2)
185 if not(other.iscreature)
189 if(self.team == other.team)
193 // if the trigger has an angles field, check player's facing direction
194 if (self.movedir != '0 0 0')
196 makevectors (other.angles);
197 if (v_forward * self.movedir < 0)
198 return; // not facing the right way
207 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
209 if (!self.takedamage)
211 self.health = self.health - damage;
212 if (self.health <= 0)
214 self.enemy = attacker;
221 self.touch = multi_touch;
222 self.health = self.max_health;
223 self.takedamage = DAMAGE_YES;
224 self.solid = SOLID_BBOX;
225 self.think = SUB_Null;
226 self.team = self.team_saved;
229 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
230 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.
231 If "delay" is set, the trigger waits some time after activating before firing.
232 "wait" : Seconds between triggerings. (.2 default)
233 If notouch is set, the trigger is only fired by other entities, not by touching.
234 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
240 set "message" to text string
242 void spawnfunc_trigger_multiple()
244 self.reset = multi_reset;
245 if (self.sounds == 1)
247 precache_sound ("misc/secret.wav");
248 self.noise = "misc/secret.wav";
250 else if (self.sounds == 2)
252 precache_sound ("misc/talk.wav");
253 self.noise = "misc/talk.wav";
255 else if (self.sounds == 3)
257 precache_sound ("misc/trigger1.wav");
258 self.noise = "misc/trigger1.wav";
263 self.use = multi_use;
267 self.team_saved = self.team;
271 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
272 objerror ("health and notouch don't make sense\n");
273 self.max_health = self.health;
274 self.event_damage = multi_eventdamage;
275 self.takedamage = DAMAGE_YES;
276 self.solid = SOLID_BBOX;
277 setorigin (self, self.origin); // make sure it links into the world
281 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
283 self.touch = multi_touch;
284 setorigin (self, self.origin); // make sure it links into the world
290 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
291 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
292 "targetname". If "health" is set, the trigger must be killed to activate.
293 If notouch is set, the trigger is only fired by other entities, not by touching.
294 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
295 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.
301 set "message" to text string
303 void spawnfunc_trigger_once()
306 spawnfunc_trigger_multiple();
309 //=============================================================================
311 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
312 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
314 void spawnfunc_trigger_relay()
316 self.use = SUB_UseTargets;
317 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
322 self.think = SUB_UseTargets;
323 self.nextthink = self.wait;
328 self.think = SUB_Null;
331 void spawnfunc_trigger_delay()
336 self.use = delay_use;
337 self.reset = delay_reset;
340 //=============================================================================
345 self.count = self.count - 1;
351 if (activator.classname == "player"
352 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
355 centerprint (activator, "There are more to go...");
356 else if (self.count == 3)
357 centerprint (activator, "Only 3 more to go...");
358 else if (self.count == 2)
359 centerprint (activator, "Only 2 more to go...");
361 centerprint (activator, "Only 1 more to go...");
366 if (activator.classname == "player"
367 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
368 centerprint(activator, "Sequence completed!");
369 self.enemy = activator;
375 self.count = self.cnt;
379 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
380 Acts as an intermediary for an action that takes multiple inputs.
382 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
384 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
386 void spawnfunc_trigger_counter()
391 self.cnt = self.count;
393 self.use = counter_use;
394 self.reset = counter_reset;
397 .float triggerhurttime;
398 void trigger_hurt_touch()
400 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
401 if (other.iscreature)
403 if (other.takedamage)
404 if (other.triggerhurttime < time)
407 other.triggerhurttime = time + 1;
408 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
415 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
418 other.pain_finished = min(other.pain_finished, time + 2);
420 else if (other.classname == "rune") // reset runes
423 other.nextthink = min(other.nextthink, time + 1);
431 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
432 Any object touching this will be hurt
433 set dmg to damage amount
436 .entity trigger_hurt_next;
437 entity trigger_hurt_last;
438 entity trigger_hurt_first;
439 void spawnfunc_trigger_hurt()
442 self.touch = trigger_hurt_touch;
446 self.message = "was in the wrong place";
448 self.message2 = "was thrown into a world of hurt by";
450 if(!trigger_hurt_first)
451 trigger_hurt_first = self;
452 if(trigger_hurt_last)
453 trigger_hurt_last.trigger_hurt_next = self;
454 trigger_hurt_last = self;
457 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
461 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
462 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
468 //////////////////////////////////////////////////////////////
472 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
474 //////////////////////////////////////////////////////////////
476 .float triggerhealtime;
477 void trigger_heal_touch()
479 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
480 if (other.iscreature)
482 if (other.takedamage)
483 if (other.triggerhealtime < time)
486 other.triggerhealtime = time + 1;
488 if (other.health < self.max_health)
490 other.health = min(other.health + self.health, self.max_health);
491 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
492 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
498 void spawnfunc_trigger_heal()
501 self.touch = trigger_heal_touch;
504 if (!self.max_health)
505 self.max_health = 200; //Max health topoff for field
507 self.noise = "misc/mediumhealth.wav";
508 precache_sound(self.noise);
512 //////////////////////////////////////////////////////////////
518 //////////////////////////////////////////////////////////////
522 // TODO add a way to do looped sounds with sound(); then complete this entity
523 .float volume, atten;
524 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
526 void spawnfunc_target_speaker()
529 precache_sound (self.noise);
533 self.atten = ATTN_NORM;
534 else if(self.atten < 0)
538 self.use = target_speaker_use;
543 self.atten = ATTN_STATIC;
544 else if(self.atten < 0)
548 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
553 void spawnfunc_func_stardust() {
554 self.effects = EF_STARDUST;
558 .float bgmscriptattack;
559 .float bgmscriptdecay;
560 .float bgmscriptsustain;
561 .float bgmscriptrelease;
562 float pointparticles_SendEntity(entity to, float fl)
564 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
566 // optional features to save space
568 if(self.spawnflags & 2)
569 fl |= 0x10; // absolute count on toggle-on
570 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
571 fl |= 0x20; // 4 bytes - saves CPU
572 if(self.waterlevel || self.count != 1)
573 fl |= 0x40; // 4 bytes - obscure features almost never used
574 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
575 fl |= 0x80; // 14 bytes - saves lots of space
577 WriteByte(MSG_ENTITY, fl);
581 WriteCoord(MSG_ENTITY, self.impulse);
583 WriteCoord(MSG_ENTITY, 0); // off
587 WriteCoord(MSG_ENTITY, self.origin_x);
588 WriteCoord(MSG_ENTITY, self.origin_y);
589 WriteCoord(MSG_ENTITY, self.origin_z);
593 if(self.model != "null")
595 WriteShort(MSG_ENTITY, self.modelindex);
598 WriteCoord(MSG_ENTITY, self.mins_x);
599 WriteCoord(MSG_ENTITY, self.mins_y);
600 WriteCoord(MSG_ENTITY, self.mins_z);
601 WriteCoord(MSG_ENTITY, self.maxs_x);
602 WriteCoord(MSG_ENTITY, self.maxs_y);
603 WriteCoord(MSG_ENTITY, self.maxs_z);
608 WriteShort(MSG_ENTITY, 0);
611 WriteCoord(MSG_ENTITY, self.maxs_x);
612 WriteCoord(MSG_ENTITY, self.maxs_y);
613 WriteCoord(MSG_ENTITY, self.maxs_z);
616 WriteShort(MSG_ENTITY, self.cnt);
619 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
620 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
624 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
625 WriteByte(MSG_ENTITY, self.count * 16.0);
627 WriteString(MSG_ENTITY, self.noise);
630 WriteByte(MSG_ENTITY, floor(self.atten * 64));
631 WriteByte(MSG_ENTITY, floor(self.volume * 255));
633 WriteString(MSG_ENTITY, self.bgmscript);
634 if(self.bgmscript != "")
636 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
637 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
638 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
639 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
645 void pointparticles_use()
647 self.state = !self.state;
651 void pointparticles_think()
653 if(self.origin != self.oldorigin)
656 self.oldorigin = self.origin;
658 self.nextthink = time;
661 void pointparticles_reset()
663 if(self.spawnflags & 1)
669 void spawnfunc_func_pointparticles()
672 setmodel(self, self.model);
674 precache_sound (self.noise);
676 if(!self.bgmscriptsustain)
677 self.bgmscriptsustain = 1;
678 else if(self.bgmscriptsustain < 0)
679 self.bgmscriptsustain = 0;
682 self.atten = ATTN_NORM;
683 else if(self.atten < 0)
694 setorigin(self, self.origin + self.mins);
695 setsize(self, '0 0 0', self.maxs - self.mins);
698 self.cnt = particleeffectnum(self.mdl);
700 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
704 self.use = pointparticles_use;
705 self.reset = pointparticles_reset;
710 self.think = pointparticles_think;
711 self.nextthink = time;
714 void spawnfunc_func_sparks()
716 // self.cnt is the amount of sparks that one burst will spawn
718 self.cnt = 25.0; // nice default value
721 // self.wait is the probability that a sparkthink will spawn a spark shower
722 // range: 0 - 1, but 0 makes little sense, so...
723 if(self.wait < 0.05) {
724 self.wait = 0.25; // nice default value
727 self.count = self.cnt;
730 self.velocity = '0 0 -1';
731 self.mdl = "TE_SPARK";
732 self.impulse = 10 * self.wait; // by default 2.5/sec
734 self.cnt = 0; // use mdl
736 spawnfunc_func_pointparticles();
739 float rainsnow_SendEntity(entity to, float sf)
741 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
742 WriteByte(MSG_ENTITY, self.state);
743 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
744 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
745 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
746 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
747 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
748 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
749 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
750 WriteShort(MSG_ENTITY, self.count);
751 WriteByte(MSG_ENTITY, self.cnt);
755 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
756 This is an invisible area like a trigger, which rain falls inside of.
760 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
762 sets color of rain (default 12 - white)
764 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
766 void spawnfunc_func_rain()
768 self.dest = self.velocity;
769 self.velocity = '0 0 0';
771 self.dest = '0 0 -700';
772 self.angles = '0 0 0';
773 self.movetype = MOVETYPE_NONE;
774 self.solid = SOLID_NOT;
775 SetBrushEntityModel();
780 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
783 if(self.count > 65535)
786 self.state = 1; // 1 is rain, 0 is snow
789 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
793 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
794 This is an invisible area like a trigger, which snow falls inside of.
798 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
800 sets color of rain (default 12 - white)
802 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
804 void spawnfunc_func_snow()
806 self.dest = self.velocity;
807 self.velocity = '0 0 0';
809 self.dest = '0 0 -300';
810 self.angles = '0 0 0';
811 self.movetype = MOVETYPE_NONE;
812 self.solid = SOLID_NOT;
813 SetBrushEntityModel();
818 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
821 if(self.count > 65535)
824 self.state = 0; // 1 is rain, 0 is snow
827 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
831 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
834 void misc_laser_aim()
839 if(self.spawnflags & 2)
841 if(self.enemy.origin != self.mangle)
843 self.mangle = self.enemy.origin;
849 a = vectoangles(self.enemy.origin - self.origin);
860 if(self.angles != self.mangle)
862 self.mangle = self.angles;
866 if(self.origin != self.oldorigin)
869 self.oldorigin = self.origin;
873 void misc_laser_init()
875 if(self.target != "")
876 self.enemy = find(world, targetname, self.target);
880 void misc_laser_think()
885 self.nextthink = time;
894 o = self.enemy.origin;
895 if not(self.spawnflags & 2)
896 o = self.origin + normalize(o - self.origin) * 32768;
900 makevectors(self.mangle);
901 o = self.origin + v_forward * 32768;
907 FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
909 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
912 if(self.enemy.target != "") // DETECTOR laser
914 traceline(self.origin, o, MOVE_NORMAL, self);
915 if(trace_ent.iscreature)
917 self.pusher = trace_ent;
924 activator = self.pusher;
937 activator = self.pusher;
945 float laser_SendEntity(entity to, float fl)
947 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
948 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
949 if(self.spawnflags & 2)
953 if(self.scale != 1 || self.modelscale != 1)
955 WriteByte(MSG_ENTITY, fl);
958 WriteCoord(MSG_ENTITY, self.origin_x);
959 WriteCoord(MSG_ENTITY, self.origin_y);
960 WriteCoord(MSG_ENTITY, self.origin_z);
964 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
965 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
966 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
968 WriteByte(MSG_ENTITY, self.alpha * 255.0);
971 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
972 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
974 WriteShort(MSG_ENTITY, self.cnt + 1);
980 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
981 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
982 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
986 WriteAngle(MSG_ENTITY, self.mangle_x);
987 WriteAngle(MSG_ENTITY, self.mangle_y);
991 WriteByte(MSG_ENTITY, self.state);
995 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
996 Any object touching the beam will be hurt
999 spawnfunc_target_position where the laser ends
1001 name of beam end effect to use
1003 color of the beam (default: red)
1005 damage per second (-1 for a laser that kills immediately)
1009 self.state = !self.state;
1010 self.SendFlags |= 4;
1016 if(self.spawnflags & 1)
1022 void spawnfunc_misc_laser()
1026 if(self.mdl == "none")
1030 self.cnt = particleeffectnum(self.mdl);
1033 self.cnt = particleeffectnum("laser_deadly");
1039 self.cnt = particleeffectnum("laser_deadly");
1046 if(self.colormod == '0 0 0')
1048 self.colormod = '1 0 0';
1050 self.message = "saw the light";
1052 self.message2 = "was pushed into a laser by";
1055 if(!self.modelscale)
1056 self.modelscale = 1;
1057 self.think = misc_laser_think;
1058 self.nextthink = time;
1059 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1061 self.mangle = self.angles;
1063 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1067 self.reset = laser_reset;
1069 self.use = laser_use;
1075 // tZorks trigger impulse / gravity
1079 .float lastpushtime;
1081 // targeted (directional) mode
1082 void trigger_impulse_touch1()
1085 float pushdeltatime;
1088 // FIXME: Better checking for what to push and not.
1089 if not(other.iscreature)
1090 if (other.classname != "corpse")
1091 if (other.classname != "body")
1092 if (other.classname != "gib")
1093 if (other.classname != "missile")
1094 if (other.classname != "rocket")
1095 if (other.classname != "casing")
1096 if (other.classname != "grenade")
1097 if (other.classname != "plasma")
1098 if (other.classname != "plasma_prim")
1099 if (other.classname != "plasma_chain")
1100 if (other.classname != "droppedweapon")
1101 if (other.classname != "ball_basketball")
1102 if (other.classname != "ball_football")
1105 if (other.deadflag && other.iscreature)
1110 targ = find(world, targetname, self.target);
1113 objerror("trigger_force without a (valid) .target!\n");
1118 if(self.falloff == 1)
1119 str = (str / self.radius) * self.strength;
1120 else if(self.falloff == 2)
1121 str = (1 - (str / self.radius)) * self.strength;
1123 str = self.strength;
1125 pushdeltatime = time - other.lastpushtime;
1126 if (pushdeltatime > 0.15) pushdeltatime = 0;
1127 other.lastpushtime = time;
1128 if(!pushdeltatime) return;
1130 other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1131 other.flags &~= FL_ONGROUND;
1134 // Directionless (accelerator/decelerator) mode
1135 void trigger_impulse_touch2()
1137 float pushdeltatime;
1139 // FIXME: Better checking for what to push and not.
1140 if not(other.iscreature)
1141 if (other.classname != "corpse")
1142 if (other.classname != "body")
1143 if (other.classname != "gib")
1144 if (other.classname != "missile")
1145 if (other.classname != "rocket")
1146 if (other.classname != "casing")
1147 if (other.classname != "grenade")
1148 if (other.classname != "plasma")
1149 if (other.classname != "plasma_prim")
1150 if (other.classname != "plasma_chain")
1151 if (other.classname != "droppedweapon")
1152 if (other.classname != "ball_basketball")
1153 if (other.classname != "ball_football")
1156 if (other.deadflag && other.iscreature)
1161 pushdeltatime = time - other.lastpushtime;
1162 if (pushdeltatime > 0.15) pushdeltatime = 0;
1163 other.lastpushtime = time;
1164 if(!pushdeltatime) return;
1166 //if(self.strength > 1)
1167 other.velocity = other.velocity * (self.strength * pushdeltatime);
1169 // other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1172 // Spherical (gravity/repulsor) mode
1173 void trigger_impulse_touch3()
1175 float pushdeltatime;
1178 // FIXME: Better checking for what to push and not.
1179 if not(other.iscreature)
1180 if (other.classname != "corpse")
1181 if (other.classname != "body")
1182 if (other.classname != "gib")
1183 if (other.classname != "missile")
1184 if (other.classname != "rocket")
1185 if (other.classname != "casing")
1186 if (other.classname != "grenade")
1187 if (other.classname != "plasma")
1188 if (other.classname != "plasma_prim")
1189 if (other.classname != "plasma_chain")
1190 if (other.classname != "droppedweapon")
1191 if (other.classname != "ball_basketball")
1192 if (other.classname != "ball_football")
1195 if (other.deadflag && other.iscreature)
1200 pushdeltatime = time - other.lastpushtime;
1201 if (pushdeltatime > 0.15) pushdeltatime = 0;
1202 other.lastpushtime = time;
1203 if(!pushdeltatime) return;
1205 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1207 str = min(self.radius, vlen(self.origin - other.origin));
1209 if(self.falloff == 1)
1210 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1211 else if(self.falloff == 2)
1212 str = (str / self.radius) * self.strength; // 0 in the inside
1214 str = self.strength;
1216 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1219 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1220 -------- KEYS --------
1221 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1222 If not, this trigger acts like a damper/accelerator field.
1224 strength : This is how mutch force to add in the direction of .target each second
1225 when .target is set. If not, this is hoe mutch to slow down/accelerate
1226 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1228 radius : If set, act as a spherical device rather then a liniar one.
1230 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1232 -------- NOTES --------
1233 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1234 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1237 void spawnfunc_trigger_impulse()
1242 if(!self.strength) self.strength = 2000;
1243 setorigin(self, self.origin);
1244 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1245 self.touch = trigger_impulse_touch3;
1251 if(!self.strength) self.strength = 950;
1252 self.touch = trigger_impulse_touch1;
1256 if(!self.strength) self.strength = 0.9;
1257 self.touch = trigger_impulse_touch2;
1262 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1263 "Flip-flop" trigger gate... lets only every second trigger event through
1267 self.state = !self.state;
1272 void spawnfunc_trigger_flipflop()
1274 if(self.spawnflags & 1)
1276 self.use = flipflop_use;
1277 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1280 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1281 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1285 self.nextthink = time + self.wait;
1286 self.enemy = activator;
1292 void monoflop_fixed_use()
1296 self.nextthink = time + self.wait;
1298 self.enemy = activator;
1302 void monoflop_think()
1305 activator = self.enemy;
1309 void monoflop_reset()
1315 void spawnfunc_trigger_monoflop()
1319 if(self.spawnflags & 1)
1320 self.use = monoflop_fixed_use;
1322 self.use = monoflop_use;
1323 self.think = monoflop_think;
1325 self.reset = monoflop_reset;
1328 void multivibrator_send()
1333 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1335 newstate = (time < cyclestart + self.wait);
1338 if(self.state != newstate)
1340 self.state = newstate;
1343 self.nextthink = cyclestart + self.wait + 0.01;
1345 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1348 void multivibrator_toggle()
1350 if(self.nextthink == 0)
1352 multivibrator_send();
1365 void multivibrator_reset()
1367 if(!(self.spawnflags & 1))
1368 self.nextthink = 0; // wait for a trigger event
1370 self.nextthink = max(1, time);
1373 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1374 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1375 -------- KEYS --------
1376 target: trigger all entities with this targetname when it goes off
1377 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1378 phase: offset of the timing
1379 wait: "on" cycle time (default: 1)
1380 respawntime: "off" cycle time (default: same as wait)
1381 -------- SPAWNFLAGS --------
1382 START_ON: assume it is already turned on (when targeted)
1384 void spawnfunc_trigger_multivibrator()
1388 if(!self.respawntime)
1389 self.respawntime = self.wait;
1392 self.use = multivibrator_toggle;
1393 self.think = multivibrator_send;
1394 self.nextthink = time;
1397 multivibrator_reset();
1404 src = find(world, targetname, self.killtarget);
1405 dst = find(world, targetname, self.target);
1409 objerror("follow: could not find target/killtarget");
1413 if(self.spawnflags & 1)
1416 if(self.spawnflags & 2)
1418 setattachment(dst, src, self.message);
1422 attach_sameorigin(dst, src, self.message);
1427 if(self.spawnflags & 2)
1429 dst.movetype = MOVETYPE_FOLLOW;
1431 // dst.punchangle = '0 0 0'; // keep unchanged
1432 dst.view_ofs = dst.origin;
1433 dst.v_angle = dst.angles;
1437 follow_sameorigin(dst, src);
1444 void spawnfunc_misc_follow()
1446 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1451 void gamestart_use() {
1457 void spawnfunc_trigger_gamestart() {
1458 self.use = gamestart_use;
1459 self.reset2 = spawnfunc_trigger_gamestart;
1463 self.think = self.use;
1464 self.nextthink = game_starttime + self.wait;
1467 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1473 .entity voicescript; // attached voice script
1474 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1475 .float voicescript_nextthink; // time to play next voice
1476 .float voicescript_voiceend; // time when this voice ends
1478 void target_voicescript_clear(entity pl)
1480 pl.voicescript = world;
1483 void target_voicescript_use()
1485 if(activator.voicescript != self)
1487 activator.voicescript = self;
1488 activator.voicescript_index = 0;
1489 activator.voicescript_nextthink = time + self.delay;
1493 void target_voicescript_next(entity pl)
1498 vs = pl.voicescript;
1501 if(vs.message == "")
1503 if(pl.classname != "player")
1508 if(time >= pl.voicescript_voiceend)
1510 if(time >= pl.voicescript_nextthink)
1512 // get the next voice...
1513 n = tokenize_sane(vs.message);
1515 if(pl.voicescript_index < vs.cnt)
1516 i = pl.voicescript_index * 2;
1517 else if(n > vs.cnt * 2)
1518 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1524 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1525 pl.voicescript_voiceend = time + stof(argv(i + 1));
1528 pl.voicescript = world;
1530 pl.voicescript_index += 1;
1531 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1536 void spawnfunc_target_voicescript()
1538 // netname: directory of the sound files
1539 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1540 // foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1541 // wait: average time between messages
1542 // delay: initial delay before the first message
1545 self.use = target_voicescript_use;
1547 n = tokenize_sane(self.message);
1549 for(i = 0; i+1 < n; i += 2)
1556 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1562 void trigger_relay_teamcheck_use()
1566 if(self.spawnflags & 2)
1568 if(activator.team != self.team)
1573 if(activator.team == self.team)
1579 if(self.spawnflags & 1)
1584 void trigger_relay_teamcheck_reset()
1586 self.team = self.team_saved;
1589 void spawnfunc_trigger_relay_teamcheck()
1591 self.team_saved = self.team;
1592 self.use = trigger_relay_teamcheck_use;
1593 self.reset = trigger_relay_teamcheck_reset;
1598 void trigger_disablerelay_use()
1605 for(e = world; (e = find(e, targetname, self.target)); )
1607 if(e.use == SUB_UseTargets)
1609 e.use = SUB_DontUseTargets;
1612 else if(e.use == SUB_DontUseTargets)
1614 e.use = SUB_UseTargets;
1620 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1623 void spawnfunc_trigger_disablerelay()
1625 self.use = trigger_disablerelay_use;