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 .entity trigger_gravity_check;
537 void trigger_gravity_check_think()
539 // This spawns when a player enters the gravity zone and checks if he left.
540 // Each frame, self.cnt is set to 2 by trigger_gravity_touch() and decreased by 1 here.
541 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
544 UpdateCSQCProjectile(self.owner);
545 self.owner.gravity = self.gravity;
546 self.owner.trigger_gravity_check = world;
552 self.nextthink = time;
556 void trigger_gravity_use()
558 self.state = !self.state;
561 void trigger_gravity_touch()
565 if(self.state != TRUE)
572 if not(self.spawnflags & 1)
574 if(other.trigger_gravity_check)
576 other.trigger_gravity_check.cnt = 2; // gravity one more frames...
579 other.trigger_gravity_check = spawn();
580 other.trigger_gravity_check.owner = other;
581 other.trigger_gravity_check.gravity = other.gravity;
582 other.trigger_gravity_check.think = trigger_gravity_check_think;
583 other.trigger_gravity_check.nextthink = time;
584 other.trigger_gravity_check.cnt = 2;
589 if (other.gravity != g)
593 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
594 UpdateCSQCProjectile(self.owner);
598 void spawnfunc_trigger_gravity()
600 if(self.gravity == 1)
604 self.touch = trigger_gravity_touch;
606 precache_sound(self.noise);
611 self.use = trigger_gravity_use;
612 if(self.spawnflags & 2)
617 //=============================================================================
619 // TODO add a way to do looped sounds with sound(); then complete this entity
620 .float volume, atten;
621 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
623 void spawnfunc_target_speaker()
626 precache_sound (self.noise);
630 self.atten = ATTN_NORM;
631 else if(self.atten < 0)
635 self.use = target_speaker_use;
640 self.atten = ATTN_STATIC;
641 else if(self.atten < 0)
645 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
650 void spawnfunc_func_stardust() {
651 self.effects = EF_STARDUST;
655 .float bgmscriptattack;
656 .float bgmscriptdecay;
657 .float bgmscriptsustain;
658 .float bgmscriptrelease;
659 float pointparticles_SendEntity(entity to, float fl)
661 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
663 // optional features to save space
665 if(self.spawnflags & 2)
666 fl |= 0x10; // absolute count on toggle-on
667 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
668 fl |= 0x20; // 4 bytes - saves CPU
669 if(self.waterlevel || self.count != 1)
670 fl |= 0x40; // 4 bytes - obscure features almost never used
671 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
672 fl |= 0x80; // 14 bytes - saves lots of space
674 WriteByte(MSG_ENTITY, fl);
678 WriteCoord(MSG_ENTITY, self.impulse);
680 WriteCoord(MSG_ENTITY, 0); // off
684 WriteCoord(MSG_ENTITY, self.origin_x);
685 WriteCoord(MSG_ENTITY, self.origin_y);
686 WriteCoord(MSG_ENTITY, self.origin_z);
690 if(self.model != "null")
692 WriteShort(MSG_ENTITY, self.modelindex);
695 WriteCoord(MSG_ENTITY, self.mins_x);
696 WriteCoord(MSG_ENTITY, self.mins_y);
697 WriteCoord(MSG_ENTITY, self.mins_z);
698 WriteCoord(MSG_ENTITY, self.maxs_x);
699 WriteCoord(MSG_ENTITY, self.maxs_y);
700 WriteCoord(MSG_ENTITY, self.maxs_z);
705 WriteShort(MSG_ENTITY, 0);
708 WriteCoord(MSG_ENTITY, self.maxs_x);
709 WriteCoord(MSG_ENTITY, self.maxs_y);
710 WriteCoord(MSG_ENTITY, self.maxs_z);
713 WriteShort(MSG_ENTITY, self.cnt);
716 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
717 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
721 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
722 WriteByte(MSG_ENTITY, self.count * 16.0);
724 WriteString(MSG_ENTITY, self.noise);
727 WriteByte(MSG_ENTITY, floor(self.atten * 64));
728 WriteByte(MSG_ENTITY, floor(self.volume * 255));
730 WriteString(MSG_ENTITY, self.bgmscript);
731 if(self.bgmscript != "")
733 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
734 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
735 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
736 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
742 void pointparticles_use()
744 self.state = !self.state;
748 void pointparticles_think()
750 if(self.origin != self.oldorigin)
753 self.oldorigin = self.origin;
755 self.nextthink = time;
758 void pointparticles_reset()
760 if(self.spawnflags & 1)
766 void spawnfunc_func_pointparticles()
769 setmodel(self, self.model);
771 precache_sound (self.noise);
773 if(!self.bgmscriptsustain)
774 self.bgmscriptsustain = 1;
775 else if(self.bgmscriptsustain < 0)
776 self.bgmscriptsustain = 0;
779 self.atten = ATTN_NORM;
780 else if(self.atten < 0)
791 setorigin(self, self.origin + self.mins);
792 setsize(self, '0 0 0', self.maxs - self.mins);
795 self.cnt = particleeffectnum(self.mdl);
797 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
801 self.use = pointparticles_use;
802 self.reset = pointparticles_reset;
807 self.think = pointparticles_think;
808 self.nextthink = time;
811 void spawnfunc_func_sparks()
813 // self.cnt is the amount of sparks that one burst will spawn
815 self.cnt = 25.0; // nice default value
818 // self.wait is the probability that a sparkthink will spawn a spark shower
819 // range: 0 - 1, but 0 makes little sense, so...
820 if(self.wait < 0.05) {
821 self.wait = 0.25; // nice default value
824 self.count = self.cnt;
827 self.velocity = '0 0 -1';
828 self.mdl = "TE_SPARK";
829 self.impulse = 10 * self.wait; // by default 2.5/sec
831 self.cnt = 0; // use mdl
833 spawnfunc_func_pointparticles();
836 float rainsnow_SendEntity(entity to, float sf)
838 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
839 WriteByte(MSG_ENTITY, self.state);
840 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
841 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
842 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
843 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
844 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
845 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
846 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
847 WriteShort(MSG_ENTITY, self.count);
848 WriteByte(MSG_ENTITY, self.cnt);
852 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
853 This is an invisible area like a trigger, which rain falls inside of.
857 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
859 sets color of rain (default 12 - white)
861 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
863 void spawnfunc_func_rain()
865 self.dest = self.velocity;
866 self.velocity = '0 0 0';
868 self.dest = '0 0 -700';
869 self.angles = '0 0 0';
870 self.movetype = MOVETYPE_NONE;
871 self.solid = SOLID_NOT;
872 SetBrushEntityModel();
877 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
880 if(self.count > 65535)
883 self.state = 1; // 1 is rain, 0 is snow
886 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
890 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
891 This is an invisible area like a trigger, which snow falls inside of.
895 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
897 sets color of rain (default 12 - white)
899 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
901 void spawnfunc_func_snow()
903 self.dest = self.velocity;
904 self.velocity = '0 0 0';
906 self.dest = '0 0 -300';
907 self.angles = '0 0 0';
908 self.movetype = MOVETYPE_NONE;
909 self.solid = SOLID_NOT;
910 SetBrushEntityModel();
915 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
918 if(self.count > 65535)
921 self.state = 0; // 1 is rain, 0 is snow
924 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
928 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
931 void misc_laser_aim()
936 if(self.spawnflags & 2)
938 if(self.enemy.origin != self.mangle)
940 self.mangle = self.enemy.origin;
946 a = vectoangles(self.enemy.origin - self.origin);
957 if(self.angles != self.mangle)
959 self.mangle = self.angles;
963 if(self.origin != self.oldorigin)
966 self.oldorigin = self.origin;
970 void misc_laser_init()
972 if(self.target != "")
973 self.enemy = find(world, targetname, self.target);
977 void misc_laser_think()
982 self.nextthink = time;
991 o = self.enemy.origin;
992 if not(self.spawnflags & 2)
993 o = self.origin + normalize(o - self.origin) * 32768;
997 makevectors(self.mangle);
998 o = self.origin + v_forward * 32768;
1004 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1006 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1009 if(self.enemy.target != "") // DETECTOR laser
1011 traceline(self.origin, o, MOVE_NORMAL, self);
1012 if(trace_ent.iscreature)
1014 self.pusher = trace_ent;
1021 activator = self.pusher;
1034 activator = self.pusher;
1042 float laser_SendEntity(entity to, float fl)
1044 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1045 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1046 if(self.spawnflags & 2)
1050 if(self.scale != 1 || self.modelscale != 1)
1052 WriteByte(MSG_ENTITY, fl);
1055 WriteCoord(MSG_ENTITY, self.origin_x);
1056 WriteCoord(MSG_ENTITY, self.origin_y);
1057 WriteCoord(MSG_ENTITY, self.origin_z);
1061 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1062 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1063 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1065 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1068 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1069 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1071 WriteShort(MSG_ENTITY, self.cnt + 1);
1077 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1078 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1079 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1083 WriteAngle(MSG_ENTITY, self.mangle_x);
1084 WriteAngle(MSG_ENTITY, self.mangle_y);
1088 WriteByte(MSG_ENTITY, self.state);
1092 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1093 Any object touching the beam will be hurt
1096 spawnfunc_target_position where the laser ends
1098 name of beam end effect to use
1100 color of the beam (default: red)
1102 damage per second (-1 for a laser that kills immediately)
1106 self.state = !self.state;
1107 self.SendFlags |= 4;
1113 if(self.spawnflags & 1)
1119 void spawnfunc_misc_laser()
1123 if(self.mdl == "none")
1127 self.cnt = particleeffectnum(self.mdl);
1130 self.cnt = particleeffectnum("laser_deadly");
1136 self.cnt = particleeffectnum("laser_deadly");
1143 if(self.colormod == '0 0 0')
1145 self.colormod = '1 0 0';
1147 self.message = "saw the light";
1149 self.message2 = "was pushed into a laser by";
1152 if(!self.modelscale)
1153 self.modelscale = 1;
1154 self.think = misc_laser_think;
1155 self.nextthink = time;
1156 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1158 self.mangle = self.angles;
1160 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1164 self.reset = laser_reset;
1166 self.use = laser_use;
1172 // tZorks trigger impulse / gravity
1176 .float lastpushtime;
1178 // targeted (directional) mode
1179 void trigger_impulse_touch1()
1182 float pushdeltatime;
1185 // FIXME: Better checking for what to push and not.
1186 if not(other.iscreature)
1187 if (other.classname != "corpse")
1188 if (other.classname != "body")
1189 if (other.classname != "gib")
1190 if (other.classname != "missile")
1191 if (other.classname != "rocket")
1192 if (other.classname != "casing")
1193 if (other.classname != "grenade")
1194 if (other.classname != "plasma")
1195 if (other.classname != "plasma_prim")
1196 if (other.classname != "plasma_chain")
1197 if (other.classname != "droppedweapon")
1198 if (other.classname != "nexball_basketball")
1199 if (other.classname != "nexball_football")
1202 if (other.deadflag && other.iscreature)
1207 targ = find(world, targetname, self.target);
1210 objerror("trigger_force without a (valid) .target!\n");
1215 if(self.falloff == 1)
1216 str = (str / self.radius) * self.strength;
1217 else if(self.falloff == 2)
1218 str = (1 - (str / self.radius)) * self.strength;
1220 str = self.strength;
1222 pushdeltatime = time - other.lastpushtime;
1223 if (pushdeltatime > 0.15) pushdeltatime = 0;
1224 other.lastpushtime = time;
1225 if(!pushdeltatime) return;
1227 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1228 other.flags &~= FL_ONGROUND;
1231 // Directionless (accelerator/decelerator) mode
1232 void trigger_impulse_touch2()
1234 float pushdeltatime;
1236 // FIXME: Better checking for what to push and not.
1237 if not(other.iscreature)
1238 if (other.classname != "corpse")
1239 if (other.classname != "body")
1240 if (other.classname != "gib")
1241 if (other.classname != "missile")
1242 if (other.classname != "rocket")
1243 if (other.classname != "casing")
1244 if (other.classname != "grenade")
1245 if (other.classname != "plasma")
1246 if (other.classname != "plasma_prim")
1247 if (other.classname != "plasma_chain")
1248 if (other.classname != "droppedweapon")
1249 if (other.classname != "nexball_basketball")
1250 if (other.classname != "nexball_football")
1253 if (other.deadflag && other.iscreature)
1258 pushdeltatime = time - other.lastpushtime;
1259 if (pushdeltatime > 0.15) pushdeltatime = 0;
1260 other.lastpushtime = time;
1261 if(!pushdeltatime) return;
1263 // div0: ticrate independent, 1 = identity (not 20)
1264 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1267 // Spherical (gravity/repulsor) mode
1268 void trigger_impulse_touch3()
1270 float pushdeltatime;
1273 // FIXME: Better checking for what to push and not.
1274 if not(other.iscreature)
1275 if (other.classname != "corpse")
1276 if (other.classname != "body")
1277 if (other.classname != "gib")
1278 if (other.classname != "missile")
1279 if (other.classname != "rocket")
1280 if (other.classname != "casing")
1281 if (other.classname != "grenade")
1282 if (other.classname != "plasma")
1283 if (other.classname != "plasma_prim")
1284 if (other.classname != "plasma_chain")
1285 if (other.classname != "droppedweapon")
1286 if (other.classname != "nexball_basketball")
1287 if (other.classname != "nexball_football")
1290 if (other.deadflag && other.iscreature)
1295 pushdeltatime = time - other.lastpushtime;
1296 if (pushdeltatime > 0.15) pushdeltatime = 0;
1297 other.lastpushtime = time;
1298 if(!pushdeltatime) return;
1300 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1302 str = min(self.radius, vlen(self.origin - other.origin));
1304 if(self.falloff == 1)
1305 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1306 else if(self.falloff == 2)
1307 str = (str / self.radius) * self.strength; // 0 in the inside
1309 str = self.strength;
1311 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1314 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1315 -------- KEYS --------
1316 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1317 If not, this trigger acts like a damper/accelerator field.
1319 strength : This is how mutch force to add in the direction of .target each second
1320 when .target is set. If not, this is hoe mutch to slow down/accelerate
1321 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1323 radius : If set, act as a spherical device rather then a liniar one.
1325 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1327 -------- NOTES --------
1328 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1329 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1332 void spawnfunc_trigger_impulse()
1337 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1338 setorigin(self, self.origin);
1339 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1340 self.touch = trigger_impulse_touch3;
1346 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1347 self.touch = trigger_impulse_touch1;
1351 if(!self.strength) self.strength = 0.9;
1352 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1353 self.touch = trigger_impulse_touch2;
1358 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1359 "Flip-flop" trigger gate... lets only every second trigger event through
1363 self.state = !self.state;
1368 void spawnfunc_trigger_flipflop()
1370 if(self.spawnflags & 1)
1372 self.use = flipflop_use;
1373 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1376 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1377 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1381 self.nextthink = time + self.wait;
1382 self.enemy = activator;
1388 void monoflop_fixed_use()
1392 self.nextthink = time + self.wait;
1394 self.enemy = activator;
1398 void monoflop_think()
1401 activator = self.enemy;
1405 void monoflop_reset()
1411 void spawnfunc_trigger_monoflop()
1415 if(self.spawnflags & 1)
1416 self.use = monoflop_fixed_use;
1418 self.use = monoflop_use;
1419 self.think = monoflop_think;
1421 self.reset = monoflop_reset;
1424 void multivibrator_send()
1429 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1431 newstate = (time < cyclestart + self.wait);
1434 if(self.state != newstate)
1436 self.state = newstate;
1439 self.nextthink = cyclestart + self.wait + 0.01;
1441 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1444 void multivibrator_toggle()
1446 if(self.nextthink == 0)
1448 multivibrator_send();
1461 void multivibrator_reset()
1463 if(!(self.spawnflags & 1))
1464 self.nextthink = 0; // wait for a trigger event
1466 self.nextthink = max(1, time);
1469 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1470 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1471 -------- KEYS --------
1472 target: trigger all entities with this targetname when it goes off
1473 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1474 phase: offset of the timing
1475 wait: "on" cycle time (default: 1)
1476 respawntime: "off" cycle time (default: same as wait)
1477 -------- SPAWNFLAGS --------
1478 START_ON: assume it is already turned on (when targeted)
1480 void spawnfunc_trigger_multivibrator()
1484 if(!self.respawntime)
1485 self.respawntime = self.wait;
1488 self.use = multivibrator_toggle;
1489 self.think = multivibrator_send;
1490 self.nextthink = time;
1493 multivibrator_reset();
1502 if(self.killtarget != "")
1503 src = find(world, targetname, self.killtarget);
1504 if(self.target != "")
1505 dst = find(world, targetname, self.target);
1509 objerror("follow: could not find target/killtarget");
1515 // already done :P entity must stay
1519 else if(!src || !dst)
1521 objerror("follow: could not find target/killtarget");
1524 else if(self.spawnflags & 1)
1527 if(self.spawnflags & 2)
1529 setattachment(dst, src, self.message);
1533 attach_sameorigin(dst, src, self.message);
1540 if(self.spawnflags & 2)
1542 dst.movetype = MOVETYPE_FOLLOW;
1544 // dst.punchangle = '0 0 0'; // keep unchanged
1545 dst.view_ofs = dst.origin;
1546 dst.v_angle = dst.angles;
1550 follow_sameorigin(dst, src);
1557 void spawnfunc_misc_follow()
1559 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1564 void gamestart_use() {
1570 void spawnfunc_trigger_gamestart() {
1571 self.use = gamestart_use;
1572 self.reset2 = spawnfunc_trigger_gamestart;
1576 self.think = self.use;
1577 self.nextthink = game_starttime + self.wait;
1580 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1586 .entity voicescript; // attached voice script
1587 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1588 .float voicescript_nextthink; // time to play next voice
1589 .float voicescript_voiceend; // time when this voice ends
1591 void target_voicescript_clear(entity pl)
1593 pl.voicescript = world;
1596 void target_voicescript_use()
1598 if(activator.voicescript != self)
1600 activator.voicescript = self;
1601 activator.voicescript_index = 0;
1602 activator.voicescript_nextthink = time + self.delay;
1606 void target_voicescript_next(entity pl)
1611 vs = pl.voicescript;
1614 if(vs.message == "")
1616 if(pl.classname != "player")
1621 if(time >= pl.voicescript_voiceend)
1623 if(time >= pl.voicescript_nextthink)
1625 // get the next voice...
1626 n = tokenize_console(vs.message);
1628 if(pl.voicescript_index < vs.cnt)
1629 i = pl.voicescript_index * 2;
1630 else if(n > vs.cnt * 2)
1631 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1637 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1638 dt = stof(argv(i + 1));
1641 pl.voicescript_voiceend = time + dt;
1642 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1646 pl.voicescript_voiceend = time - dt;
1647 pl.voicescript_nextthink = pl.voicescript_voiceend;
1650 pl.voicescript_index += 1;
1654 pl.voicescript = world; // stop trying then
1660 void spawnfunc_target_voicescript()
1662 // netname: directory of the sound files
1663 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1664 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1665 // Here, a - in front of the duration means that no delay is to be
1666 // added after this message
1667 // wait: average time between messages
1668 // delay: initial delay before the first message
1671 self.use = target_voicescript_use;
1673 n = tokenize_console(self.message);
1675 for(i = 0; i+1 < n; i += 2)
1682 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1688 void trigger_relay_teamcheck_use()
1692 if(self.spawnflags & 2)
1694 if(activator.team != self.team)
1699 if(activator.team == self.team)
1705 if(self.spawnflags & 1)
1710 void trigger_relay_teamcheck_reset()
1712 self.team = self.team_saved;
1715 void spawnfunc_trigger_relay_teamcheck()
1717 self.team_saved = self.team;
1718 self.use = trigger_relay_teamcheck_use;
1719 self.reset = trigger_relay_teamcheck_reset;
1724 void trigger_disablerelay_use()
1731 for(e = world; (e = find(e, targetname, self.target)); )
1733 if(e.use == SUB_UseTargets)
1735 e.use = SUB_DontUseTargets;
1738 else if(e.use == SUB_DontUseTargets)
1740 e.use = SUB_UseTargets;
1746 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1749 void spawnfunc_trigger_disablerelay()
1751 self.use = trigger_disablerelay_use;
1754 float magicear_matched;
1755 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1757 float domatch, dotrigger, matchstart, l;
1761 magicear_matched = FALSE;
1763 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1764 domatch = ((ear.spawnflags & 32) || dotrigger);
1770 if(ear.spawnflags & 4)
1776 if(ear.spawnflags & 1)
1779 if(ear.spawnflags & 2)
1782 if(ear.spawnflags & 8)
1787 l = strlen(ear.message);
1789 if(self.spawnflags & 128)
1792 msg = strdecolorize(msgin);
1794 if(substring(ear.message, 0, 1) == "*")
1796 if(substring(ear.message, -1, 1) == "*")
1799 // as we need multi-replacement here...
1800 s = substring(ear.message, 1, -2);
1802 if(strstrofs(msg, s, 0) >= 0)
1803 matchstart = -2; // we use strreplace on s
1808 s = substring(ear.message, 1, -1);
1810 if(substring(msg, -l, l) == s)
1811 matchstart = strlen(msg) - l;
1816 if(substring(ear.message, -1, 1) == "*")
1819 s = substring(ear.message, 0, -2);
1821 if(substring(msg, 0, l) == s)
1828 if(msg == ear.message)
1833 if(matchstart == -1) // no match
1836 magicear_matched = TRUE;
1840 oldself = activator = self;
1846 if(ear.spawnflags & 16)
1850 else if(ear.netname != "")
1853 return strreplace(s, ear.netname, msg);
1856 substring(msg, 0, matchstart),
1858 substring(msg, matchstart + l, -1)
1866 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1870 for(ear = magicears; ear; ear = ear.enemy)
1872 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1873 if not(ear.spawnflags & 64)
1874 if(magicear_matched)
1881 void spawnfunc_trigger_magicear()
1883 self.enemy = magicears;
1886 // actually handled in "say" processing
1889 // 2 = ignore teamsay
1891 // 8 = ignore tell to unknown player
1892 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1893 // 32 = perform the replacement even if outside the radius or dead
1894 // 64 = continue replacing/triggering even if this one matched
1904 // if set, replacement for the matched text
1906 // "hearing distance"