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, oldgravity;
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 dprint("XXXXXXXXXXXXXXXXXXXXXXXXXX "); // temporary execution test
546 self.owner.gravity = self.owner.oldgravity;
547 self.owner.triggergravity = 0;
553 self.nextthink = time;
557 void trigger_gravity_touch()
561 if(!other.triggergravity)
563 other.triggergravity = 1;
564 other.trigger_gravity_check = spawn();
565 other.trigger_gravity_check.owner = other;
566 other.trigger_gravity_check.think = trigger_gravity_check_think;
567 other.trigger_gravity_check.nextthink = time;
569 other.trigger_gravity_check.cnt = 2;
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);
580 void spawnfunc_trigger_gravity()
585 self.touch = trigger_gravity_touch;
587 precache_sound(self.noise);
590 // TODO add a way to do looped sounds with sound(); then complete this entity
591 .float volume, atten;
592 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
594 void spawnfunc_target_speaker()
597 precache_sound (self.noise);
601 self.atten = ATTN_NORM;
602 else if(self.atten < 0)
606 self.use = target_speaker_use;
611 self.atten = ATTN_STATIC;
612 else if(self.atten < 0)
616 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
621 void spawnfunc_func_stardust() {
622 self.effects = EF_STARDUST;
626 .float bgmscriptattack;
627 .float bgmscriptdecay;
628 .float bgmscriptsustain;
629 .float bgmscriptrelease;
630 float pointparticles_SendEntity(entity to, float fl)
632 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
634 // optional features to save space
636 if(self.spawnflags & 2)
637 fl |= 0x10; // absolute count on toggle-on
638 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
639 fl |= 0x20; // 4 bytes - saves CPU
640 if(self.waterlevel || self.count != 1)
641 fl |= 0x40; // 4 bytes - obscure features almost never used
642 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
643 fl |= 0x80; // 14 bytes - saves lots of space
645 WriteByte(MSG_ENTITY, fl);
649 WriteCoord(MSG_ENTITY, self.impulse);
651 WriteCoord(MSG_ENTITY, 0); // off
655 WriteCoord(MSG_ENTITY, self.origin_x);
656 WriteCoord(MSG_ENTITY, self.origin_y);
657 WriteCoord(MSG_ENTITY, self.origin_z);
661 if(self.model != "null")
663 WriteShort(MSG_ENTITY, self.modelindex);
666 WriteCoord(MSG_ENTITY, self.mins_x);
667 WriteCoord(MSG_ENTITY, self.mins_y);
668 WriteCoord(MSG_ENTITY, self.mins_z);
669 WriteCoord(MSG_ENTITY, self.maxs_x);
670 WriteCoord(MSG_ENTITY, self.maxs_y);
671 WriteCoord(MSG_ENTITY, self.maxs_z);
676 WriteShort(MSG_ENTITY, 0);
679 WriteCoord(MSG_ENTITY, self.maxs_x);
680 WriteCoord(MSG_ENTITY, self.maxs_y);
681 WriteCoord(MSG_ENTITY, self.maxs_z);
684 WriteShort(MSG_ENTITY, self.cnt);
687 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
688 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
692 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
693 WriteByte(MSG_ENTITY, self.count * 16.0);
695 WriteString(MSG_ENTITY, self.noise);
698 WriteByte(MSG_ENTITY, floor(self.atten * 64));
699 WriteByte(MSG_ENTITY, floor(self.volume * 255));
701 WriteString(MSG_ENTITY, self.bgmscript);
702 if(self.bgmscript != "")
704 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
705 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
706 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
707 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
713 void pointparticles_use()
715 self.state = !self.state;
719 void pointparticles_think()
721 if(self.origin != self.oldorigin)
724 self.oldorigin = self.origin;
726 self.nextthink = time;
729 void pointparticles_reset()
731 if(self.spawnflags & 1)
737 void spawnfunc_func_pointparticles()
740 setmodel(self, self.model);
742 precache_sound (self.noise);
744 if(!self.bgmscriptsustain)
745 self.bgmscriptsustain = 1;
746 else if(self.bgmscriptsustain < 0)
747 self.bgmscriptsustain = 0;
750 self.atten = ATTN_NORM;
751 else if(self.atten < 0)
762 setorigin(self, self.origin + self.mins);
763 setsize(self, '0 0 0', self.maxs - self.mins);
766 self.cnt = particleeffectnum(self.mdl);
768 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
772 self.use = pointparticles_use;
773 self.reset = pointparticles_reset;
778 self.think = pointparticles_think;
779 self.nextthink = time;
782 void spawnfunc_func_sparks()
784 // self.cnt is the amount of sparks that one burst will spawn
786 self.cnt = 25.0; // nice default value
789 // self.wait is the probability that a sparkthink will spawn a spark shower
790 // range: 0 - 1, but 0 makes little sense, so...
791 if(self.wait < 0.05) {
792 self.wait = 0.25; // nice default value
795 self.count = self.cnt;
798 self.velocity = '0 0 -1';
799 self.mdl = "TE_SPARK";
800 self.impulse = 10 * self.wait; // by default 2.5/sec
802 self.cnt = 0; // use mdl
804 spawnfunc_func_pointparticles();
807 float rainsnow_SendEntity(entity to, float sf)
809 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
810 WriteByte(MSG_ENTITY, self.state);
811 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
812 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
813 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
814 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
815 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
816 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
817 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
818 WriteShort(MSG_ENTITY, self.count);
819 WriteByte(MSG_ENTITY, self.cnt);
823 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
824 This is an invisible area like a trigger, which rain falls inside of.
828 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
830 sets color of rain (default 12 - white)
832 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
834 void spawnfunc_func_rain()
836 self.dest = self.velocity;
837 self.velocity = '0 0 0';
839 self.dest = '0 0 -700';
840 self.angles = '0 0 0';
841 self.movetype = MOVETYPE_NONE;
842 self.solid = SOLID_NOT;
843 SetBrushEntityModel();
848 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
851 if(self.count > 65535)
854 self.state = 1; // 1 is rain, 0 is snow
857 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
861 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
862 This is an invisible area like a trigger, which snow falls inside of.
866 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
868 sets color of rain (default 12 - white)
870 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
872 void spawnfunc_func_snow()
874 self.dest = self.velocity;
875 self.velocity = '0 0 0';
877 self.dest = '0 0 -300';
878 self.angles = '0 0 0';
879 self.movetype = MOVETYPE_NONE;
880 self.solid = SOLID_NOT;
881 SetBrushEntityModel();
886 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
889 if(self.count > 65535)
892 self.state = 0; // 1 is rain, 0 is snow
895 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
899 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
902 void misc_laser_aim()
907 if(self.spawnflags & 2)
909 if(self.enemy.origin != self.mangle)
911 self.mangle = self.enemy.origin;
917 a = vectoangles(self.enemy.origin - self.origin);
928 if(self.angles != self.mangle)
930 self.mangle = self.angles;
934 if(self.origin != self.oldorigin)
937 self.oldorigin = self.origin;
941 void misc_laser_init()
943 if(self.target != "")
944 self.enemy = find(world, targetname, self.target);
948 void misc_laser_think()
953 self.nextthink = time;
962 o = self.enemy.origin;
963 if not(self.spawnflags & 2)
964 o = self.origin + normalize(o - self.origin) * 32768;
968 makevectors(self.mangle);
969 o = self.origin + v_forward * 32768;
975 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
977 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
980 if(self.enemy.target != "") // DETECTOR laser
982 traceline(self.origin, o, MOVE_NORMAL, self);
983 if(trace_ent.iscreature)
985 self.pusher = trace_ent;
992 activator = self.pusher;
1005 activator = self.pusher;
1013 float laser_SendEntity(entity to, float fl)
1015 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1016 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1017 if(self.spawnflags & 2)
1021 if(self.scale != 1 || self.modelscale != 1)
1023 WriteByte(MSG_ENTITY, fl);
1026 WriteCoord(MSG_ENTITY, self.origin_x);
1027 WriteCoord(MSG_ENTITY, self.origin_y);
1028 WriteCoord(MSG_ENTITY, self.origin_z);
1032 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1033 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1034 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1036 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1039 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1040 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1042 WriteShort(MSG_ENTITY, self.cnt + 1);
1048 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1049 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1050 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1054 WriteAngle(MSG_ENTITY, self.mangle_x);
1055 WriteAngle(MSG_ENTITY, self.mangle_y);
1059 WriteByte(MSG_ENTITY, self.state);
1063 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1064 Any object touching the beam will be hurt
1067 spawnfunc_target_position where the laser ends
1069 name of beam end effect to use
1071 color of the beam (default: red)
1073 damage per second (-1 for a laser that kills immediately)
1077 self.state = !self.state;
1078 self.SendFlags |= 4;
1084 if(self.spawnflags & 1)
1090 void spawnfunc_misc_laser()
1094 if(self.mdl == "none")
1098 self.cnt = particleeffectnum(self.mdl);
1101 self.cnt = particleeffectnum("laser_deadly");
1107 self.cnt = particleeffectnum("laser_deadly");
1114 if(self.colormod == '0 0 0')
1116 self.colormod = '1 0 0';
1118 self.message = "saw the light";
1120 self.message2 = "was pushed into a laser by";
1123 if(!self.modelscale)
1124 self.modelscale = 1;
1125 self.think = misc_laser_think;
1126 self.nextthink = time;
1127 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1129 self.mangle = self.angles;
1131 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1135 self.reset = laser_reset;
1137 self.use = laser_use;
1143 // tZorks trigger impulse / gravity
1147 .float lastpushtime;
1149 // targeted (directional) mode
1150 void trigger_impulse_touch1()
1153 float pushdeltatime;
1156 // FIXME: Better checking for what to push and not.
1157 if not(other.iscreature)
1158 if (other.classname != "corpse")
1159 if (other.classname != "body")
1160 if (other.classname != "gib")
1161 if (other.classname != "missile")
1162 if (other.classname != "rocket")
1163 if (other.classname != "casing")
1164 if (other.classname != "grenade")
1165 if (other.classname != "plasma")
1166 if (other.classname != "plasma_prim")
1167 if (other.classname != "plasma_chain")
1168 if (other.classname != "droppedweapon")
1169 if (other.classname != "nexball_basketball")
1170 if (other.classname != "nexball_football")
1173 if (other.deadflag && other.iscreature)
1178 targ = find(world, targetname, self.target);
1181 objerror("trigger_force without a (valid) .target!\n");
1186 if(self.falloff == 1)
1187 str = (str / self.radius) * self.strength;
1188 else if(self.falloff == 2)
1189 str = (1 - (str / self.radius)) * self.strength;
1191 str = self.strength;
1193 pushdeltatime = time - other.lastpushtime;
1194 if (pushdeltatime > 0.15) pushdeltatime = 0;
1195 other.lastpushtime = time;
1196 if(!pushdeltatime) return;
1198 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1199 other.flags &~= FL_ONGROUND;
1202 // Directionless (accelerator/decelerator) mode
1203 void trigger_impulse_touch2()
1205 float pushdeltatime;
1207 // FIXME: Better checking for what to push and not.
1208 if not(other.iscreature)
1209 if (other.classname != "corpse")
1210 if (other.classname != "body")
1211 if (other.classname != "gib")
1212 if (other.classname != "missile")
1213 if (other.classname != "rocket")
1214 if (other.classname != "casing")
1215 if (other.classname != "grenade")
1216 if (other.classname != "plasma")
1217 if (other.classname != "plasma_prim")
1218 if (other.classname != "plasma_chain")
1219 if (other.classname != "droppedweapon")
1220 if (other.classname != "nexball_basketball")
1221 if (other.classname != "nexball_football")
1224 if (other.deadflag && other.iscreature)
1229 pushdeltatime = time - other.lastpushtime;
1230 if (pushdeltatime > 0.15) pushdeltatime = 0;
1231 other.lastpushtime = time;
1232 if(!pushdeltatime) return;
1234 // div0: ticrate independent, 1 = identity (not 20)
1235 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1238 // Spherical (gravity/repulsor) mode
1239 void trigger_impulse_touch3()
1241 float pushdeltatime;
1244 // FIXME: Better checking for what to push and not.
1245 if not(other.iscreature)
1246 if (other.classname != "corpse")
1247 if (other.classname != "body")
1248 if (other.classname != "gib")
1249 if (other.classname != "missile")
1250 if (other.classname != "rocket")
1251 if (other.classname != "casing")
1252 if (other.classname != "grenade")
1253 if (other.classname != "plasma")
1254 if (other.classname != "plasma_prim")
1255 if (other.classname != "plasma_chain")
1256 if (other.classname != "droppedweapon")
1257 if (other.classname != "nexball_basketball")
1258 if (other.classname != "nexball_football")
1261 if (other.deadflag && other.iscreature)
1266 pushdeltatime = time - other.lastpushtime;
1267 if (pushdeltatime > 0.15) pushdeltatime = 0;
1268 other.lastpushtime = time;
1269 if(!pushdeltatime) return;
1271 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1273 str = min(self.radius, vlen(self.origin - other.origin));
1275 if(self.falloff == 1)
1276 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1277 else if(self.falloff == 2)
1278 str = (str / self.radius) * self.strength; // 0 in the inside
1280 str = self.strength;
1282 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1285 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1286 -------- KEYS --------
1287 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1288 If not, this trigger acts like a damper/accelerator field.
1290 strength : This is how mutch force to add in the direction of .target each second
1291 when .target is set. If not, this is hoe mutch to slow down/accelerate
1292 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1294 radius : If set, act as a spherical device rather then a liniar one.
1296 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1298 -------- NOTES --------
1299 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1300 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1303 void spawnfunc_trigger_impulse()
1308 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1309 setorigin(self, self.origin);
1310 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1311 self.touch = trigger_impulse_touch3;
1317 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1318 self.touch = trigger_impulse_touch1;
1322 if(!self.strength) self.strength = 0.9;
1323 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1324 self.touch = trigger_impulse_touch2;
1329 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1330 "Flip-flop" trigger gate... lets only every second trigger event through
1334 self.state = !self.state;
1339 void spawnfunc_trigger_flipflop()
1341 if(self.spawnflags & 1)
1343 self.use = flipflop_use;
1344 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1347 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1348 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1352 self.nextthink = time + self.wait;
1353 self.enemy = activator;
1359 void monoflop_fixed_use()
1363 self.nextthink = time + self.wait;
1365 self.enemy = activator;
1369 void monoflop_think()
1372 activator = self.enemy;
1376 void monoflop_reset()
1382 void spawnfunc_trigger_monoflop()
1386 if(self.spawnflags & 1)
1387 self.use = monoflop_fixed_use;
1389 self.use = monoflop_use;
1390 self.think = monoflop_think;
1392 self.reset = monoflop_reset;
1395 void multivibrator_send()
1400 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1402 newstate = (time < cyclestart + self.wait);
1405 if(self.state != newstate)
1407 self.state = newstate;
1410 self.nextthink = cyclestart + self.wait + 0.01;
1412 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1415 void multivibrator_toggle()
1417 if(self.nextthink == 0)
1419 multivibrator_send();
1432 void multivibrator_reset()
1434 if(!(self.spawnflags & 1))
1435 self.nextthink = 0; // wait for a trigger event
1437 self.nextthink = max(1, time);
1440 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1441 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1442 -------- KEYS --------
1443 target: trigger all entities with this targetname when it goes off
1444 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1445 phase: offset of the timing
1446 wait: "on" cycle time (default: 1)
1447 respawntime: "off" cycle time (default: same as wait)
1448 -------- SPAWNFLAGS --------
1449 START_ON: assume it is already turned on (when targeted)
1451 void spawnfunc_trigger_multivibrator()
1455 if(!self.respawntime)
1456 self.respawntime = self.wait;
1459 self.use = multivibrator_toggle;
1460 self.think = multivibrator_send;
1461 self.nextthink = time;
1464 multivibrator_reset();
1473 if(self.killtarget != "")
1474 src = find(world, targetname, self.killtarget);
1475 if(self.target != "")
1476 dst = find(world, targetname, self.target);
1480 objerror("follow: could not find target/killtarget");
1486 // already done :P entity must stay
1490 else if(!src || !dst)
1492 objerror("follow: could not find target/killtarget");
1495 else if(self.spawnflags & 1)
1498 if(self.spawnflags & 2)
1500 setattachment(dst, src, self.message);
1504 attach_sameorigin(dst, src, self.message);
1511 if(self.spawnflags & 2)
1513 dst.movetype = MOVETYPE_FOLLOW;
1515 // dst.punchangle = '0 0 0'; // keep unchanged
1516 dst.view_ofs = dst.origin;
1517 dst.v_angle = dst.angles;
1521 follow_sameorigin(dst, src);
1528 void spawnfunc_misc_follow()
1530 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1535 void gamestart_use() {
1541 void spawnfunc_trigger_gamestart() {
1542 self.use = gamestart_use;
1543 self.reset2 = spawnfunc_trigger_gamestart;
1547 self.think = self.use;
1548 self.nextthink = game_starttime + self.wait;
1551 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1557 .entity voicescript; // attached voice script
1558 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1559 .float voicescript_nextthink; // time to play next voice
1560 .float voicescript_voiceend; // time when this voice ends
1562 void target_voicescript_clear(entity pl)
1564 pl.voicescript = world;
1567 void target_voicescript_use()
1569 if(activator.voicescript != self)
1571 activator.voicescript = self;
1572 activator.voicescript_index = 0;
1573 activator.voicescript_nextthink = time + self.delay;
1577 void target_voicescript_next(entity pl)
1582 vs = pl.voicescript;
1585 if(vs.message == "")
1587 if(pl.classname != "player")
1592 if(time >= pl.voicescript_voiceend)
1594 if(time >= pl.voicescript_nextthink)
1596 // get the next voice...
1597 n = tokenize_console(vs.message);
1599 if(pl.voicescript_index < vs.cnt)
1600 i = pl.voicescript_index * 2;
1601 else if(n > vs.cnt * 2)
1602 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1608 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1609 dt = stof(argv(i + 1));
1612 pl.voicescript_voiceend = time + dt;
1613 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1617 pl.voicescript_voiceend = time - dt;
1618 pl.voicescript_nextthink = pl.voicescript_voiceend;
1621 pl.voicescript_index += 1;
1625 pl.voicescript = world; // stop trying then
1631 void spawnfunc_target_voicescript()
1633 // netname: directory of the sound files
1634 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1635 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1636 // Here, a - in front of the duration means that no delay is to be
1637 // added after this message
1638 // wait: average time between messages
1639 // delay: initial delay before the first message
1642 self.use = target_voicescript_use;
1644 n = tokenize_console(self.message);
1646 for(i = 0; i+1 < n; i += 2)
1653 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1659 void trigger_relay_teamcheck_use()
1663 if(self.spawnflags & 2)
1665 if(activator.team != self.team)
1670 if(activator.team == self.team)
1676 if(self.spawnflags & 1)
1681 void trigger_relay_teamcheck_reset()
1683 self.team = self.team_saved;
1686 void spawnfunc_trigger_relay_teamcheck()
1688 self.team_saved = self.team;
1689 self.use = trigger_relay_teamcheck_use;
1690 self.reset = trigger_relay_teamcheck_reset;
1695 void trigger_disablerelay_use()
1702 for(e = world; (e = find(e, targetname, self.target)); )
1704 if(e.use == SUB_UseTargets)
1706 e.use = SUB_DontUseTargets;
1709 else if(e.use == SUB_DontUseTargets)
1711 e.use = SUB_UseTargets;
1717 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1720 void spawnfunc_trigger_disablerelay()
1722 self.use = trigger_disablerelay_use;
1725 float magicear_matched;
1726 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1728 float domatch, dotrigger, matchstart, l;
1732 magicear_matched = FALSE;
1734 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1735 domatch = ((ear.spawnflags & 32) || dotrigger);
1741 if(ear.spawnflags & 4)
1747 if(ear.spawnflags & 1)
1750 if(ear.spawnflags & 2)
1753 if(ear.spawnflags & 8)
1758 l = strlen(ear.message);
1760 if(self.spawnflags & 128)
1763 msg = strdecolorize(msgin);
1765 if(substring(ear.message, 0, 1) == "*")
1767 if(substring(ear.message, -1, 1) == "*")
1770 // as we need multi-replacement here...
1771 s = substring(ear.message, 1, -2);
1773 if(strstrofs(msg, s, 0) >= 0)
1774 matchstart = -2; // we use strreplace on s
1779 s = substring(ear.message, 1, -1);
1781 if(substring(msg, -l, l) == s)
1782 matchstart = strlen(msg) - l;
1787 if(substring(ear.message, -1, 1) == "*")
1790 s = substring(ear.message, 0, -2);
1792 if(substring(msg, 0, l) == s)
1799 if(msg == ear.message)
1804 if(matchstart == -1) // no match
1807 magicear_matched = TRUE;
1811 oldself = activator = self;
1817 if(ear.spawnflags & 16)
1821 else if(ear.netname != "")
1824 return strreplace(s, ear.netname, msg);
1827 substring(msg, 0, matchstart),
1829 substring(msg, matchstart + l, -1)
1837 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1841 for(ear = magicears; ear; ear = ear.enemy)
1843 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1844 if not(ear.spawnflags & 64)
1845 if(magicear_matched)
1852 void spawnfunc_trigger_magicear()
1854 self.enemy = magicears;
1857 // actually handled in "say" processing
1860 // 2 = ignore teamsay
1862 // 8 = ignore tell to unknown player
1863 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1864 // 32 = perform the replacement even if outside the radius or dead
1865 // 64 = continue replacing/triggering even if this one matched
1875 // if set, replacement for the matched text
1877 // "hearing distance"