1 void SUB_DontUseTargets()
10 activator = self.enemy;
16 ==============================
19 the global "activator" should be set to the entity that initiated the firing.
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
24 Centerprints any self.message to the activator.
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
32 ==============================
36 local entity t, stemp, otemp, act;
45 // create a temp object to fire at a later time
47 t.classname = "DelayedUse";
48 t.nextthink = time + self.delay;
51 t.message = self.message;
52 t.killtarget = self.killtarget;
53 t.target = self.target;
61 if (activator.classname == "player" && self.message != "")
63 if(clienttype(activator) == CLIENTTYPE_REAL)
65 centerprint (activator, self.message);
67 play2(activator, "misc/talk.wav");
72 // kill the killtagets
77 for(t = world; (t = find(t, targetname, s)); )
88 for(i = 0; i < 4; ++i)
93 case 0: s = stemp.target; break;
94 case 1: s = stemp.target2; break;
95 case 2: s = stemp.target3; break;
96 case 3: s = stemp.target4; break;
100 for(t = world; (t = find(t, targetname, s)); )
103 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
118 //=============================================================================
120 float SPAWNFLAG_NOMESSAGE = 1;
121 float SPAWNFLAG_NOTOUCH = 1;
123 // the wait time has passed, so set back up for another activation
128 self.health = self.max_health;
129 self.takedamage = DAMAGE_YES;
130 self.solid = SOLID_BBOX;
135 // the trigger was just touched/killed/used
136 // self.enemy should be set to the activator so it can be held through a delay
137 // so wait for the delay time before firing
140 if (self.nextthink > time)
142 return; // allready been triggered
145 if (self.classname == "trigger_secret")
147 if (self.enemy.classname != "player")
149 found_secrets = found_secrets + 1;
150 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
154 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
156 // don't trigger again until reset
157 self.takedamage = DAMAGE_NO;
159 activator = self.enemy;
160 other = self.goalentity;
165 self.think = multi_wait;
166 self.nextthink = time + self.wait;
168 else if (self.wait == 0)
170 multi_wait(); // waiting finished
173 { // we can't just remove (self) here, because this is a touch function
174 // called wheil C code is looping through area links...
175 self.touch = SUB_Null;
181 self.goalentity = other;
182 self.enemy = activator;
188 if not(self.spawnflags & 2)
190 if not(other.iscreature)
194 if(self.team == other.team)
198 // if the trigger has an angles field, check player's facing direction
199 if (self.movedir != '0 0 0')
201 makevectors (other.angles);
202 if (v_forward * self.movedir < 0)
203 return; // not facing the right way
209 self.goalentity = other;
213 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
215 if (!self.takedamage)
217 if(self.spawnflags & DOOR_NOSPLASH)
218 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
220 self.health = self.health - damage;
221 if (self.health <= 0)
223 self.enemy = attacker;
224 self.goalentity = inflictor;
231 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
232 self.touch = multi_touch;
235 self.health = self.max_health;
236 self.takedamage = DAMAGE_YES;
237 self.solid = SOLID_BBOX;
239 self.think = SUB_Null;
240 self.team = self.team_saved;
243 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
244 Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
245 If "delay" is set, the trigger waits some time after activating before firing.
246 "wait" : Seconds between triggerings. (.2 default)
247 If notouch is set, the trigger is only fired by other entities, not by touching.
248 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
254 set "message" to text string
256 void spawnfunc_trigger_multiple()
258 self.reset = multi_reset;
259 if (self.sounds == 1)
261 precache_sound ("misc/secret.wav");
262 self.noise = "misc/secret.wav";
264 else if (self.sounds == 2)
266 precache_sound ("misc/talk.wav");
267 self.noise = "misc/talk.wav";
269 else if (self.sounds == 3)
271 precache_sound ("misc/trigger1.wav");
272 self.noise = "misc/trigger1.wav";
277 else if(self.wait < -1)
279 self.use = multi_use;
283 self.team_saved = self.team;
287 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
288 objerror ("health and notouch don't make sense\n");
289 self.max_health = self.health;
290 self.event_damage = multi_eventdamage;
291 self.takedamage = DAMAGE_YES;
292 self.solid = SOLID_BBOX;
293 setorigin (self, self.origin); // make sure it links into the world
297 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
299 self.touch = multi_touch;
300 setorigin (self, self.origin); // make sure it links into the world
306 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
307 Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
308 "targetname". If "health" is set, the trigger must be killed to activate.
309 If notouch is set, the trigger is only fired by other entities, not by touching.
310 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
311 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
317 set "message" to text string
319 void spawnfunc_trigger_once()
322 spawnfunc_trigger_multiple();
325 //=============================================================================
327 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
328 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
330 void spawnfunc_trigger_relay()
332 self.use = SUB_UseTargets;
333 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
338 self.think = SUB_UseTargets;
339 self.nextthink = self.wait;
344 self.think = SUB_Null;
347 void spawnfunc_trigger_delay()
352 self.use = delay_use;
353 self.reset = delay_reset;
356 //=============================================================================
361 self.count = self.count - 1;
367 if (activator.classname == "player"
368 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
371 centerprint (activator, "There are more to go...");
372 else if (self.count == 3)
373 centerprint (activator, "Only 3 more to go...");
374 else if (self.count == 2)
375 centerprint (activator, "Only 2 more to go...");
377 centerprint (activator, "Only 1 more to go...");
382 if (activator.classname == "player"
383 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
384 centerprint(activator, "Sequence completed!");
385 self.enemy = activator;
391 self.count = self.cnt;
395 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
396 Acts as an intermediary for an action that takes multiple inputs.
398 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
400 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
402 void spawnfunc_trigger_counter()
407 self.cnt = self.count;
409 self.use = counter_use;
410 self.reset = counter_reset;
413 .float triggerhurttime;
414 void trigger_hurt_touch()
416 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
417 if (other.iscreature)
419 if (other.takedamage)
420 if (other.triggerhurttime < time)
423 other.triggerhurttime = time + 1;
424 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
431 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
434 other.pain_finished = min(other.pain_finished, time + 2);
436 else if (other.classname == "rune") // reset runes
439 other.nextthink = min(other.nextthink, time + 1);
447 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
448 Any object touching this will be hurt
449 set dmg to damage amount
452 .entity trigger_hurt_next;
453 entity trigger_hurt_last;
454 entity trigger_hurt_first;
455 void spawnfunc_trigger_hurt()
458 self.touch = trigger_hurt_touch;
462 self.message = "was in the wrong place";
464 self.message2 = "was thrown into a world of hurt by";
466 if(!trigger_hurt_first)
467 trigger_hurt_first = self;
468 if(trigger_hurt_last)
469 trigger_hurt_last.trigger_hurt_next = self;
470 trigger_hurt_last = self;
473 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
477 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
478 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
484 //////////////////////////////////////////////////////////////
488 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
490 //////////////////////////////////////////////////////////////
492 .float triggerhealtime;
493 void trigger_heal_touch()
495 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
496 if (other.iscreature)
498 if (other.takedamage)
499 if (other.triggerhealtime < time)
502 other.triggerhealtime = time + 1;
504 if (other.health < self.max_health)
506 other.health = min(other.health + self.health, self.max_health);
507 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
508 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
514 void spawnfunc_trigger_heal()
517 self.touch = trigger_heal_touch;
520 if (!self.max_health)
521 self.max_health = 200; //Max health topoff for field
523 self.noise = "misc/mediumhealth.wav";
524 precache_sound(self.noise);
528 //////////////////////////////////////////////////////////////
534 //////////////////////////////////////////////////////////////
536 .float triggergravity, triggergravitytime;
537 .entity trigger_gravity_check;
538 void trigger_gravity_check_think()
540 if(self.owner.triggergravitytime < time + 0.1) // need to figure out a correct formula here
542 dprint("XXXXXXXXXXXXXXXXXXXXXXXXXX ");
543 self.owner.gravity = 0;
544 self.nextthink = self.owner.triggergravity = 0;
549 void trigger_gravity_touch()
551 if(sv_gravity != 800)
553 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
554 if (other.triggergravitytime < time)
557 other.triggergravitytime = time + 1;
559 if(!other.triggergravity)
561 other.triggergravity = 1;
562 other.trigger_gravity_check = spawn();
563 other.trigger_gravity_check.owner = other;
564 other.trigger_gravity_check.think = trigger_gravity_check_think;
565 other.trigger_gravity_check.nextthink = time;
568 if (other.gravity != self.gravity)
570 other.gravity = self.gravity;
572 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
577 void spawnfunc_trigger_gravity()
582 self.touch = trigger_gravity_touch;
584 precache_sound(self.noise);
587 // TODO add a way to do looped sounds with sound(); then complete this entity
588 .float volume, atten;
589 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
591 void spawnfunc_target_speaker()
594 precache_sound (self.noise);
598 self.atten = ATTN_NORM;
599 else if(self.atten < 0)
603 self.use = target_speaker_use;
608 self.atten = ATTN_STATIC;
609 else if(self.atten < 0)
613 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
618 void spawnfunc_func_stardust() {
619 self.effects = EF_STARDUST;
623 .float bgmscriptattack;
624 .float bgmscriptdecay;
625 .float bgmscriptsustain;
626 .float bgmscriptrelease;
627 float pointparticles_SendEntity(entity to, float fl)
629 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
631 // optional features to save space
633 if(self.spawnflags & 2)
634 fl |= 0x10; // absolute count on toggle-on
635 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
636 fl |= 0x20; // 4 bytes - saves CPU
637 if(self.waterlevel || self.count != 1)
638 fl |= 0x40; // 4 bytes - obscure features almost never used
639 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
640 fl |= 0x80; // 14 bytes - saves lots of space
642 WriteByte(MSG_ENTITY, fl);
646 WriteCoord(MSG_ENTITY, self.impulse);
648 WriteCoord(MSG_ENTITY, 0); // off
652 WriteCoord(MSG_ENTITY, self.origin_x);
653 WriteCoord(MSG_ENTITY, self.origin_y);
654 WriteCoord(MSG_ENTITY, self.origin_z);
658 if(self.model != "null")
660 WriteShort(MSG_ENTITY, self.modelindex);
663 WriteCoord(MSG_ENTITY, self.mins_x);
664 WriteCoord(MSG_ENTITY, self.mins_y);
665 WriteCoord(MSG_ENTITY, self.mins_z);
666 WriteCoord(MSG_ENTITY, self.maxs_x);
667 WriteCoord(MSG_ENTITY, self.maxs_y);
668 WriteCoord(MSG_ENTITY, self.maxs_z);
673 WriteShort(MSG_ENTITY, 0);
676 WriteCoord(MSG_ENTITY, self.maxs_x);
677 WriteCoord(MSG_ENTITY, self.maxs_y);
678 WriteCoord(MSG_ENTITY, self.maxs_z);
681 WriteShort(MSG_ENTITY, self.cnt);
684 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
685 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
689 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
690 WriteByte(MSG_ENTITY, self.count * 16.0);
692 WriteString(MSG_ENTITY, self.noise);
695 WriteByte(MSG_ENTITY, floor(self.atten * 64));
696 WriteByte(MSG_ENTITY, floor(self.volume * 255));
698 WriteString(MSG_ENTITY, self.bgmscript);
699 if(self.bgmscript != "")
701 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
702 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
703 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
704 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
710 void pointparticles_use()
712 self.state = !self.state;
716 void pointparticles_think()
718 if(self.origin != self.oldorigin)
721 self.oldorigin = self.origin;
723 self.nextthink = time;
726 void pointparticles_reset()
728 if(self.spawnflags & 1)
734 void spawnfunc_func_pointparticles()
737 setmodel(self, self.model);
739 precache_sound (self.noise);
741 if(!self.bgmscriptsustain)
742 self.bgmscriptsustain = 1;
743 else if(self.bgmscriptsustain < 0)
744 self.bgmscriptsustain = 0;
747 self.atten = ATTN_NORM;
748 else if(self.atten < 0)
759 setorigin(self, self.origin + self.mins);
760 setsize(self, '0 0 0', self.maxs - self.mins);
763 self.cnt = particleeffectnum(self.mdl);
765 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
769 self.use = pointparticles_use;
770 self.reset = pointparticles_reset;
775 self.think = pointparticles_think;
776 self.nextthink = time;
779 void spawnfunc_func_sparks()
781 // self.cnt is the amount of sparks that one burst will spawn
783 self.cnt = 25.0; // nice default value
786 // self.wait is the probability that a sparkthink will spawn a spark shower
787 // range: 0 - 1, but 0 makes little sense, so...
788 if(self.wait < 0.05) {
789 self.wait = 0.25; // nice default value
792 self.count = self.cnt;
795 self.velocity = '0 0 -1';
796 self.mdl = "TE_SPARK";
797 self.impulse = 10 * self.wait; // by default 2.5/sec
799 self.cnt = 0; // use mdl
801 spawnfunc_func_pointparticles();
804 float rainsnow_SendEntity(entity to, float sf)
806 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
807 WriteByte(MSG_ENTITY, self.state);
808 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
809 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
810 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
811 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
812 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
813 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
814 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
815 WriteShort(MSG_ENTITY, self.count);
816 WriteByte(MSG_ENTITY, self.cnt);
820 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
821 This is an invisible area like a trigger, which rain falls inside of.
825 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
827 sets color of rain (default 12 - white)
829 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
831 void spawnfunc_func_rain()
833 self.dest = self.velocity;
834 self.velocity = '0 0 0';
836 self.dest = '0 0 -700';
837 self.angles = '0 0 0';
838 self.movetype = MOVETYPE_NONE;
839 self.solid = SOLID_NOT;
840 SetBrushEntityModel();
845 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
848 if(self.count > 65535)
851 self.state = 1; // 1 is rain, 0 is snow
854 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
858 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
859 This is an invisible area like a trigger, which snow falls inside of.
863 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
865 sets color of rain (default 12 - white)
867 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
869 void spawnfunc_func_snow()
871 self.dest = self.velocity;
872 self.velocity = '0 0 0';
874 self.dest = '0 0 -300';
875 self.angles = '0 0 0';
876 self.movetype = MOVETYPE_NONE;
877 self.solid = SOLID_NOT;
878 SetBrushEntityModel();
883 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
886 if(self.count > 65535)
889 self.state = 0; // 1 is rain, 0 is snow
892 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
896 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
899 void misc_laser_aim()
904 if(self.spawnflags & 2)
906 if(self.enemy.origin != self.mangle)
908 self.mangle = self.enemy.origin;
914 a = vectoangles(self.enemy.origin - self.origin);
925 if(self.angles != self.mangle)
927 self.mangle = self.angles;
931 if(self.origin != self.oldorigin)
934 self.oldorigin = self.origin;
938 void misc_laser_init()
940 if(self.target != "")
941 self.enemy = find(world, targetname, self.target);
945 void misc_laser_think()
950 self.nextthink = time;
959 o = self.enemy.origin;
960 if not(self.spawnflags & 2)
961 o = self.origin + normalize(o - self.origin) * 32768;
965 makevectors(self.mangle);
966 o = self.origin + v_forward * 32768;
972 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
974 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
977 if(self.enemy.target != "") // DETECTOR laser
979 traceline(self.origin, o, MOVE_NORMAL, self);
980 if(trace_ent.iscreature)
982 self.pusher = trace_ent;
989 activator = self.pusher;
1002 activator = self.pusher;
1010 float laser_SendEntity(entity to, float fl)
1012 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1013 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1014 if(self.spawnflags & 2)
1018 if(self.scale != 1 || self.modelscale != 1)
1020 WriteByte(MSG_ENTITY, fl);
1023 WriteCoord(MSG_ENTITY, self.origin_x);
1024 WriteCoord(MSG_ENTITY, self.origin_y);
1025 WriteCoord(MSG_ENTITY, self.origin_z);
1029 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1030 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1031 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1033 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1036 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1037 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1039 WriteShort(MSG_ENTITY, self.cnt + 1);
1045 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1046 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1047 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1051 WriteAngle(MSG_ENTITY, self.mangle_x);
1052 WriteAngle(MSG_ENTITY, self.mangle_y);
1056 WriteByte(MSG_ENTITY, self.state);
1060 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1061 Any object touching the beam will be hurt
1064 spawnfunc_target_position where the laser ends
1066 name of beam end effect to use
1068 color of the beam (default: red)
1070 damage per second (-1 for a laser that kills immediately)
1074 self.state = !self.state;
1075 self.SendFlags |= 4;
1081 if(self.spawnflags & 1)
1087 void spawnfunc_misc_laser()
1091 if(self.mdl == "none")
1095 self.cnt = particleeffectnum(self.mdl);
1098 self.cnt = particleeffectnum("laser_deadly");
1104 self.cnt = particleeffectnum("laser_deadly");
1111 if(self.colormod == '0 0 0')
1113 self.colormod = '1 0 0';
1115 self.message = "saw the light";
1117 self.message2 = "was pushed into a laser by";
1120 if(!self.modelscale)
1121 self.modelscale = 1;
1122 self.think = misc_laser_think;
1123 self.nextthink = time;
1124 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1126 self.mangle = self.angles;
1128 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1132 self.reset = laser_reset;
1134 self.use = laser_use;
1140 // tZorks trigger impulse / gravity
1144 .float lastpushtime;
1146 // targeted (directional) mode
1147 void trigger_impulse_touch1()
1150 float pushdeltatime;
1153 // FIXME: Better checking for what to push and not.
1154 if not(other.iscreature)
1155 if (other.classname != "corpse")
1156 if (other.classname != "body")
1157 if (other.classname != "gib")
1158 if (other.classname != "missile")
1159 if (other.classname != "rocket")
1160 if (other.classname != "casing")
1161 if (other.classname != "grenade")
1162 if (other.classname != "plasma")
1163 if (other.classname != "plasma_prim")
1164 if (other.classname != "plasma_chain")
1165 if (other.classname != "droppedweapon")
1166 if (other.classname != "nexball_basketball")
1167 if (other.classname != "nexball_football")
1170 if (other.deadflag && other.iscreature)
1175 targ = find(world, targetname, self.target);
1178 objerror("trigger_force without a (valid) .target!\n");
1183 if(self.falloff == 1)
1184 str = (str / self.radius) * self.strength;
1185 else if(self.falloff == 2)
1186 str = (1 - (str / self.radius)) * self.strength;
1188 str = self.strength;
1190 pushdeltatime = time - other.lastpushtime;
1191 if (pushdeltatime > 0.15) pushdeltatime = 0;
1192 other.lastpushtime = time;
1193 if(!pushdeltatime) return;
1195 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1196 other.flags &~= FL_ONGROUND;
1199 // Directionless (accelerator/decelerator) mode
1200 void trigger_impulse_touch2()
1202 float pushdeltatime;
1204 // FIXME: Better checking for what to push and not.
1205 if not(other.iscreature)
1206 if (other.classname != "corpse")
1207 if (other.classname != "body")
1208 if (other.classname != "gib")
1209 if (other.classname != "missile")
1210 if (other.classname != "rocket")
1211 if (other.classname != "casing")
1212 if (other.classname != "grenade")
1213 if (other.classname != "plasma")
1214 if (other.classname != "plasma_prim")
1215 if (other.classname != "plasma_chain")
1216 if (other.classname != "droppedweapon")
1217 if (other.classname != "nexball_basketball")
1218 if (other.classname != "nexball_football")
1221 if (other.deadflag && other.iscreature)
1226 pushdeltatime = time - other.lastpushtime;
1227 if (pushdeltatime > 0.15) pushdeltatime = 0;
1228 other.lastpushtime = time;
1229 if(!pushdeltatime) return;
1231 // div0: ticrate independent, 1 = identity (not 20)
1232 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1235 // Spherical (gravity/repulsor) mode
1236 void trigger_impulse_touch3()
1238 float pushdeltatime;
1241 // FIXME: Better checking for what to push and not.
1242 if not(other.iscreature)
1243 if (other.classname != "corpse")
1244 if (other.classname != "body")
1245 if (other.classname != "gib")
1246 if (other.classname != "missile")
1247 if (other.classname != "rocket")
1248 if (other.classname != "casing")
1249 if (other.classname != "grenade")
1250 if (other.classname != "plasma")
1251 if (other.classname != "plasma_prim")
1252 if (other.classname != "plasma_chain")
1253 if (other.classname != "droppedweapon")
1254 if (other.classname != "nexball_basketball")
1255 if (other.classname != "nexball_football")
1258 if (other.deadflag && other.iscreature)
1263 pushdeltatime = time - other.lastpushtime;
1264 if (pushdeltatime > 0.15) pushdeltatime = 0;
1265 other.lastpushtime = time;
1266 if(!pushdeltatime) return;
1268 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1270 str = min(self.radius, vlen(self.origin - other.origin));
1272 if(self.falloff == 1)
1273 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1274 else if(self.falloff == 2)
1275 str = (str / self.radius) * self.strength; // 0 in the inside
1277 str = self.strength;
1279 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1282 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1283 -------- KEYS --------
1284 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1285 If not, this trigger acts like a damper/accelerator field.
1287 strength : This is how mutch force to add in the direction of .target each second
1288 when .target is set. If not, this is hoe mutch to slow down/accelerate
1289 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1291 radius : If set, act as a spherical device rather then a liniar one.
1293 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1295 -------- NOTES --------
1296 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1297 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1300 void spawnfunc_trigger_impulse()
1305 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1306 setorigin(self, self.origin);
1307 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1308 self.touch = trigger_impulse_touch3;
1314 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1315 self.touch = trigger_impulse_touch1;
1319 if(!self.strength) self.strength = 0.9;
1320 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1321 self.touch = trigger_impulse_touch2;
1326 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1327 "Flip-flop" trigger gate... lets only every second trigger event through
1331 self.state = !self.state;
1336 void spawnfunc_trigger_flipflop()
1338 if(self.spawnflags & 1)
1340 self.use = flipflop_use;
1341 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1344 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1345 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1349 self.nextthink = time + self.wait;
1350 self.enemy = activator;
1356 void monoflop_fixed_use()
1360 self.nextthink = time + self.wait;
1362 self.enemy = activator;
1366 void monoflop_think()
1369 activator = self.enemy;
1373 void monoflop_reset()
1379 void spawnfunc_trigger_monoflop()
1383 if(self.spawnflags & 1)
1384 self.use = monoflop_fixed_use;
1386 self.use = monoflop_use;
1387 self.think = monoflop_think;
1389 self.reset = monoflop_reset;
1392 void multivibrator_send()
1397 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1399 newstate = (time < cyclestart + self.wait);
1402 if(self.state != newstate)
1404 self.state = newstate;
1407 self.nextthink = cyclestart + self.wait + 0.01;
1409 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1412 void multivibrator_toggle()
1414 if(self.nextthink == 0)
1416 multivibrator_send();
1429 void multivibrator_reset()
1431 if(!(self.spawnflags & 1))
1432 self.nextthink = 0; // wait for a trigger event
1434 self.nextthink = max(1, time);
1437 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1438 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1439 -------- KEYS --------
1440 target: trigger all entities with this targetname when it goes off
1441 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1442 phase: offset of the timing
1443 wait: "on" cycle time (default: 1)
1444 respawntime: "off" cycle time (default: same as wait)
1445 -------- SPAWNFLAGS --------
1446 START_ON: assume it is already turned on (when targeted)
1448 void spawnfunc_trigger_multivibrator()
1452 if(!self.respawntime)
1453 self.respawntime = self.wait;
1456 self.use = multivibrator_toggle;
1457 self.think = multivibrator_send;
1458 self.nextthink = time;
1461 multivibrator_reset();
1470 if(self.killtarget != "")
1471 src = find(world, targetname, self.killtarget);
1472 if(self.target != "")
1473 dst = find(world, targetname, self.target);
1477 objerror("follow: could not find target/killtarget");
1483 // already done :P entity must stay
1487 else if(!src || !dst)
1489 objerror("follow: could not find target/killtarget");
1492 else if(self.spawnflags & 1)
1495 if(self.spawnflags & 2)
1497 setattachment(dst, src, self.message);
1501 attach_sameorigin(dst, src, self.message);
1508 if(self.spawnflags & 2)
1510 dst.movetype = MOVETYPE_FOLLOW;
1512 // dst.punchangle = '0 0 0'; // keep unchanged
1513 dst.view_ofs = dst.origin;
1514 dst.v_angle = dst.angles;
1518 follow_sameorigin(dst, src);
1525 void spawnfunc_misc_follow()
1527 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1532 void gamestart_use() {
1538 void spawnfunc_trigger_gamestart() {
1539 self.use = gamestart_use;
1540 self.reset2 = spawnfunc_trigger_gamestart;
1544 self.think = self.use;
1545 self.nextthink = game_starttime + self.wait;
1548 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1554 .entity voicescript; // attached voice script
1555 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1556 .float voicescript_nextthink; // time to play next voice
1557 .float voicescript_voiceend; // time when this voice ends
1559 void target_voicescript_clear(entity pl)
1561 pl.voicescript = world;
1564 void target_voicescript_use()
1566 if(activator.voicescript != self)
1568 activator.voicescript = self;
1569 activator.voicescript_index = 0;
1570 activator.voicescript_nextthink = time + self.delay;
1574 void target_voicescript_next(entity pl)
1579 vs = pl.voicescript;
1582 if(vs.message == "")
1584 if(pl.classname != "player")
1589 if(time >= pl.voicescript_voiceend)
1591 if(time >= pl.voicescript_nextthink)
1593 // get the next voice...
1594 n = tokenize_console(vs.message);
1596 if(pl.voicescript_index < vs.cnt)
1597 i = pl.voicescript_index * 2;
1598 else if(n > vs.cnt * 2)
1599 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1605 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1606 dt = stof(argv(i + 1));
1609 pl.voicescript_voiceend = time + dt;
1610 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1614 pl.voicescript_voiceend = time - dt;
1615 pl.voicescript_nextthink = pl.voicescript_voiceend;
1618 pl.voicescript_index += 1;
1622 pl.voicescript = world; // stop trying then
1628 void spawnfunc_target_voicescript()
1630 // netname: directory of the sound files
1631 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1632 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1633 // Here, a - in front of the duration means that no delay is to be
1634 // added after this message
1635 // wait: average time between messages
1636 // delay: initial delay before the first message
1639 self.use = target_voicescript_use;
1641 n = tokenize_console(self.message);
1643 for(i = 0; i+1 < n; i += 2)
1650 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1656 void trigger_relay_teamcheck_use()
1660 if(self.spawnflags & 2)
1662 if(activator.team != self.team)
1667 if(activator.team == self.team)
1673 if(self.spawnflags & 1)
1678 void trigger_relay_teamcheck_reset()
1680 self.team = self.team_saved;
1683 void spawnfunc_trigger_relay_teamcheck()
1685 self.team_saved = self.team;
1686 self.use = trigger_relay_teamcheck_use;
1687 self.reset = trigger_relay_teamcheck_reset;
1692 void trigger_disablerelay_use()
1699 for(e = world; (e = find(e, targetname, self.target)); )
1701 if(e.use == SUB_UseTargets)
1703 e.use = SUB_DontUseTargets;
1706 else if(e.use == SUB_DontUseTargets)
1708 e.use = SUB_UseTargets;
1714 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1717 void spawnfunc_trigger_disablerelay()
1719 self.use = trigger_disablerelay_use;
1722 float magicear_matched;
1723 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1725 float domatch, dotrigger, matchstart, l;
1729 magicear_matched = FALSE;
1731 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1732 domatch = ((ear.spawnflags & 32) || dotrigger);
1738 if(ear.spawnflags & 4)
1744 if(ear.spawnflags & 1)
1747 if(ear.spawnflags & 2)
1750 if(ear.spawnflags & 8)
1755 l = strlen(ear.message);
1757 if(self.spawnflags & 128)
1760 msg = strdecolorize(msgin);
1762 if(substring(ear.message, 0, 1) == "*")
1764 if(substring(ear.message, -1, 1) == "*")
1767 // as we need multi-replacement here...
1768 s = substring(ear.message, 1, -2);
1770 if(strstrofs(msg, s, 0) >= 0)
1771 matchstart = -2; // we use strreplace on s
1776 s = substring(ear.message, 1, -1);
1778 if(substring(msg, -l, l) == s)
1779 matchstart = strlen(msg) - l;
1784 if(substring(ear.message, -1, 1) == "*")
1787 s = substring(ear.message, 0, -2);
1789 if(substring(msg, 0, l) == s)
1796 if(msg == ear.message)
1801 if(matchstart == -1) // no match
1804 magicear_matched = TRUE;
1808 oldself = activator = self;
1814 if(ear.spawnflags & 16)
1818 else if(ear.netname != "")
1821 return strreplace(s, ear.netname, msg);
1824 substring(msg, 0, matchstart),
1826 substring(msg, matchstart + l, -1)
1834 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1838 for(ear = magicears; ear; ear = ear.enemy)
1840 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1841 if not(ear.spawnflags & 64)
1842 if(magicear_matched)
1849 void spawnfunc_trigger_magicear()
1851 self.enemy = magicears;
1854 // actually handled in "say" processing
1857 // 2 = ignore teamsay
1859 // 8 = ignore tell to unknown player
1860 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1861 // 32 = perform the replacement even if outside the radius or dead
1862 // 64 = continue replacing/triggering even if this one matched
1872 // if set, replacement for the matched text
1874 // "hearing distance"