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 trigger_gravity_active;
537 .entity trigger_gravity_check;
538 void trigger_gravity_check_think()
540 // This spawns when a player enters the gravity zone and checks if he left.
541 // Each frame, self.cnt is set to 2 by trigger_gravity_touch() and decreased by 1 here.
542 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
545 self.owner.gravity = 0;
546 self.owner.trigger_gravity_active = 0;
552 self.nextthink = time;
556 void trigger_gravity_use()
558 self.state = !self.state;
561 void trigger_gravity_touch()
563 if(self.state != TRUE)
568 if not(self.spawnflags & 1)
570 if(!other.trigger_gravity_active)
572 other.trigger_gravity_active = 1;
573 other.trigger_gravity_check = spawn();
574 other.trigger_gravity_check.owner = other;
575 other.trigger_gravity_check.think = trigger_gravity_check_think;
576 other.trigger_gravity_check.nextthink = time;
578 other.trigger_gravity_check.cnt = 2;
581 if (other.gravity != self.gravity)
583 other.gravity = self.gravity;
585 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
589 void spawnfunc_trigger_gravity()
594 self.touch = trigger_gravity_touch;
596 precache_sound(self.noise);
601 self.use = trigger_gravity_use;
602 if(self.spawnflags & 2)
607 //=============================================================================
609 // TODO add a way to do looped sounds with sound(); then complete this entity
610 .float volume, atten;
611 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
613 void spawnfunc_target_speaker()
616 precache_sound (self.noise);
620 self.atten = ATTN_NORM;
621 else if(self.atten < 0)
625 self.use = target_speaker_use;
630 self.atten = ATTN_STATIC;
631 else if(self.atten < 0)
635 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
640 void spawnfunc_func_stardust() {
641 self.effects = EF_STARDUST;
645 .float bgmscriptattack;
646 .float bgmscriptdecay;
647 .float bgmscriptsustain;
648 .float bgmscriptrelease;
649 float pointparticles_SendEntity(entity to, float fl)
651 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
653 // optional features to save space
655 if(self.spawnflags & 2)
656 fl |= 0x10; // absolute count on toggle-on
657 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
658 fl |= 0x20; // 4 bytes - saves CPU
659 if(self.waterlevel || self.count != 1)
660 fl |= 0x40; // 4 bytes - obscure features almost never used
661 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
662 fl |= 0x80; // 14 bytes - saves lots of space
664 WriteByte(MSG_ENTITY, fl);
668 WriteCoord(MSG_ENTITY, self.impulse);
670 WriteCoord(MSG_ENTITY, 0); // off
674 WriteCoord(MSG_ENTITY, self.origin_x);
675 WriteCoord(MSG_ENTITY, self.origin_y);
676 WriteCoord(MSG_ENTITY, self.origin_z);
680 if(self.model != "null")
682 WriteShort(MSG_ENTITY, self.modelindex);
685 WriteCoord(MSG_ENTITY, self.mins_x);
686 WriteCoord(MSG_ENTITY, self.mins_y);
687 WriteCoord(MSG_ENTITY, self.mins_z);
688 WriteCoord(MSG_ENTITY, self.maxs_x);
689 WriteCoord(MSG_ENTITY, self.maxs_y);
690 WriteCoord(MSG_ENTITY, self.maxs_z);
695 WriteShort(MSG_ENTITY, 0);
698 WriteCoord(MSG_ENTITY, self.maxs_x);
699 WriteCoord(MSG_ENTITY, self.maxs_y);
700 WriteCoord(MSG_ENTITY, self.maxs_z);
703 WriteShort(MSG_ENTITY, self.cnt);
706 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
707 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
711 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
712 WriteByte(MSG_ENTITY, self.count * 16.0);
714 WriteString(MSG_ENTITY, self.noise);
717 WriteByte(MSG_ENTITY, floor(self.atten * 64));
718 WriteByte(MSG_ENTITY, floor(self.volume * 255));
720 WriteString(MSG_ENTITY, self.bgmscript);
721 if(self.bgmscript != "")
723 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
724 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
725 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
726 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
732 void pointparticles_use()
734 self.state = !self.state;
738 void pointparticles_think()
740 if(self.origin != self.oldorigin)
743 self.oldorigin = self.origin;
745 self.nextthink = time;
748 void pointparticles_reset()
750 if(self.spawnflags & 1)
756 void spawnfunc_func_pointparticles()
759 setmodel(self, self.model);
761 precache_sound (self.noise);
763 if(!self.bgmscriptsustain)
764 self.bgmscriptsustain = 1;
765 else if(self.bgmscriptsustain < 0)
766 self.bgmscriptsustain = 0;
769 self.atten = ATTN_NORM;
770 else if(self.atten < 0)
781 setorigin(self, self.origin + self.mins);
782 setsize(self, '0 0 0', self.maxs - self.mins);
785 self.cnt = particleeffectnum(self.mdl);
787 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
791 self.use = pointparticles_use;
792 self.reset = pointparticles_reset;
797 self.think = pointparticles_think;
798 self.nextthink = time;
801 void spawnfunc_func_sparks()
803 // self.cnt is the amount of sparks that one burst will spawn
805 self.cnt = 25.0; // nice default value
808 // self.wait is the probability that a sparkthink will spawn a spark shower
809 // range: 0 - 1, but 0 makes little sense, so...
810 if(self.wait < 0.05) {
811 self.wait = 0.25; // nice default value
814 self.count = self.cnt;
817 self.velocity = '0 0 -1';
818 self.mdl = "TE_SPARK";
819 self.impulse = 10 * self.wait; // by default 2.5/sec
821 self.cnt = 0; // use mdl
823 spawnfunc_func_pointparticles();
826 float rainsnow_SendEntity(entity to, float sf)
828 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
829 WriteByte(MSG_ENTITY, self.state);
830 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
831 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
832 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
833 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
834 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
835 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
836 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
837 WriteShort(MSG_ENTITY, self.count);
838 WriteByte(MSG_ENTITY, self.cnt);
842 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
843 This is an invisible area like a trigger, which rain falls inside of.
847 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
849 sets color of rain (default 12 - white)
851 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
853 void spawnfunc_func_rain()
855 self.dest = self.velocity;
856 self.velocity = '0 0 0';
858 self.dest = '0 0 -700';
859 self.angles = '0 0 0';
860 self.movetype = MOVETYPE_NONE;
861 self.solid = SOLID_NOT;
862 SetBrushEntityModel();
867 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
870 if(self.count > 65535)
873 self.state = 1; // 1 is rain, 0 is snow
876 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
880 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
881 This is an invisible area like a trigger, which snow falls inside of.
885 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
887 sets color of rain (default 12 - white)
889 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
891 void spawnfunc_func_snow()
893 self.dest = self.velocity;
894 self.velocity = '0 0 0';
896 self.dest = '0 0 -300';
897 self.angles = '0 0 0';
898 self.movetype = MOVETYPE_NONE;
899 self.solid = SOLID_NOT;
900 SetBrushEntityModel();
905 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
908 if(self.count > 65535)
911 self.state = 0; // 1 is rain, 0 is snow
914 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
918 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
921 void misc_laser_aim()
926 if(self.spawnflags & 2)
928 if(self.enemy.origin != self.mangle)
930 self.mangle = self.enemy.origin;
936 a = vectoangles(self.enemy.origin - self.origin);
947 if(self.angles != self.mangle)
949 self.mangle = self.angles;
953 if(self.origin != self.oldorigin)
956 self.oldorigin = self.origin;
960 void misc_laser_init()
962 if(self.target != "")
963 self.enemy = find(world, targetname, self.target);
967 void misc_laser_think()
972 self.nextthink = time;
981 o = self.enemy.origin;
982 if not(self.spawnflags & 2)
983 o = self.origin + normalize(o - self.origin) * 32768;
987 makevectors(self.mangle);
988 o = self.origin + v_forward * 32768;
994 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
996 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
999 if(self.enemy.target != "") // DETECTOR laser
1001 traceline(self.origin, o, MOVE_NORMAL, self);
1002 if(trace_ent.iscreature)
1004 self.pusher = trace_ent;
1011 activator = self.pusher;
1024 activator = self.pusher;
1032 float laser_SendEntity(entity to, float fl)
1034 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1035 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1036 if(self.spawnflags & 2)
1040 if(self.scale != 1 || self.modelscale != 1)
1042 WriteByte(MSG_ENTITY, fl);
1045 WriteCoord(MSG_ENTITY, self.origin_x);
1046 WriteCoord(MSG_ENTITY, self.origin_y);
1047 WriteCoord(MSG_ENTITY, self.origin_z);
1051 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1052 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1053 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1055 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1058 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1059 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1061 WriteShort(MSG_ENTITY, self.cnt + 1);
1067 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1068 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1069 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1073 WriteAngle(MSG_ENTITY, self.mangle_x);
1074 WriteAngle(MSG_ENTITY, self.mangle_y);
1078 WriteByte(MSG_ENTITY, self.state);
1082 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1083 Any object touching the beam will be hurt
1086 spawnfunc_target_position where the laser ends
1088 name of beam end effect to use
1090 color of the beam (default: red)
1092 damage per second (-1 for a laser that kills immediately)
1096 self.state = !self.state;
1097 self.SendFlags |= 4;
1103 if(self.spawnflags & 1)
1109 void spawnfunc_misc_laser()
1113 if(self.mdl == "none")
1117 self.cnt = particleeffectnum(self.mdl);
1120 self.cnt = particleeffectnum("laser_deadly");
1126 self.cnt = particleeffectnum("laser_deadly");
1133 if(self.colormod == '0 0 0')
1135 self.colormod = '1 0 0';
1137 self.message = "saw the light";
1139 self.message2 = "was pushed into a laser by";
1142 if(!self.modelscale)
1143 self.modelscale = 1;
1144 self.think = misc_laser_think;
1145 self.nextthink = time;
1146 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1148 self.mangle = self.angles;
1150 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1154 self.reset = laser_reset;
1156 self.use = laser_use;
1162 // tZorks trigger impulse / gravity
1166 .float lastpushtime;
1168 // targeted (directional) mode
1169 void trigger_impulse_touch1()
1172 float pushdeltatime;
1175 // FIXME: Better checking for what to push and not.
1176 if not(other.iscreature)
1177 if (other.classname != "corpse")
1178 if (other.classname != "body")
1179 if (other.classname != "gib")
1180 if (other.classname != "missile")
1181 if (other.classname != "rocket")
1182 if (other.classname != "casing")
1183 if (other.classname != "grenade")
1184 if (other.classname != "plasma")
1185 if (other.classname != "plasma_prim")
1186 if (other.classname != "plasma_chain")
1187 if (other.classname != "droppedweapon")
1188 if (other.classname != "nexball_basketball")
1189 if (other.classname != "nexball_football")
1192 if (other.deadflag && other.iscreature)
1197 targ = find(world, targetname, self.target);
1200 objerror("trigger_force without a (valid) .target!\n");
1205 if(self.falloff == 1)
1206 str = (str / self.radius) * self.strength;
1207 else if(self.falloff == 2)
1208 str = (1 - (str / self.radius)) * self.strength;
1210 str = self.strength;
1212 pushdeltatime = time - other.lastpushtime;
1213 if (pushdeltatime > 0.15) pushdeltatime = 0;
1214 other.lastpushtime = time;
1215 if(!pushdeltatime) return;
1217 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1218 other.flags &~= FL_ONGROUND;
1221 // Directionless (accelerator/decelerator) mode
1222 void trigger_impulse_touch2()
1224 float pushdeltatime;
1226 // FIXME: Better checking for what to push and not.
1227 if not(other.iscreature)
1228 if (other.classname != "corpse")
1229 if (other.classname != "body")
1230 if (other.classname != "gib")
1231 if (other.classname != "missile")
1232 if (other.classname != "rocket")
1233 if (other.classname != "casing")
1234 if (other.classname != "grenade")
1235 if (other.classname != "plasma")
1236 if (other.classname != "plasma_prim")
1237 if (other.classname != "plasma_chain")
1238 if (other.classname != "droppedweapon")
1239 if (other.classname != "nexball_basketball")
1240 if (other.classname != "nexball_football")
1243 if (other.deadflag && other.iscreature)
1248 pushdeltatime = time - other.lastpushtime;
1249 if (pushdeltatime > 0.15) pushdeltatime = 0;
1250 other.lastpushtime = time;
1251 if(!pushdeltatime) return;
1253 // div0: ticrate independent, 1 = identity (not 20)
1254 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1257 // Spherical (gravity/repulsor) mode
1258 void trigger_impulse_touch3()
1260 float pushdeltatime;
1263 // FIXME: Better checking for what to push and not.
1264 if not(other.iscreature)
1265 if (other.classname != "corpse")
1266 if (other.classname != "body")
1267 if (other.classname != "gib")
1268 if (other.classname != "missile")
1269 if (other.classname != "rocket")
1270 if (other.classname != "casing")
1271 if (other.classname != "grenade")
1272 if (other.classname != "plasma")
1273 if (other.classname != "plasma_prim")
1274 if (other.classname != "plasma_chain")
1275 if (other.classname != "droppedweapon")
1276 if (other.classname != "nexball_basketball")
1277 if (other.classname != "nexball_football")
1280 if (other.deadflag && other.iscreature)
1285 pushdeltatime = time - other.lastpushtime;
1286 if (pushdeltatime > 0.15) pushdeltatime = 0;
1287 other.lastpushtime = time;
1288 if(!pushdeltatime) return;
1290 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1292 str = min(self.radius, vlen(self.origin - other.origin));
1294 if(self.falloff == 1)
1295 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1296 else if(self.falloff == 2)
1297 str = (str / self.radius) * self.strength; // 0 in the inside
1299 str = self.strength;
1301 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1304 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1305 -------- KEYS --------
1306 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1307 If not, this trigger acts like a damper/accelerator field.
1309 strength : This is how mutch force to add in the direction of .target each second
1310 when .target is set. If not, this is hoe mutch to slow down/accelerate
1311 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1313 radius : If set, act as a spherical device rather then a liniar one.
1315 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1317 -------- NOTES --------
1318 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1319 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1322 void spawnfunc_trigger_impulse()
1327 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1328 setorigin(self, self.origin);
1329 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1330 self.touch = trigger_impulse_touch3;
1336 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1337 self.touch = trigger_impulse_touch1;
1341 if(!self.strength) self.strength = 0.9;
1342 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1343 self.touch = trigger_impulse_touch2;
1348 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1349 "Flip-flop" trigger gate... lets only every second trigger event through
1353 self.state = !self.state;
1358 void spawnfunc_trigger_flipflop()
1360 if(self.spawnflags & 1)
1362 self.use = flipflop_use;
1363 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1366 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1367 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1371 self.nextthink = time + self.wait;
1372 self.enemy = activator;
1378 void monoflop_fixed_use()
1382 self.nextthink = time + self.wait;
1384 self.enemy = activator;
1388 void monoflop_think()
1391 activator = self.enemy;
1395 void monoflop_reset()
1401 void spawnfunc_trigger_monoflop()
1405 if(self.spawnflags & 1)
1406 self.use = monoflop_fixed_use;
1408 self.use = monoflop_use;
1409 self.think = monoflop_think;
1411 self.reset = monoflop_reset;
1414 void multivibrator_send()
1419 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1421 newstate = (time < cyclestart + self.wait);
1424 if(self.state != newstate)
1426 self.state = newstate;
1429 self.nextthink = cyclestart + self.wait + 0.01;
1431 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1434 void multivibrator_toggle()
1436 if(self.nextthink == 0)
1438 multivibrator_send();
1451 void multivibrator_reset()
1453 if(!(self.spawnflags & 1))
1454 self.nextthink = 0; // wait for a trigger event
1456 self.nextthink = max(1, time);
1459 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1460 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1461 -------- KEYS --------
1462 target: trigger all entities with this targetname when it goes off
1463 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1464 phase: offset of the timing
1465 wait: "on" cycle time (default: 1)
1466 respawntime: "off" cycle time (default: same as wait)
1467 -------- SPAWNFLAGS --------
1468 START_ON: assume it is already turned on (when targeted)
1470 void spawnfunc_trigger_multivibrator()
1474 if(!self.respawntime)
1475 self.respawntime = self.wait;
1478 self.use = multivibrator_toggle;
1479 self.think = multivibrator_send;
1480 self.nextthink = time;
1483 multivibrator_reset();
1492 if(self.killtarget != "")
1493 src = find(world, targetname, self.killtarget);
1494 if(self.target != "")
1495 dst = find(world, targetname, self.target);
1499 objerror("follow: could not find target/killtarget");
1505 // already done :P entity must stay
1509 else if(!src || !dst)
1511 objerror("follow: could not find target/killtarget");
1514 else if(self.spawnflags & 1)
1517 if(self.spawnflags & 2)
1519 setattachment(dst, src, self.message);
1523 attach_sameorigin(dst, src, self.message);
1530 if(self.spawnflags & 2)
1532 dst.movetype = MOVETYPE_FOLLOW;
1534 // dst.punchangle = '0 0 0'; // keep unchanged
1535 dst.view_ofs = dst.origin;
1536 dst.v_angle = dst.angles;
1540 follow_sameorigin(dst, src);
1547 void spawnfunc_misc_follow()
1549 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1554 void gamestart_use() {
1560 void spawnfunc_trigger_gamestart() {
1561 self.use = gamestart_use;
1562 self.reset2 = spawnfunc_trigger_gamestart;
1566 self.think = self.use;
1567 self.nextthink = game_starttime + self.wait;
1570 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1576 .entity voicescript; // attached voice script
1577 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1578 .float voicescript_nextthink; // time to play next voice
1579 .float voicescript_voiceend; // time when this voice ends
1581 void target_voicescript_clear(entity pl)
1583 pl.voicescript = world;
1586 void target_voicescript_use()
1588 if(activator.voicescript != self)
1590 activator.voicescript = self;
1591 activator.voicescript_index = 0;
1592 activator.voicescript_nextthink = time + self.delay;
1596 void target_voicescript_next(entity pl)
1601 vs = pl.voicescript;
1604 if(vs.message == "")
1606 if(pl.classname != "player")
1611 if(time >= pl.voicescript_voiceend)
1613 if(time >= pl.voicescript_nextthink)
1615 // get the next voice...
1616 n = tokenize_console(vs.message);
1618 if(pl.voicescript_index < vs.cnt)
1619 i = pl.voicescript_index * 2;
1620 else if(n > vs.cnt * 2)
1621 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1627 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1628 dt = stof(argv(i + 1));
1631 pl.voicescript_voiceend = time + dt;
1632 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1636 pl.voicescript_voiceend = time - dt;
1637 pl.voicescript_nextthink = pl.voicescript_voiceend;
1640 pl.voicescript_index += 1;
1644 pl.voicescript = world; // stop trying then
1650 void spawnfunc_target_voicescript()
1652 // netname: directory of the sound files
1653 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1654 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1655 // Here, a - in front of the duration means that no delay is to be
1656 // added after this message
1657 // wait: average time between messages
1658 // delay: initial delay before the first message
1661 self.use = target_voicescript_use;
1663 n = tokenize_console(self.message);
1665 for(i = 0; i+1 < n; i += 2)
1672 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1678 void trigger_relay_teamcheck_use()
1682 if(self.spawnflags & 2)
1684 if(activator.team != self.team)
1689 if(activator.team == self.team)
1695 if(self.spawnflags & 1)
1700 void trigger_relay_teamcheck_reset()
1702 self.team = self.team_saved;
1705 void spawnfunc_trigger_relay_teamcheck()
1707 self.team_saved = self.team;
1708 self.use = trigger_relay_teamcheck_use;
1709 self.reset = trigger_relay_teamcheck_reset;
1714 void trigger_disablerelay_use()
1721 for(e = world; (e = find(e, targetname, self.target)); )
1723 if(e.use == SUB_UseTargets)
1725 e.use = SUB_DontUseTargets;
1728 else if(e.use == SUB_DontUseTargets)
1730 e.use = SUB_UseTargets;
1736 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1739 void spawnfunc_trigger_disablerelay()
1741 self.use = trigger_disablerelay_use;
1744 float magicear_matched;
1745 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1747 float domatch, dotrigger, matchstart, l;
1751 magicear_matched = FALSE;
1753 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1754 domatch = ((ear.spawnflags & 32) || dotrigger);
1760 if(ear.spawnflags & 4)
1766 if(ear.spawnflags & 1)
1769 if(ear.spawnflags & 2)
1772 if(ear.spawnflags & 8)
1777 l = strlen(ear.message);
1779 if(self.spawnflags & 128)
1782 msg = strdecolorize(msgin);
1784 if(substring(ear.message, 0, 1) == "*")
1786 if(substring(ear.message, -1, 1) == "*")
1789 // as we need multi-replacement here...
1790 s = substring(ear.message, 1, -2);
1792 if(strstrofs(msg, s, 0) >= 0)
1793 matchstart = -2; // we use strreplace on s
1798 s = substring(ear.message, 1, -1);
1800 if(substring(msg, -l, l) == s)
1801 matchstart = strlen(msg) - l;
1806 if(substring(ear.message, -1, 1) == "*")
1809 s = substring(ear.message, 0, -2);
1811 if(substring(msg, 0, l) == s)
1818 if(msg == ear.message)
1823 if(matchstart == -1) // no match
1826 magicear_matched = TRUE;
1830 oldself = activator = self;
1836 if(ear.spawnflags & 16)
1840 else if(ear.netname != "")
1843 return strreplace(s, ear.netname, msg);
1846 substring(msg, 0, matchstart),
1848 substring(msg, matchstart + l, -1)
1856 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1860 for(ear = magicears; ear; ear = ear.enemy)
1862 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1863 if not(ear.spawnflags & 64)
1864 if(magicear_matched)
1871 void spawnfunc_trigger_magicear()
1873 self.enemy = magicears;
1876 // actually handled in "say" processing
1879 // 2 = ignore teamsay
1881 // 8 = ignore tell to unknown player
1882 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1883 // 32 = perform the replacement even if outside the radius or dead
1884 // 64 = continue replacing/triggering even if this one matched
1894 // if set, replacement for the matched text
1896 // "hearing distance"