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 //////////////////////////////////////////////////////////////
538 // TODO add a way to do looped sounds with sound(); then complete this entity
539 .float volume, atten;
540 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
542 void spawnfunc_target_speaker()
545 precache_sound (self.noise);
549 self.atten = ATTN_NORM;
550 else if(self.atten < 0)
554 self.use = target_speaker_use;
559 self.atten = ATTN_STATIC;
560 else if(self.atten < 0)
564 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
569 void spawnfunc_func_stardust() {
570 self.effects = EF_STARDUST;
574 .float bgmscriptattack;
575 .float bgmscriptdecay;
576 .float bgmscriptsustain;
577 .float bgmscriptrelease;
578 float pointparticles_SendEntity(entity to, float fl)
580 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
582 // optional features to save space
584 if(self.spawnflags & 2)
585 fl |= 0x10; // absolute count on toggle-on
586 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
587 fl |= 0x20; // 4 bytes - saves CPU
588 if(self.waterlevel || self.count != 1)
589 fl |= 0x40; // 4 bytes - obscure features almost never used
590 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
591 fl |= 0x80; // 14 bytes - saves lots of space
593 WriteByte(MSG_ENTITY, fl);
597 WriteCoord(MSG_ENTITY, self.impulse);
599 WriteCoord(MSG_ENTITY, 0); // off
603 WriteCoord(MSG_ENTITY, self.origin_x);
604 WriteCoord(MSG_ENTITY, self.origin_y);
605 WriteCoord(MSG_ENTITY, self.origin_z);
609 if(self.model != "null")
611 WriteShort(MSG_ENTITY, self.modelindex);
614 WriteCoord(MSG_ENTITY, self.mins_x);
615 WriteCoord(MSG_ENTITY, self.mins_y);
616 WriteCoord(MSG_ENTITY, self.mins_z);
617 WriteCoord(MSG_ENTITY, self.maxs_x);
618 WriteCoord(MSG_ENTITY, self.maxs_y);
619 WriteCoord(MSG_ENTITY, self.maxs_z);
624 WriteShort(MSG_ENTITY, 0);
627 WriteCoord(MSG_ENTITY, self.maxs_x);
628 WriteCoord(MSG_ENTITY, self.maxs_y);
629 WriteCoord(MSG_ENTITY, self.maxs_z);
632 WriteShort(MSG_ENTITY, self.cnt);
635 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
636 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
640 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
641 WriteByte(MSG_ENTITY, self.count * 16.0);
643 WriteString(MSG_ENTITY, self.noise);
646 WriteByte(MSG_ENTITY, floor(self.atten * 64));
647 WriteByte(MSG_ENTITY, floor(self.volume * 255));
649 WriteString(MSG_ENTITY, self.bgmscript);
650 if(self.bgmscript != "")
652 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
653 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
654 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
655 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
661 void pointparticles_use()
663 self.state = !self.state;
667 void pointparticles_think()
669 if(self.origin != self.oldorigin)
672 self.oldorigin = self.origin;
674 self.nextthink = time;
677 void pointparticles_reset()
679 if(self.spawnflags & 1)
685 void spawnfunc_func_pointparticles()
688 setmodel(self, self.model);
690 precache_sound (self.noise);
692 if(!self.bgmscriptsustain)
693 self.bgmscriptsustain = 1;
694 else if(self.bgmscriptsustain < 0)
695 self.bgmscriptsustain = 0;
698 self.atten = ATTN_NORM;
699 else if(self.atten < 0)
710 setorigin(self, self.origin + self.mins);
711 setsize(self, '0 0 0', self.maxs - self.mins);
714 self.cnt = particleeffectnum(self.mdl);
716 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
720 self.use = pointparticles_use;
721 self.reset = pointparticles_reset;
726 self.think = pointparticles_think;
727 self.nextthink = time;
730 void spawnfunc_func_sparks()
732 // self.cnt is the amount of sparks that one burst will spawn
734 self.cnt = 25.0; // nice default value
737 // self.wait is the probability that a sparkthink will spawn a spark shower
738 // range: 0 - 1, but 0 makes little sense, so...
739 if(self.wait < 0.05) {
740 self.wait = 0.25; // nice default value
743 self.count = self.cnt;
746 self.velocity = '0 0 -1';
747 self.mdl = "TE_SPARK";
748 self.impulse = 10 * self.wait; // by default 2.5/sec
750 self.cnt = 0; // use mdl
752 spawnfunc_func_pointparticles();
755 float rainsnow_SendEntity(entity to, float sf)
757 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
758 WriteByte(MSG_ENTITY, self.state);
759 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
760 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
761 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
762 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
763 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
764 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
765 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
766 WriteShort(MSG_ENTITY, self.count);
767 WriteByte(MSG_ENTITY, self.cnt);
771 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
772 This is an invisible area like a trigger, which rain falls inside of.
776 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
778 sets color of rain (default 12 - white)
780 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
782 void spawnfunc_func_rain()
784 self.dest = self.velocity;
785 self.velocity = '0 0 0';
787 self.dest = '0 0 -700';
788 self.angles = '0 0 0';
789 self.movetype = MOVETYPE_NONE;
790 self.solid = SOLID_NOT;
791 SetBrushEntityModel();
796 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
799 if(self.count > 65535)
802 self.state = 1; // 1 is rain, 0 is snow
805 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
809 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
810 This is an invisible area like a trigger, which snow falls inside of.
814 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
816 sets color of rain (default 12 - white)
818 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
820 void spawnfunc_func_snow()
822 self.dest = self.velocity;
823 self.velocity = '0 0 0';
825 self.dest = '0 0 -300';
826 self.angles = '0 0 0';
827 self.movetype = MOVETYPE_NONE;
828 self.solid = SOLID_NOT;
829 SetBrushEntityModel();
834 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
837 if(self.count > 65535)
840 self.state = 0; // 1 is rain, 0 is snow
843 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
847 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
850 void misc_laser_aim()
855 if(self.spawnflags & 2)
857 if(self.enemy.origin != self.mangle)
859 self.mangle = self.enemy.origin;
865 a = vectoangles(self.enemy.origin - self.origin);
876 if(self.angles != self.mangle)
878 self.mangle = self.angles;
882 if(self.origin != self.oldorigin)
885 self.oldorigin = self.origin;
889 void misc_laser_init()
891 if(self.target != "")
892 self.enemy = find(world, targetname, self.target);
896 void misc_laser_think()
901 self.nextthink = time;
910 o = self.enemy.origin;
911 if not(self.spawnflags & 2)
912 o = self.origin + normalize(o - self.origin) * 32768;
916 makevectors(self.mangle);
917 o = self.origin + v_forward * 32768;
923 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
925 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
928 if(self.enemy.target != "") // DETECTOR laser
930 traceline(self.origin, o, MOVE_NORMAL, self);
931 if(trace_ent.iscreature)
933 self.pusher = trace_ent;
940 activator = self.pusher;
953 activator = self.pusher;
961 float laser_SendEntity(entity to, float fl)
963 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
964 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
965 if(self.spawnflags & 2)
969 if(self.scale != 1 || self.modelscale != 1)
971 WriteByte(MSG_ENTITY, fl);
974 WriteCoord(MSG_ENTITY, self.origin_x);
975 WriteCoord(MSG_ENTITY, self.origin_y);
976 WriteCoord(MSG_ENTITY, self.origin_z);
980 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
981 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
982 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
984 WriteByte(MSG_ENTITY, self.alpha * 255.0);
987 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
988 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
990 WriteShort(MSG_ENTITY, self.cnt + 1);
996 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
997 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
998 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1002 WriteAngle(MSG_ENTITY, self.mangle_x);
1003 WriteAngle(MSG_ENTITY, self.mangle_y);
1007 WriteByte(MSG_ENTITY, self.state);
1011 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1012 Any object touching the beam will be hurt
1015 spawnfunc_target_position where the laser ends
1017 name of beam end effect to use
1019 color of the beam (default: red)
1021 damage per second (-1 for a laser that kills immediately)
1025 self.state = !self.state;
1026 self.SendFlags |= 4;
1032 if(self.spawnflags & 1)
1038 void spawnfunc_misc_laser()
1042 if(self.mdl == "none")
1046 self.cnt = particleeffectnum(self.mdl);
1049 self.cnt = particleeffectnum("laser_deadly");
1055 self.cnt = particleeffectnum("laser_deadly");
1062 if(self.colormod == '0 0 0')
1064 self.colormod = '1 0 0';
1066 self.message = "saw the light";
1068 self.message2 = "was pushed into a laser by";
1071 if(!self.modelscale)
1072 self.modelscale = 1;
1073 self.think = misc_laser_think;
1074 self.nextthink = time;
1075 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1077 self.mangle = self.angles;
1079 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1083 self.reset = laser_reset;
1085 self.use = laser_use;
1091 // tZorks trigger impulse / gravity
1095 .float lastpushtime;
1097 // targeted (directional) mode
1098 void trigger_impulse_touch1()
1101 float pushdeltatime;
1104 // FIXME: Better checking for what to push and not.
1105 if not(other.iscreature)
1106 if (other.classname != "corpse")
1107 if (other.classname != "body")
1108 if (other.classname != "gib")
1109 if (other.classname != "missile")
1110 if (other.classname != "rocket")
1111 if (other.classname != "casing")
1112 if (other.classname != "grenade")
1113 if (other.classname != "plasma")
1114 if (other.classname != "plasma_prim")
1115 if (other.classname != "plasma_chain")
1116 if (other.classname != "droppedweapon")
1117 if (other.classname != "nexball_basketball")
1118 if (other.classname != "nexball_football")
1121 if (other.deadflag && other.iscreature)
1126 targ = find(world, targetname, self.target);
1129 objerror("trigger_force without a (valid) .target!\n");
1134 if(self.falloff == 1)
1135 str = (str / self.radius) * self.strength;
1136 else if(self.falloff == 2)
1137 str = (1 - (str / self.radius)) * self.strength;
1139 str = self.strength;
1141 pushdeltatime = time - other.lastpushtime;
1142 if (pushdeltatime > 0.15) pushdeltatime = 0;
1143 other.lastpushtime = time;
1144 if(!pushdeltatime) return;
1146 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1147 other.flags &~= FL_ONGROUND;
1150 // Directionless (accelerator/decelerator) mode
1151 void trigger_impulse_touch2()
1153 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 pushdeltatime = time - other.lastpushtime;
1178 if (pushdeltatime > 0.15) pushdeltatime = 0;
1179 other.lastpushtime = time;
1180 if(!pushdeltatime) return;
1182 // div0: ticrate independent, 1 = identity (not 20)
1183 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1186 // Spherical (gravity/repulsor) mode
1187 void trigger_impulse_touch3()
1189 float pushdeltatime;
1192 // FIXME: Better checking for what to push and not.
1193 if not(other.iscreature)
1194 if (other.classname != "corpse")
1195 if (other.classname != "body")
1196 if (other.classname != "gib")
1197 if (other.classname != "missile")
1198 if (other.classname != "rocket")
1199 if (other.classname != "casing")
1200 if (other.classname != "grenade")
1201 if (other.classname != "plasma")
1202 if (other.classname != "plasma_prim")
1203 if (other.classname != "plasma_chain")
1204 if (other.classname != "droppedweapon")
1205 if (other.classname != "nexball_basketball")
1206 if (other.classname != "nexball_football")
1209 if (other.deadflag && other.iscreature)
1214 pushdeltatime = time - other.lastpushtime;
1215 if (pushdeltatime > 0.15) pushdeltatime = 0;
1216 other.lastpushtime = time;
1217 if(!pushdeltatime) return;
1219 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1221 str = min(self.radius, vlen(self.origin - other.origin));
1223 if(self.falloff == 1)
1224 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1225 else if(self.falloff == 2)
1226 str = (str / self.radius) * self.strength; // 0 in the inside
1228 str = self.strength;
1230 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1233 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1234 -------- KEYS --------
1235 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1236 If not, this trigger acts like a damper/accelerator field.
1238 strength : This is how mutch force to add in the direction of .target each second
1239 when .target is set. If not, this is hoe mutch to slow down/accelerate
1240 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1242 radius : If set, act as a spherical device rather then a liniar one.
1244 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1246 -------- NOTES --------
1247 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1248 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1251 void spawnfunc_trigger_impulse()
1256 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1257 setorigin(self, self.origin);
1258 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1259 self.touch = trigger_impulse_touch3;
1265 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1266 self.touch = trigger_impulse_touch1;
1270 if(!self.strength) self.strength = 0.9;
1271 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1272 self.touch = trigger_impulse_touch2;
1277 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1278 "Flip-flop" trigger gate... lets only every second trigger event through
1282 self.state = !self.state;
1287 void spawnfunc_trigger_flipflop()
1289 if(self.spawnflags & 1)
1291 self.use = flipflop_use;
1292 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1295 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1296 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1300 self.nextthink = time + self.wait;
1301 self.enemy = activator;
1307 void monoflop_fixed_use()
1311 self.nextthink = time + self.wait;
1313 self.enemy = activator;
1317 void monoflop_think()
1320 activator = self.enemy;
1324 void monoflop_reset()
1330 void spawnfunc_trigger_monoflop()
1334 if(self.spawnflags & 1)
1335 self.use = monoflop_fixed_use;
1337 self.use = monoflop_use;
1338 self.think = monoflop_think;
1340 self.reset = monoflop_reset;
1343 void multivibrator_send()
1348 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1350 newstate = (time < cyclestart + self.wait);
1353 if(self.state != newstate)
1355 self.state = newstate;
1358 self.nextthink = cyclestart + self.wait + 0.01;
1360 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1363 void multivibrator_toggle()
1365 if(self.nextthink == 0)
1367 multivibrator_send();
1380 void multivibrator_reset()
1382 if(!(self.spawnflags & 1))
1383 self.nextthink = 0; // wait for a trigger event
1385 self.nextthink = max(1, time);
1388 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1389 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1390 -------- KEYS --------
1391 target: trigger all entities with this targetname when it goes off
1392 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1393 phase: offset of the timing
1394 wait: "on" cycle time (default: 1)
1395 respawntime: "off" cycle time (default: same as wait)
1396 -------- SPAWNFLAGS --------
1397 START_ON: assume it is already turned on (when targeted)
1399 void spawnfunc_trigger_multivibrator()
1403 if(!self.respawntime)
1404 self.respawntime = self.wait;
1407 self.use = multivibrator_toggle;
1408 self.think = multivibrator_send;
1409 self.nextthink = time;
1412 multivibrator_reset();
1421 if(self.killtarget != "")
1422 src = find(world, targetname, self.killtarget);
1423 if(self.target != "")
1424 dst = find(world, targetname, self.target);
1428 objerror("follow: could not find target/killtarget");
1434 // already done :P entity must stay
1438 else if(!src || !dst)
1440 objerror("follow: could not find target/killtarget");
1443 else if(self.spawnflags & 1)
1446 if(self.spawnflags & 2)
1448 setattachment(dst, src, self.message);
1452 attach_sameorigin(dst, src, self.message);
1459 if(self.spawnflags & 2)
1461 dst.movetype = MOVETYPE_FOLLOW;
1463 // dst.punchangle = '0 0 0'; // keep unchanged
1464 dst.view_ofs = dst.origin;
1465 dst.v_angle = dst.angles;
1469 follow_sameorigin(dst, src);
1476 void spawnfunc_misc_follow()
1478 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1483 void gamestart_use() {
1489 void spawnfunc_trigger_gamestart() {
1490 self.use = gamestart_use;
1491 self.reset2 = spawnfunc_trigger_gamestart;
1495 self.think = self.use;
1496 self.nextthink = game_starttime + self.wait;
1499 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1505 .entity voicescript; // attached voice script
1506 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1507 .float voicescript_nextthink; // time to play next voice
1508 .float voicescript_voiceend; // time when this voice ends
1510 void target_voicescript_clear(entity pl)
1512 pl.voicescript = world;
1515 void target_voicescript_use()
1517 if(activator.voicescript != self)
1519 activator.voicescript = self;
1520 activator.voicescript_index = 0;
1521 activator.voicescript_nextthink = time + self.delay;
1525 void target_voicescript_next(entity pl)
1530 vs = pl.voicescript;
1533 if(vs.message == "")
1535 if(pl.classname != "player")
1540 if(time >= pl.voicescript_voiceend)
1542 if(time >= pl.voicescript_nextthink)
1544 // get the next voice...
1545 n = tokenize_console(vs.message);
1547 if(pl.voicescript_index < vs.cnt)
1548 i = pl.voicescript_index * 2;
1549 else if(n > vs.cnt * 2)
1550 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1556 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1557 dt = stof(argv(i + 1));
1560 pl.voicescript_voiceend = time + dt;
1561 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1565 pl.voicescript_voiceend = time - dt;
1566 pl.voicescript_nextthink = pl.voicescript_voiceend;
1569 pl.voicescript_index += 1;
1573 pl.voicescript = world; // stop trying then
1579 void spawnfunc_target_voicescript()
1581 // netname: directory of the sound files
1582 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1583 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1584 // Here, a - in front of the duration means that no delay is to be
1585 // added after this message
1586 // wait: average time between messages
1587 // delay: initial delay before the first message
1590 self.use = target_voicescript_use;
1592 n = tokenize_console(self.message);
1594 for(i = 0; i+1 < n; i += 2)
1601 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1607 void trigger_relay_teamcheck_use()
1611 if(self.spawnflags & 2)
1613 if(activator.team != self.team)
1618 if(activator.team == self.team)
1624 if(self.spawnflags & 1)
1629 void trigger_relay_teamcheck_reset()
1631 self.team = self.team_saved;
1634 void spawnfunc_trigger_relay_teamcheck()
1636 self.team_saved = self.team;
1637 self.use = trigger_relay_teamcheck_use;
1638 self.reset = trigger_relay_teamcheck_reset;
1643 void trigger_disablerelay_use()
1650 for(e = world; (e = find(e, targetname, self.target)); )
1652 if(e.use == SUB_UseTargets)
1654 e.use = SUB_DontUseTargets;
1657 else if(e.use == SUB_DontUseTargets)
1659 e.use = SUB_UseTargets;
1665 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1668 void spawnfunc_trigger_disablerelay()
1670 self.use = trigger_disablerelay_use;
1673 float magicear_matched;
1674 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1676 float domatch, dotrigger, matchstart, l;
1680 magicear_matched = FALSE;
1682 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1683 domatch = ((ear.spawnflags & 32) || dotrigger);
1689 if(ear.spawnflags & 4)
1695 if(ear.spawnflags & 1)
1698 if(ear.spawnflags & 2)
1701 if(ear.spawnflags & 8)
1706 l = strlen(ear.message);
1708 if(self.spawnflags & 128)
1711 msg = strdecolorize(msgin);
1713 if(substring(ear.message, 0, 1) == "*")
1715 if(substring(ear.message, -1, 1) == "*")
1718 // as we need multi-replacement here...
1719 s = substring(ear.message, 1, -2);
1721 if(strstrofs(msg, s, 0) >= 0)
1722 matchstart = -2; // we use strreplace on s
1727 s = substring(ear.message, 1, -1);
1729 if(substring(msg, -l, l) == s)
1730 matchstart = strlen(msg) - l;
1735 if(substring(ear.message, -1, 1) == "*")
1738 s = substring(ear.message, 0, -2);
1740 if(substring(msg, 0, l) == s)
1747 if(msg == ear.message)
1752 if(matchstart == -1) // no match
1755 magicear_matched = TRUE;
1759 oldself = activator = self;
1765 if(ear.spawnflags & 16)
1769 else if(ear.netname != "")
1772 return strreplace(s, ear.netname, msg);
1775 substring(msg, 0, matchstart),
1777 substring(msg, matchstart + l, -1)
1785 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1789 for(ear = magicears; ear; ear = ear.enemy)
1791 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1792 if not(ear.spawnflags & 64)
1793 if(magicear_matched)
1800 void spawnfunc_trigger_magicear()
1802 self.enemy = magicears;
1805 // actually handled in "say" processing
1808 // 2 = ignore teamsay
1810 // 8 = ignore tell to unknown player
1811 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1812 // 32 = perform the replacement even if outside the radius or dead
1813 // 64 = continue replacing/triggering even if this one matched
1823 // if set, replacement for the matched text
1825 // "hearing distance"