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 if(self.state == TRUE)
560 if(self.state == FALSE)
564 void trigger_gravity_touch()
566 if(self.state != TRUE)
571 if not(self.spawnflags & 1)
573 if(!other.trigger_gravity_active)
575 other.trigger_gravity_active = 1;
576 other.trigger_gravity_check = spawn();
577 other.trigger_gravity_check.owner = other;
578 other.trigger_gravity_check.think = trigger_gravity_check_think;
579 other.trigger_gravity_check.nextthink = time;
581 other.trigger_gravity_check.cnt = 2;
584 if (other.gravity != self.gravity)
586 other.gravity = self.gravity;
588 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
592 void spawnfunc_trigger_gravity()
597 self.touch = trigger_gravity_touch;
599 precache_sound(self.noise);
604 self.use = trigger_gravity_use;
605 if(self.spawnflags & 2)
610 //=============================================================================
612 // TODO add a way to do looped sounds with sound(); then complete this entity
613 .float volume, atten;
614 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
616 void spawnfunc_target_speaker()
619 precache_sound (self.noise);
623 self.atten = ATTN_NORM;
624 else if(self.atten < 0)
628 self.use = target_speaker_use;
633 self.atten = ATTN_STATIC;
634 else if(self.atten < 0)
638 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
643 void spawnfunc_func_stardust() {
644 self.effects = EF_STARDUST;
648 .float bgmscriptattack;
649 .float bgmscriptdecay;
650 .float bgmscriptsustain;
651 .float bgmscriptrelease;
652 float pointparticles_SendEntity(entity to, float fl)
654 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
656 // optional features to save space
658 if(self.spawnflags & 2)
659 fl |= 0x10; // absolute count on toggle-on
660 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
661 fl |= 0x20; // 4 bytes - saves CPU
662 if(self.waterlevel || self.count != 1)
663 fl |= 0x40; // 4 bytes - obscure features almost never used
664 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
665 fl |= 0x80; // 14 bytes - saves lots of space
667 WriteByte(MSG_ENTITY, fl);
671 WriteCoord(MSG_ENTITY, self.impulse);
673 WriteCoord(MSG_ENTITY, 0); // off
677 WriteCoord(MSG_ENTITY, self.origin_x);
678 WriteCoord(MSG_ENTITY, self.origin_y);
679 WriteCoord(MSG_ENTITY, self.origin_z);
683 if(self.model != "null")
685 WriteShort(MSG_ENTITY, self.modelindex);
688 WriteCoord(MSG_ENTITY, self.mins_x);
689 WriteCoord(MSG_ENTITY, self.mins_y);
690 WriteCoord(MSG_ENTITY, self.mins_z);
691 WriteCoord(MSG_ENTITY, self.maxs_x);
692 WriteCoord(MSG_ENTITY, self.maxs_y);
693 WriteCoord(MSG_ENTITY, self.maxs_z);
698 WriteShort(MSG_ENTITY, 0);
701 WriteCoord(MSG_ENTITY, self.maxs_x);
702 WriteCoord(MSG_ENTITY, self.maxs_y);
703 WriteCoord(MSG_ENTITY, self.maxs_z);
706 WriteShort(MSG_ENTITY, self.cnt);
709 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
710 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
714 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
715 WriteByte(MSG_ENTITY, self.count * 16.0);
717 WriteString(MSG_ENTITY, self.noise);
720 WriteByte(MSG_ENTITY, floor(self.atten * 64));
721 WriteByte(MSG_ENTITY, floor(self.volume * 255));
723 WriteString(MSG_ENTITY, self.bgmscript);
724 if(self.bgmscript != "")
726 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
727 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
728 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
729 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
735 void pointparticles_use()
737 self.state = !self.state;
741 void pointparticles_think()
743 if(self.origin != self.oldorigin)
746 self.oldorigin = self.origin;
748 self.nextthink = time;
751 void pointparticles_reset()
753 if(self.spawnflags & 1)
759 void spawnfunc_func_pointparticles()
762 setmodel(self, self.model);
764 precache_sound (self.noise);
766 if(!self.bgmscriptsustain)
767 self.bgmscriptsustain = 1;
768 else if(self.bgmscriptsustain < 0)
769 self.bgmscriptsustain = 0;
772 self.atten = ATTN_NORM;
773 else if(self.atten < 0)
784 setorigin(self, self.origin + self.mins);
785 setsize(self, '0 0 0', self.maxs - self.mins);
788 self.cnt = particleeffectnum(self.mdl);
790 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
794 self.use = pointparticles_use;
795 self.reset = pointparticles_reset;
800 self.think = pointparticles_think;
801 self.nextthink = time;
804 void spawnfunc_func_sparks()
806 // self.cnt is the amount of sparks that one burst will spawn
808 self.cnt = 25.0; // nice default value
811 // self.wait is the probability that a sparkthink will spawn a spark shower
812 // range: 0 - 1, but 0 makes little sense, so...
813 if(self.wait < 0.05) {
814 self.wait = 0.25; // nice default value
817 self.count = self.cnt;
820 self.velocity = '0 0 -1';
821 self.mdl = "TE_SPARK";
822 self.impulse = 10 * self.wait; // by default 2.5/sec
824 self.cnt = 0; // use mdl
826 spawnfunc_func_pointparticles();
829 float rainsnow_SendEntity(entity to, float sf)
831 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
832 WriteByte(MSG_ENTITY, self.state);
833 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
834 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
835 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
836 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
837 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
838 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
839 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
840 WriteShort(MSG_ENTITY, self.count);
841 WriteByte(MSG_ENTITY, self.cnt);
845 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
846 This is an invisible area like a trigger, which rain falls inside of.
850 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
852 sets color of rain (default 12 - white)
854 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
856 void spawnfunc_func_rain()
858 self.dest = self.velocity;
859 self.velocity = '0 0 0';
861 self.dest = '0 0 -700';
862 self.angles = '0 0 0';
863 self.movetype = MOVETYPE_NONE;
864 self.solid = SOLID_NOT;
865 SetBrushEntityModel();
870 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
873 if(self.count > 65535)
876 self.state = 1; // 1 is rain, 0 is snow
879 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
883 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
884 This is an invisible area like a trigger, which snow falls inside of.
888 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
890 sets color of rain (default 12 - white)
892 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
894 void spawnfunc_func_snow()
896 self.dest = self.velocity;
897 self.velocity = '0 0 0';
899 self.dest = '0 0 -300';
900 self.angles = '0 0 0';
901 self.movetype = MOVETYPE_NONE;
902 self.solid = SOLID_NOT;
903 SetBrushEntityModel();
908 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
911 if(self.count > 65535)
914 self.state = 0; // 1 is rain, 0 is snow
917 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
921 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
924 void misc_laser_aim()
929 if(self.spawnflags & 2)
931 if(self.enemy.origin != self.mangle)
933 self.mangle = self.enemy.origin;
939 a = vectoangles(self.enemy.origin - self.origin);
950 if(self.angles != self.mangle)
952 self.mangle = self.angles;
956 if(self.origin != self.oldorigin)
959 self.oldorigin = self.origin;
963 void misc_laser_init()
965 if(self.target != "")
966 self.enemy = find(world, targetname, self.target);
970 void misc_laser_think()
975 self.nextthink = time;
984 o = self.enemy.origin;
985 if not(self.spawnflags & 2)
986 o = self.origin + normalize(o - self.origin) * 32768;
990 makevectors(self.mangle);
991 o = self.origin + v_forward * 32768;
997 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
999 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1002 if(self.enemy.target != "") // DETECTOR laser
1004 traceline(self.origin, o, MOVE_NORMAL, self);
1005 if(trace_ent.iscreature)
1007 self.pusher = trace_ent;
1014 activator = self.pusher;
1027 activator = self.pusher;
1035 float laser_SendEntity(entity to, float fl)
1037 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1038 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1039 if(self.spawnflags & 2)
1043 if(self.scale != 1 || self.modelscale != 1)
1045 WriteByte(MSG_ENTITY, fl);
1048 WriteCoord(MSG_ENTITY, self.origin_x);
1049 WriteCoord(MSG_ENTITY, self.origin_y);
1050 WriteCoord(MSG_ENTITY, self.origin_z);
1054 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1055 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1056 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1058 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1061 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1062 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1064 WriteShort(MSG_ENTITY, self.cnt + 1);
1070 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1071 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1072 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1076 WriteAngle(MSG_ENTITY, self.mangle_x);
1077 WriteAngle(MSG_ENTITY, self.mangle_y);
1081 WriteByte(MSG_ENTITY, self.state);
1085 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1086 Any object touching the beam will be hurt
1089 spawnfunc_target_position where the laser ends
1091 name of beam end effect to use
1093 color of the beam (default: red)
1095 damage per second (-1 for a laser that kills immediately)
1099 self.state = !self.state;
1100 self.SendFlags |= 4;
1106 if(self.spawnflags & 1)
1112 void spawnfunc_misc_laser()
1116 if(self.mdl == "none")
1120 self.cnt = particleeffectnum(self.mdl);
1123 self.cnt = particleeffectnum("laser_deadly");
1129 self.cnt = particleeffectnum("laser_deadly");
1136 if(self.colormod == '0 0 0')
1138 self.colormod = '1 0 0';
1140 self.message = "saw the light";
1142 self.message2 = "was pushed into a laser by";
1145 if(!self.modelscale)
1146 self.modelscale = 1;
1147 self.think = misc_laser_think;
1148 self.nextthink = time;
1149 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1151 self.mangle = self.angles;
1153 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1157 self.reset = laser_reset;
1159 self.use = laser_use;
1165 // tZorks trigger impulse / gravity
1169 .float lastpushtime;
1171 // targeted (directional) mode
1172 void trigger_impulse_touch1()
1175 float pushdeltatime;
1178 // FIXME: Better checking for what to push and not.
1179 if not(other.iscreature)
1180 if (other.classname != "corpse")
1181 if (other.classname != "body")
1182 if (other.classname != "gib")
1183 if (other.classname != "missile")
1184 if (other.classname != "rocket")
1185 if (other.classname != "casing")
1186 if (other.classname != "grenade")
1187 if (other.classname != "plasma")
1188 if (other.classname != "plasma_prim")
1189 if (other.classname != "plasma_chain")
1190 if (other.classname != "droppedweapon")
1191 if (other.classname != "nexball_basketball")
1192 if (other.classname != "nexball_football")
1195 if (other.deadflag && other.iscreature)
1200 targ = find(world, targetname, self.target);
1203 objerror("trigger_force without a (valid) .target!\n");
1208 if(self.falloff == 1)
1209 str = (str / self.radius) * self.strength;
1210 else if(self.falloff == 2)
1211 str = (1 - (str / self.radius)) * self.strength;
1213 str = self.strength;
1215 pushdeltatime = time - other.lastpushtime;
1216 if (pushdeltatime > 0.15) pushdeltatime = 0;
1217 other.lastpushtime = time;
1218 if(!pushdeltatime) return;
1220 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1221 other.flags &~= FL_ONGROUND;
1224 // Directionless (accelerator/decelerator) mode
1225 void trigger_impulse_touch2()
1227 float pushdeltatime;
1229 // FIXME: Better checking for what to push and not.
1230 if not(other.iscreature)
1231 if (other.classname != "corpse")
1232 if (other.classname != "body")
1233 if (other.classname != "gib")
1234 if (other.classname != "missile")
1235 if (other.classname != "rocket")
1236 if (other.classname != "casing")
1237 if (other.classname != "grenade")
1238 if (other.classname != "plasma")
1239 if (other.classname != "plasma_prim")
1240 if (other.classname != "plasma_chain")
1241 if (other.classname != "droppedweapon")
1242 if (other.classname != "nexball_basketball")
1243 if (other.classname != "nexball_football")
1246 if (other.deadflag && other.iscreature)
1251 pushdeltatime = time - other.lastpushtime;
1252 if (pushdeltatime > 0.15) pushdeltatime = 0;
1253 other.lastpushtime = time;
1254 if(!pushdeltatime) return;
1256 // div0: ticrate independent, 1 = identity (not 20)
1257 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1260 // Spherical (gravity/repulsor) mode
1261 void trigger_impulse_touch3()
1263 float pushdeltatime;
1266 // FIXME: Better checking for what to push and not.
1267 if not(other.iscreature)
1268 if (other.classname != "corpse")
1269 if (other.classname != "body")
1270 if (other.classname != "gib")
1271 if (other.classname != "missile")
1272 if (other.classname != "rocket")
1273 if (other.classname != "casing")
1274 if (other.classname != "grenade")
1275 if (other.classname != "plasma")
1276 if (other.classname != "plasma_prim")
1277 if (other.classname != "plasma_chain")
1278 if (other.classname != "droppedweapon")
1279 if (other.classname != "nexball_basketball")
1280 if (other.classname != "nexball_football")
1283 if (other.deadflag && other.iscreature)
1288 pushdeltatime = time - other.lastpushtime;
1289 if (pushdeltatime > 0.15) pushdeltatime = 0;
1290 other.lastpushtime = time;
1291 if(!pushdeltatime) return;
1293 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1295 str = min(self.radius, vlen(self.origin - other.origin));
1297 if(self.falloff == 1)
1298 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1299 else if(self.falloff == 2)
1300 str = (str / self.radius) * self.strength; // 0 in the inside
1302 str = self.strength;
1304 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1307 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1308 -------- KEYS --------
1309 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1310 If not, this trigger acts like a damper/accelerator field.
1312 strength : This is how mutch force to add in the direction of .target each second
1313 when .target is set. If not, this is hoe mutch to slow down/accelerate
1314 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1316 radius : If set, act as a spherical device rather then a liniar one.
1318 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1320 -------- NOTES --------
1321 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1322 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1325 void spawnfunc_trigger_impulse()
1330 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1331 setorigin(self, self.origin);
1332 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1333 self.touch = trigger_impulse_touch3;
1339 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1340 self.touch = trigger_impulse_touch1;
1344 if(!self.strength) self.strength = 0.9;
1345 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1346 self.touch = trigger_impulse_touch2;
1351 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1352 "Flip-flop" trigger gate... lets only every second trigger event through
1356 self.state = !self.state;
1361 void spawnfunc_trigger_flipflop()
1363 if(self.spawnflags & 1)
1365 self.use = flipflop_use;
1366 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1369 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1370 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1374 self.nextthink = time + self.wait;
1375 self.enemy = activator;
1381 void monoflop_fixed_use()
1385 self.nextthink = time + self.wait;
1387 self.enemy = activator;
1391 void monoflop_think()
1394 activator = self.enemy;
1398 void monoflop_reset()
1404 void spawnfunc_trigger_monoflop()
1408 if(self.spawnflags & 1)
1409 self.use = monoflop_fixed_use;
1411 self.use = monoflop_use;
1412 self.think = monoflop_think;
1414 self.reset = monoflop_reset;
1417 void multivibrator_send()
1422 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1424 newstate = (time < cyclestart + self.wait);
1427 if(self.state != newstate)
1429 self.state = newstate;
1432 self.nextthink = cyclestart + self.wait + 0.01;
1434 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1437 void multivibrator_toggle()
1439 if(self.nextthink == 0)
1441 multivibrator_send();
1454 void multivibrator_reset()
1456 if(!(self.spawnflags & 1))
1457 self.nextthink = 0; // wait for a trigger event
1459 self.nextthink = max(1, time);
1462 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1463 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1464 -------- KEYS --------
1465 target: trigger all entities with this targetname when it goes off
1466 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1467 phase: offset of the timing
1468 wait: "on" cycle time (default: 1)
1469 respawntime: "off" cycle time (default: same as wait)
1470 -------- SPAWNFLAGS --------
1471 START_ON: assume it is already turned on (when targeted)
1473 void spawnfunc_trigger_multivibrator()
1477 if(!self.respawntime)
1478 self.respawntime = self.wait;
1481 self.use = multivibrator_toggle;
1482 self.think = multivibrator_send;
1483 self.nextthink = time;
1486 multivibrator_reset();
1495 if(self.killtarget != "")
1496 src = find(world, targetname, self.killtarget);
1497 if(self.target != "")
1498 dst = find(world, targetname, self.target);
1502 objerror("follow: could not find target/killtarget");
1508 // already done :P entity must stay
1512 else if(!src || !dst)
1514 objerror("follow: could not find target/killtarget");
1517 else if(self.spawnflags & 1)
1520 if(self.spawnflags & 2)
1522 setattachment(dst, src, self.message);
1526 attach_sameorigin(dst, src, self.message);
1533 if(self.spawnflags & 2)
1535 dst.movetype = MOVETYPE_FOLLOW;
1537 // dst.punchangle = '0 0 0'; // keep unchanged
1538 dst.view_ofs = dst.origin;
1539 dst.v_angle = dst.angles;
1543 follow_sameorigin(dst, src);
1550 void spawnfunc_misc_follow()
1552 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1557 void gamestart_use() {
1563 void spawnfunc_trigger_gamestart() {
1564 self.use = gamestart_use;
1565 self.reset2 = spawnfunc_trigger_gamestart;
1569 self.think = self.use;
1570 self.nextthink = game_starttime + self.wait;
1573 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1579 .entity voicescript; // attached voice script
1580 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1581 .float voicescript_nextthink; // time to play next voice
1582 .float voicescript_voiceend; // time when this voice ends
1584 void target_voicescript_clear(entity pl)
1586 pl.voicescript = world;
1589 void target_voicescript_use()
1591 if(activator.voicescript != self)
1593 activator.voicescript = self;
1594 activator.voicescript_index = 0;
1595 activator.voicescript_nextthink = time + self.delay;
1599 void target_voicescript_next(entity pl)
1604 vs = pl.voicescript;
1607 if(vs.message == "")
1609 if(pl.classname != "player")
1614 if(time >= pl.voicescript_voiceend)
1616 if(time >= pl.voicescript_nextthink)
1618 // get the next voice...
1619 n = tokenize_console(vs.message);
1621 if(pl.voicescript_index < vs.cnt)
1622 i = pl.voicescript_index * 2;
1623 else if(n > vs.cnt * 2)
1624 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1630 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1631 dt = stof(argv(i + 1));
1634 pl.voicescript_voiceend = time + dt;
1635 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1639 pl.voicescript_voiceend = time - dt;
1640 pl.voicescript_nextthink = pl.voicescript_voiceend;
1643 pl.voicescript_index += 1;
1647 pl.voicescript = world; // stop trying then
1653 void spawnfunc_target_voicescript()
1655 // netname: directory of the sound files
1656 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1657 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1658 // Here, a - in front of the duration means that no delay is to be
1659 // added after this message
1660 // wait: average time between messages
1661 // delay: initial delay before the first message
1664 self.use = target_voicescript_use;
1666 n = tokenize_console(self.message);
1668 for(i = 0; i+1 < n; i += 2)
1675 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1681 void trigger_relay_teamcheck_use()
1685 if(self.spawnflags & 2)
1687 if(activator.team != self.team)
1692 if(activator.team == self.team)
1698 if(self.spawnflags & 1)
1703 void trigger_relay_teamcheck_reset()
1705 self.team = self.team_saved;
1708 void spawnfunc_trigger_relay_teamcheck()
1710 self.team_saved = self.team;
1711 self.use = trigger_relay_teamcheck_use;
1712 self.reset = trigger_relay_teamcheck_reset;
1717 void trigger_disablerelay_use()
1724 for(e = world; (e = find(e, targetname, self.target)); )
1726 if(e.use == SUB_UseTargets)
1728 e.use = SUB_DontUseTargets;
1731 else if(e.use == SUB_DontUseTargets)
1733 e.use = SUB_UseTargets;
1739 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1742 void spawnfunc_trigger_disablerelay()
1744 self.use = trigger_disablerelay_use;
1747 float magicear_matched;
1748 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1750 float domatch, dotrigger, matchstart, l;
1754 magicear_matched = FALSE;
1756 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1757 domatch = ((ear.spawnflags & 32) || dotrigger);
1763 if(ear.spawnflags & 4)
1769 if(ear.spawnflags & 1)
1772 if(ear.spawnflags & 2)
1775 if(ear.spawnflags & 8)
1780 l = strlen(ear.message);
1782 if(self.spawnflags & 128)
1785 msg = strdecolorize(msgin);
1787 if(substring(ear.message, 0, 1) == "*")
1789 if(substring(ear.message, -1, 1) == "*")
1792 // as we need multi-replacement here...
1793 s = substring(ear.message, 1, -2);
1795 if(strstrofs(msg, s, 0) >= 0)
1796 matchstart = -2; // we use strreplace on s
1801 s = substring(ear.message, 1, -1);
1803 if(substring(msg, -l, l) == s)
1804 matchstart = strlen(msg) - l;
1809 if(substring(ear.message, -1, 1) == "*")
1812 s = substring(ear.message, 0, -2);
1814 if(substring(msg, 0, l) == s)
1821 if(msg == ear.message)
1826 if(matchstart == -1) // no match
1829 magicear_matched = TRUE;
1833 oldself = activator = self;
1839 if(ear.spawnflags & 16)
1843 else if(ear.netname != "")
1846 return strreplace(s, ear.netname, msg);
1849 substring(msg, 0, matchstart),
1851 substring(msg, matchstart + l, -1)
1859 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1863 for(ear = magicears; ear; ear = ear.enemy)
1865 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1866 if not(ear.spawnflags & 64)
1867 if(magicear_matched)
1874 void spawnfunc_trigger_magicear()
1876 self.enemy = magicears;
1879 // actually handled in "say" processing
1882 // 2 = ignore teamsay
1884 // 8 = ignore tell to unknown player
1885 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1886 // 32 = perform the replacement even if outside the radius or dead
1887 // 64 = continue replacing/triggering even if this one matched
1897 // if set, replacement for the matched text
1899 // "hearing distance"