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, oldgravity;
537 .entity trigger_gravity_check;
538 void trigger_gravity_check_think()
540 // Entity that spawns when you enter a gravity zone, and checks if you left it
541 if(self.owner.triggergravitytime < time - 0.1) // need to figure out a correct formula here
543 dprint("XXXXXXXXXXXXXXXXXXXXXXXXXX ");
544 self.owner.gravity = self.owner.oldgravity;
545 self.owner.triggergravity = 0;
549 self.nextthink = time;
552 void trigger_gravity_touch()
554 if(sv_gravity != 800)
556 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
557 if (other.triggergravitytime < time)
560 other.triggergravitytime = time + 0.1;
562 if(!other.triggergravity)
564 other.triggergravity = 1;
565 other.trigger_gravity_check = spawn();
566 other.trigger_gravity_check.owner = other;
567 other.trigger_gravity_check.think = trigger_gravity_check_think;
568 other.trigger_gravity_check.nextthink = time;
571 if (other.gravity != self.gravity)
573 other.oldgravity = other.gravity;
574 other.gravity = self.gravity;
576 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
581 void spawnfunc_trigger_gravity()
586 self.touch = trigger_gravity_touch;
588 precache_sound(self.noise);
591 // TODO add a way to do looped sounds with sound(); then complete this entity
592 .float volume, atten;
593 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
595 void spawnfunc_target_speaker()
598 precache_sound (self.noise);
602 self.atten = ATTN_NORM;
603 else if(self.atten < 0)
607 self.use = target_speaker_use;
612 self.atten = ATTN_STATIC;
613 else if(self.atten < 0)
617 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
622 void spawnfunc_func_stardust() {
623 self.effects = EF_STARDUST;
627 .float bgmscriptattack;
628 .float bgmscriptdecay;
629 .float bgmscriptsustain;
630 .float bgmscriptrelease;
631 float pointparticles_SendEntity(entity to, float fl)
633 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
635 // optional features to save space
637 if(self.spawnflags & 2)
638 fl |= 0x10; // absolute count on toggle-on
639 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
640 fl |= 0x20; // 4 bytes - saves CPU
641 if(self.waterlevel || self.count != 1)
642 fl |= 0x40; // 4 bytes - obscure features almost never used
643 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
644 fl |= 0x80; // 14 bytes - saves lots of space
646 WriteByte(MSG_ENTITY, fl);
650 WriteCoord(MSG_ENTITY, self.impulse);
652 WriteCoord(MSG_ENTITY, 0); // off
656 WriteCoord(MSG_ENTITY, self.origin_x);
657 WriteCoord(MSG_ENTITY, self.origin_y);
658 WriteCoord(MSG_ENTITY, self.origin_z);
662 if(self.model != "null")
664 WriteShort(MSG_ENTITY, self.modelindex);
667 WriteCoord(MSG_ENTITY, self.mins_x);
668 WriteCoord(MSG_ENTITY, self.mins_y);
669 WriteCoord(MSG_ENTITY, self.mins_z);
670 WriteCoord(MSG_ENTITY, self.maxs_x);
671 WriteCoord(MSG_ENTITY, self.maxs_y);
672 WriteCoord(MSG_ENTITY, self.maxs_z);
677 WriteShort(MSG_ENTITY, 0);
680 WriteCoord(MSG_ENTITY, self.maxs_x);
681 WriteCoord(MSG_ENTITY, self.maxs_y);
682 WriteCoord(MSG_ENTITY, self.maxs_z);
685 WriteShort(MSG_ENTITY, self.cnt);
688 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
689 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
693 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
694 WriteByte(MSG_ENTITY, self.count * 16.0);
696 WriteString(MSG_ENTITY, self.noise);
699 WriteByte(MSG_ENTITY, floor(self.atten * 64));
700 WriteByte(MSG_ENTITY, floor(self.volume * 255));
702 WriteString(MSG_ENTITY, self.bgmscript);
703 if(self.bgmscript != "")
705 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
706 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
707 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
708 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
714 void pointparticles_use()
716 self.state = !self.state;
720 void pointparticles_think()
722 if(self.origin != self.oldorigin)
725 self.oldorigin = self.origin;
727 self.nextthink = time;
730 void pointparticles_reset()
732 if(self.spawnflags & 1)
738 void spawnfunc_func_pointparticles()
741 setmodel(self, self.model);
743 precache_sound (self.noise);
745 if(!self.bgmscriptsustain)
746 self.bgmscriptsustain = 1;
747 else if(self.bgmscriptsustain < 0)
748 self.bgmscriptsustain = 0;
751 self.atten = ATTN_NORM;
752 else if(self.atten < 0)
763 setorigin(self, self.origin + self.mins);
764 setsize(self, '0 0 0', self.maxs - self.mins);
767 self.cnt = particleeffectnum(self.mdl);
769 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
773 self.use = pointparticles_use;
774 self.reset = pointparticles_reset;
779 self.think = pointparticles_think;
780 self.nextthink = time;
783 void spawnfunc_func_sparks()
785 // self.cnt is the amount of sparks that one burst will spawn
787 self.cnt = 25.0; // nice default value
790 // self.wait is the probability that a sparkthink will spawn a spark shower
791 // range: 0 - 1, but 0 makes little sense, so...
792 if(self.wait < 0.05) {
793 self.wait = 0.25; // nice default value
796 self.count = self.cnt;
799 self.velocity = '0 0 -1';
800 self.mdl = "TE_SPARK";
801 self.impulse = 10 * self.wait; // by default 2.5/sec
803 self.cnt = 0; // use mdl
805 spawnfunc_func_pointparticles();
808 float rainsnow_SendEntity(entity to, float sf)
810 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
811 WriteByte(MSG_ENTITY, self.state);
812 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
813 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
814 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
815 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
816 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
817 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
818 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
819 WriteShort(MSG_ENTITY, self.count);
820 WriteByte(MSG_ENTITY, self.cnt);
824 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
825 This is an invisible area like a trigger, which rain falls inside of.
829 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
831 sets color of rain (default 12 - white)
833 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
835 void spawnfunc_func_rain()
837 self.dest = self.velocity;
838 self.velocity = '0 0 0';
840 self.dest = '0 0 -700';
841 self.angles = '0 0 0';
842 self.movetype = MOVETYPE_NONE;
843 self.solid = SOLID_NOT;
844 SetBrushEntityModel();
849 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
852 if(self.count > 65535)
855 self.state = 1; // 1 is rain, 0 is snow
858 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
862 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
863 This is an invisible area like a trigger, which snow falls inside of.
867 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
869 sets color of rain (default 12 - white)
871 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
873 void spawnfunc_func_snow()
875 self.dest = self.velocity;
876 self.velocity = '0 0 0';
878 self.dest = '0 0 -300';
879 self.angles = '0 0 0';
880 self.movetype = MOVETYPE_NONE;
881 self.solid = SOLID_NOT;
882 SetBrushEntityModel();
887 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
890 if(self.count > 65535)
893 self.state = 0; // 1 is rain, 0 is snow
896 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
900 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
903 void misc_laser_aim()
908 if(self.spawnflags & 2)
910 if(self.enemy.origin != self.mangle)
912 self.mangle = self.enemy.origin;
918 a = vectoangles(self.enemy.origin - self.origin);
929 if(self.angles != self.mangle)
931 self.mangle = self.angles;
935 if(self.origin != self.oldorigin)
938 self.oldorigin = self.origin;
942 void misc_laser_init()
944 if(self.target != "")
945 self.enemy = find(world, targetname, self.target);
949 void misc_laser_think()
954 self.nextthink = time;
963 o = self.enemy.origin;
964 if not(self.spawnflags & 2)
965 o = self.origin + normalize(o - self.origin) * 32768;
969 makevectors(self.mangle);
970 o = self.origin + v_forward * 32768;
976 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
978 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
981 if(self.enemy.target != "") // DETECTOR laser
983 traceline(self.origin, o, MOVE_NORMAL, self);
984 if(trace_ent.iscreature)
986 self.pusher = trace_ent;
993 activator = self.pusher;
1006 activator = self.pusher;
1014 float laser_SendEntity(entity to, float fl)
1016 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1017 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1018 if(self.spawnflags & 2)
1022 if(self.scale != 1 || self.modelscale != 1)
1024 WriteByte(MSG_ENTITY, fl);
1027 WriteCoord(MSG_ENTITY, self.origin_x);
1028 WriteCoord(MSG_ENTITY, self.origin_y);
1029 WriteCoord(MSG_ENTITY, self.origin_z);
1033 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1034 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1035 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1037 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1040 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1041 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1043 WriteShort(MSG_ENTITY, self.cnt + 1);
1049 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1050 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1051 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1055 WriteAngle(MSG_ENTITY, self.mangle_x);
1056 WriteAngle(MSG_ENTITY, self.mangle_y);
1060 WriteByte(MSG_ENTITY, self.state);
1064 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1065 Any object touching the beam will be hurt
1068 spawnfunc_target_position where the laser ends
1070 name of beam end effect to use
1072 color of the beam (default: red)
1074 damage per second (-1 for a laser that kills immediately)
1078 self.state = !self.state;
1079 self.SendFlags |= 4;
1085 if(self.spawnflags & 1)
1091 void spawnfunc_misc_laser()
1095 if(self.mdl == "none")
1099 self.cnt = particleeffectnum(self.mdl);
1102 self.cnt = particleeffectnum("laser_deadly");
1108 self.cnt = particleeffectnum("laser_deadly");
1115 if(self.colormod == '0 0 0')
1117 self.colormod = '1 0 0';
1119 self.message = "saw the light";
1121 self.message2 = "was pushed into a laser by";
1124 if(!self.modelscale)
1125 self.modelscale = 1;
1126 self.think = misc_laser_think;
1127 self.nextthink = time;
1128 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1130 self.mangle = self.angles;
1132 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1136 self.reset = laser_reset;
1138 self.use = laser_use;
1144 // tZorks trigger impulse / gravity
1148 .float lastpushtime;
1150 // targeted (directional) mode
1151 void trigger_impulse_touch1()
1154 float pushdeltatime;
1157 // FIXME: Better checking for what to push and not.
1158 if not(other.iscreature)
1159 if (other.classname != "corpse")
1160 if (other.classname != "body")
1161 if (other.classname != "gib")
1162 if (other.classname != "missile")
1163 if (other.classname != "rocket")
1164 if (other.classname != "casing")
1165 if (other.classname != "grenade")
1166 if (other.classname != "plasma")
1167 if (other.classname != "plasma_prim")
1168 if (other.classname != "plasma_chain")
1169 if (other.classname != "droppedweapon")
1170 if (other.classname != "nexball_basketball")
1171 if (other.classname != "nexball_football")
1174 if (other.deadflag && other.iscreature)
1179 targ = find(world, targetname, self.target);
1182 objerror("trigger_force without a (valid) .target!\n");
1187 if(self.falloff == 1)
1188 str = (str / self.radius) * self.strength;
1189 else if(self.falloff == 2)
1190 str = (1 - (str / self.radius)) * self.strength;
1192 str = self.strength;
1194 pushdeltatime = time - other.lastpushtime;
1195 if (pushdeltatime > 0.15) pushdeltatime = 0;
1196 other.lastpushtime = time;
1197 if(!pushdeltatime) return;
1199 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1200 other.flags &~= FL_ONGROUND;
1203 // Directionless (accelerator/decelerator) mode
1204 void trigger_impulse_touch2()
1206 float pushdeltatime;
1208 // FIXME: Better checking for what to push and not.
1209 if not(other.iscreature)
1210 if (other.classname != "corpse")
1211 if (other.classname != "body")
1212 if (other.classname != "gib")
1213 if (other.classname != "missile")
1214 if (other.classname != "rocket")
1215 if (other.classname != "casing")
1216 if (other.classname != "grenade")
1217 if (other.classname != "plasma")
1218 if (other.classname != "plasma_prim")
1219 if (other.classname != "plasma_chain")
1220 if (other.classname != "droppedweapon")
1221 if (other.classname != "nexball_basketball")
1222 if (other.classname != "nexball_football")
1225 if (other.deadflag && other.iscreature)
1230 pushdeltatime = time - other.lastpushtime;
1231 if (pushdeltatime > 0.15) pushdeltatime = 0;
1232 other.lastpushtime = time;
1233 if(!pushdeltatime) return;
1235 // div0: ticrate independent, 1 = identity (not 20)
1236 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1239 // Spherical (gravity/repulsor) mode
1240 void trigger_impulse_touch3()
1242 float pushdeltatime;
1245 // FIXME: Better checking for what to push and not.
1246 if not(other.iscreature)
1247 if (other.classname != "corpse")
1248 if (other.classname != "body")
1249 if (other.classname != "gib")
1250 if (other.classname != "missile")
1251 if (other.classname != "rocket")
1252 if (other.classname != "casing")
1253 if (other.classname != "grenade")
1254 if (other.classname != "plasma")
1255 if (other.classname != "plasma_prim")
1256 if (other.classname != "plasma_chain")
1257 if (other.classname != "droppedweapon")
1258 if (other.classname != "nexball_basketball")
1259 if (other.classname != "nexball_football")
1262 if (other.deadflag && other.iscreature)
1267 pushdeltatime = time - other.lastpushtime;
1268 if (pushdeltatime > 0.15) pushdeltatime = 0;
1269 other.lastpushtime = time;
1270 if(!pushdeltatime) return;
1272 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1274 str = min(self.radius, vlen(self.origin - other.origin));
1276 if(self.falloff == 1)
1277 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1278 else if(self.falloff == 2)
1279 str = (str / self.radius) * self.strength; // 0 in the inside
1281 str = self.strength;
1283 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1286 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1287 -------- KEYS --------
1288 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1289 If not, this trigger acts like a damper/accelerator field.
1291 strength : This is how mutch force to add in the direction of .target each second
1292 when .target is set. If not, this is hoe mutch to slow down/accelerate
1293 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1295 radius : If set, act as a spherical device rather then a liniar one.
1297 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1299 -------- NOTES --------
1300 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1301 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1304 void spawnfunc_trigger_impulse()
1309 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1310 setorigin(self, self.origin);
1311 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1312 self.touch = trigger_impulse_touch3;
1318 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1319 self.touch = trigger_impulse_touch1;
1323 if(!self.strength) self.strength = 0.9;
1324 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1325 self.touch = trigger_impulse_touch2;
1330 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1331 "Flip-flop" trigger gate... lets only every second trigger event through
1335 self.state = !self.state;
1340 void spawnfunc_trigger_flipflop()
1342 if(self.spawnflags & 1)
1344 self.use = flipflop_use;
1345 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1348 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1349 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1353 self.nextthink = time + self.wait;
1354 self.enemy = activator;
1360 void monoflop_fixed_use()
1364 self.nextthink = time + self.wait;
1366 self.enemy = activator;
1370 void monoflop_think()
1373 activator = self.enemy;
1377 void monoflop_reset()
1383 void spawnfunc_trigger_monoflop()
1387 if(self.spawnflags & 1)
1388 self.use = monoflop_fixed_use;
1390 self.use = monoflop_use;
1391 self.think = monoflop_think;
1393 self.reset = monoflop_reset;
1396 void multivibrator_send()
1401 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1403 newstate = (time < cyclestart + self.wait);
1406 if(self.state != newstate)
1408 self.state = newstate;
1411 self.nextthink = cyclestart + self.wait + 0.01;
1413 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1416 void multivibrator_toggle()
1418 if(self.nextthink == 0)
1420 multivibrator_send();
1433 void multivibrator_reset()
1435 if(!(self.spawnflags & 1))
1436 self.nextthink = 0; // wait for a trigger event
1438 self.nextthink = max(1, time);
1441 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1442 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1443 -------- KEYS --------
1444 target: trigger all entities with this targetname when it goes off
1445 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1446 phase: offset of the timing
1447 wait: "on" cycle time (default: 1)
1448 respawntime: "off" cycle time (default: same as wait)
1449 -------- SPAWNFLAGS --------
1450 START_ON: assume it is already turned on (when targeted)
1452 void spawnfunc_trigger_multivibrator()
1456 if(!self.respawntime)
1457 self.respawntime = self.wait;
1460 self.use = multivibrator_toggle;
1461 self.think = multivibrator_send;
1462 self.nextthink = time;
1465 multivibrator_reset();
1474 if(self.killtarget != "")
1475 src = find(world, targetname, self.killtarget);
1476 if(self.target != "")
1477 dst = find(world, targetname, self.target);
1481 objerror("follow: could not find target/killtarget");
1487 // already done :P entity must stay
1491 else if(!src || !dst)
1493 objerror("follow: could not find target/killtarget");
1496 else if(self.spawnflags & 1)
1499 if(self.spawnflags & 2)
1501 setattachment(dst, src, self.message);
1505 attach_sameorigin(dst, src, self.message);
1512 if(self.spawnflags & 2)
1514 dst.movetype = MOVETYPE_FOLLOW;
1516 // dst.punchangle = '0 0 0'; // keep unchanged
1517 dst.view_ofs = dst.origin;
1518 dst.v_angle = dst.angles;
1522 follow_sameorigin(dst, src);
1529 void spawnfunc_misc_follow()
1531 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1536 void gamestart_use() {
1542 void spawnfunc_trigger_gamestart() {
1543 self.use = gamestart_use;
1544 self.reset2 = spawnfunc_trigger_gamestart;
1548 self.think = self.use;
1549 self.nextthink = game_starttime + self.wait;
1552 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1558 .entity voicescript; // attached voice script
1559 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1560 .float voicescript_nextthink; // time to play next voice
1561 .float voicescript_voiceend; // time when this voice ends
1563 void target_voicescript_clear(entity pl)
1565 pl.voicescript = world;
1568 void target_voicescript_use()
1570 if(activator.voicescript != self)
1572 activator.voicescript = self;
1573 activator.voicescript_index = 0;
1574 activator.voicescript_nextthink = time + self.delay;
1578 void target_voicescript_next(entity pl)
1583 vs = pl.voicescript;
1586 if(vs.message == "")
1588 if(pl.classname != "player")
1593 if(time >= pl.voicescript_voiceend)
1595 if(time >= pl.voicescript_nextthink)
1597 // get the next voice...
1598 n = tokenize_console(vs.message);
1600 if(pl.voicescript_index < vs.cnt)
1601 i = pl.voicescript_index * 2;
1602 else if(n > vs.cnt * 2)
1603 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1609 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1610 dt = stof(argv(i + 1));
1613 pl.voicescript_voiceend = time + dt;
1614 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1618 pl.voicescript_voiceend = time - dt;
1619 pl.voicescript_nextthink = pl.voicescript_voiceend;
1622 pl.voicescript_index += 1;
1626 pl.voicescript = world; // stop trying then
1632 void spawnfunc_target_voicescript()
1634 // netname: directory of the sound files
1635 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1636 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1637 // Here, a - in front of the duration means that no delay is to be
1638 // added after this message
1639 // wait: average time between messages
1640 // delay: initial delay before the first message
1643 self.use = target_voicescript_use;
1645 n = tokenize_console(self.message);
1647 for(i = 0; i+1 < n; i += 2)
1654 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1660 void trigger_relay_teamcheck_use()
1664 if(self.spawnflags & 2)
1666 if(activator.team != self.team)
1671 if(activator.team == self.team)
1677 if(self.spawnflags & 1)
1682 void trigger_relay_teamcheck_reset()
1684 self.team = self.team_saved;
1687 void spawnfunc_trigger_relay_teamcheck()
1689 self.team_saved = self.team;
1690 self.use = trigger_relay_teamcheck_use;
1691 self.reset = trigger_relay_teamcheck_reset;
1696 void trigger_disablerelay_use()
1703 for(e = world; (e = find(e, targetname, self.target)); )
1705 if(e.use == SUB_UseTargets)
1707 e.use = SUB_DontUseTargets;
1710 else if(e.use == SUB_DontUseTargets)
1712 e.use = SUB_UseTargets;
1718 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1721 void spawnfunc_trigger_disablerelay()
1723 self.use = trigger_disablerelay_use;
1726 float magicear_matched;
1727 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1729 float domatch, dotrigger, matchstart, l;
1733 magicear_matched = FALSE;
1735 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1736 domatch = ((ear.spawnflags & 32) || dotrigger);
1742 if(ear.spawnflags & 4)
1748 if(ear.spawnflags & 1)
1751 if(ear.spawnflags & 2)
1754 if(ear.spawnflags & 8)
1759 l = strlen(ear.message);
1761 if(self.spawnflags & 128)
1764 msg = strdecolorize(msgin);
1766 if(substring(ear.message, 0, 1) == "*")
1768 if(substring(ear.message, -1, 1) == "*")
1771 // as we need multi-replacement here...
1772 s = substring(ear.message, 1, -2);
1774 if(strstrofs(msg, s, 0) >= 0)
1775 matchstart = -2; // we use strreplace on s
1780 s = substring(ear.message, 1, -1);
1782 if(substring(msg, -l, l) == s)
1783 matchstart = strlen(msg) - l;
1788 if(substring(ear.message, -1, 1) == "*")
1791 s = substring(ear.message, 0, -2);
1793 if(substring(msg, 0, l) == s)
1800 if(msg == ear.message)
1805 if(matchstart == -1) // no match
1808 magicear_matched = TRUE;
1812 oldself = activator = self;
1818 if(ear.spawnflags & 16)
1822 else if(ear.netname != "")
1825 return strreplace(s, ear.netname, msg);
1828 substring(msg, 0, matchstart),
1830 substring(msg, matchstart + l, -1)
1838 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1842 for(ear = magicears; ear; ear = ear.enemy)
1844 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1845 if not(ear.spawnflags & 64)
1846 if(magicear_matched)
1853 void spawnfunc_trigger_magicear()
1855 self.enemy = magicears;
1858 // actually handled in "say" processing
1861 // 2 = ignore teamsay
1863 // 8 = ignore tell to unknown player
1864 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1865 // 32 = perform the replacement even if outside the radius or dead
1866 // 64 = continue replacing/triggering even if this one matched
1876 // if set, replacement for the matched text
1878 // "hearing distance"