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;
169 { // we can't just remove (self) here, because this is a touch function
170 // called wheil C code is looping through area links...
171 self.touch = SUB_Null;
177 self.goalentity = other;
178 self.enemy = activator;
184 if not(self.spawnflags & 2)
186 if not(other.iscreature)
190 if(self.team == other.team)
194 // if the trigger has an angles field, check player's facing direction
195 if (self.movedir != '0 0 0')
197 makevectors (other.angles);
198 if (v_forward * self.movedir < 0)
199 return; // not facing the right way
205 self.goalentity = other;
209 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
211 if (!self.takedamage)
213 if(self.spawnflags & DOOR_NOSPLASH)
214 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
216 self.health = self.health - damage;
217 if (self.health <= 0)
219 self.enemy = attacker;
220 self.goalentity = inflictor;
227 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
228 self.touch = multi_touch;
231 self.health = self.max_health;
232 self.takedamage = DAMAGE_YES;
233 self.solid = SOLID_BBOX;
235 self.think = SUB_Null;
236 self.team = self.team_saved;
239 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
240 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.
241 If "delay" is set, the trigger waits some time after activating before firing.
242 "wait" : Seconds between triggerings. (.2 default)
243 If notouch is set, the trigger is only fired by other entities, not by touching.
244 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
250 set "message" to text string
252 void spawnfunc_trigger_multiple()
254 self.reset = multi_reset;
255 if (self.sounds == 1)
257 precache_sound ("misc/secret.wav");
258 self.noise = "misc/secret.wav";
260 else if (self.sounds == 2)
262 precache_sound ("misc/talk.wav");
263 self.noise = "misc/talk.wav";
265 else if (self.sounds == 3)
267 precache_sound ("misc/trigger1.wav");
268 self.noise = "misc/trigger1.wav";
273 self.use = multi_use;
277 self.team_saved = self.team;
281 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
282 objerror ("health and notouch don't make sense\n");
283 self.max_health = self.health;
284 self.event_damage = multi_eventdamage;
285 self.takedamage = DAMAGE_YES;
286 self.solid = SOLID_BBOX;
287 setorigin (self, self.origin); // make sure it links into the world
291 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
293 self.touch = multi_touch;
294 setorigin (self, self.origin); // make sure it links into the world
300 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
301 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
302 "targetname". If "health" is set, the trigger must be killed to activate.
303 If notouch is set, the trigger is only fired by other entities, not by touching.
304 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
305 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.
311 set "message" to text string
313 void spawnfunc_trigger_once()
316 spawnfunc_trigger_multiple();
319 //=============================================================================
321 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
322 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
324 void spawnfunc_trigger_relay()
326 self.use = SUB_UseTargets;
327 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
332 self.think = SUB_UseTargets;
333 self.nextthink = self.wait;
338 self.think = SUB_Null;
341 void spawnfunc_trigger_delay()
346 self.use = delay_use;
347 self.reset = delay_reset;
350 //=============================================================================
355 self.count = self.count - 1;
361 if (activator.classname == "player"
362 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
365 centerprint (activator, "There are more to go...");
366 else if (self.count == 3)
367 centerprint (activator, "Only 3 more to go...");
368 else if (self.count == 2)
369 centerprint (activator, "Only 2 more to go...");
371 centerprint (activator, "Only 1 more to go...");
376 if (activator.classname == "player"
377 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
378 centerprint(activator, "Sequence completed!");
379 self.enemy = activator;
385 self.count = self.cnt;
389 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
390 Acts as an intermediary for an action that takes multiple inputs.
392 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
394 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
396 void spawnfunc_trigger_counter()
401 self.cnt = self.count;
403 self.use = counter_use;
404 self.reset = counter_reset;
407 .float triggerhurttime;
408 void trigger_hurt_touch()
410 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
411 if (other.iscreature)
413 if (other.takedamage)
414 if (other.triggerhurttime < time)
417 other.triggerhurttime = time + 1;
418 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
425 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
428 other.pain_finished = min(other.pain_finished, time + 2);
430 else if (other.classname == "rune") // reset runes
433 other.nextthink = min(other.nextthink, time + 1);
441 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
442 Any object touching this will be hurt
443 set dmg to damage amount
446 .entity trigger_hurt_next;
447 entity trigger_hurt_last;
448 entity trigger_hurt_first;
449 void spawnfunc_trigger_hurt()
452 self.touch = trigger_hurt_touch;
456 self.message = "was in the wrong place";
458 self.message2 = "was thrown into a world of hurt by";
460 if(!trigger_hurt_first)
461 trigger_hurt_first = self;
462 if(trigger_hurt_last)
463 trigger_hurt_last.trigger_hurt_next = self;
464 trigger_hurt_last = self;
467 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
471 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
472 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
478 //////////////////////////////////////////////////////////////
482 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
484 //////////////////////////////////////////////////////////////
486 .float triggerhealtime;
487 void trigger_heal_touch()
489 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
490 if (other.iscreature)
492 if (other.takedamage)
493 if (other.triggerhealtime < time)
496 other.triggerhealtime = time + 1;
498 if (other.health < self.max_health)
500 other.health = min(other.health + self.health, self.max_health);
501 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
502 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
508 void spawnfunc_trigger_heal()
511 self.touch = trigger_heal_touch;
514 if (!self.max_health)
515 self.max_health = 200; //Max health topoff for field
517 self.noise = "misc/mediumhealth.wav";
518 precache_sound(self.noise);
522 //////////////////////////////////////////////////////////////
528 //////////////////////////////////////////////////////////////
532 // TODO add a way to do looped sounds with sound(); then complete this entity
533 .float volume, atten;
534 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
536 void spawnfunc_target_speaker()
539 precache_sound (self.noise);
543 self.atten = ATTN_NORM;
544 else if(self.atten < 0)
548 self.use = target_speaker_use;
553 self.atten = ATTN_STATIC;
554 else if(self.atten < 0)
558 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
563 void spawnfunc_func_stardust() {
564 self.effects = EF_STARDUST;
568 .float bgmscriptattack;
569 .float bgmscriptdecay;
570 .float bgmscriptsustain;
571 .float bgmscriptrelease;
572 float pointparticles_SendEntity(entity to, float fl)
574 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
576 // optional features to save space
578 if(self.spawnflags & 2)
579 fl |= 0x10; // absolute count on toggle-on
580 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
581 fl |= 0x20; // 4 bytes - saves CPU
582 if(self.waterlevel || self.count != 1)
583 fl |= 0x40; // 4 bytes - obscure features almost never used
584 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
585 fl |= 0x80; // 14 bytes - saves lots of space
587 WriteByte(MSG_ENTITY, fl);
591 WriteCoord(MSG_ENTITY, self.impulse);
593 WriteCoord(MSG_ENTITY, 0); // off
597 WriteCoord(MSG_ENTITY, self.origin_x);
598 WriteCoord(MSG_ENTITY, self.origin_y);
599 WriteCoord(MSG_ENTITY, self.origin_z);
603 if(self.model != "null")
605 WriteShort(MSG_ENTITY, self.modelindex);
608 WriteCoord(MSG_ENTITY, self.mins_x);
609 WriteCoord(MSG_ENTITY, self.mins_y);
610 WriteCoord(MSG_ENTITY, self.mins_z);
611 WriteCoord(MSG_ENTITY, self.maxs_x);
612 WriteCoord(MSG_ENTITY, self.maxs_y);
613 WriteCoord(MSG_ENTITY, self.maxs_z);
618 WriteShort(MSG_ENTITY, 0);
621 WriteCoord(MSG_ENTITY, self.maxs_x);
622 WriteCoord(MSG_ENTITY, self.maxs_y);
623 WriteCoord(MSG_ENTITY, self.maxs_z);
626 WriteShort(MSG_ENTITY, self.cnt);
629 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
630 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
634 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
635 WriteByte(MSG_ENTITY, self.count * 16.0);
637 WriteString(MSG_ENTITY, self.noise);
640 WriteByte(MSG_ENTITY, floor(self.atten * 64));
641 WriteByte(MSG_ENTITY, floor(self.volume * 255));
643 WriteString(MSG_ENTITY, self.bgmscript);
644 if(self.bgmscript != "")
646 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
647 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
648 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
649 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
655 void pointparticles_use()
657 self.state = !self.state;
661 void pointparticles_think()
663 if(self.origin != self.oldorigin)
666 self.oldorigin = self.origin;
668 self.nextthink = time;
671 void pointparticles_reset()
673 if(self.spawnflags & 1)
679 void spawnfunc_func_pointparticles()
682 setmodel(self, self.model);
684 precache_sound (self.noise);
686 if(!self.bgmscriptsustain)
687 self.bgmscriptsustain = 1;
688 else if(self.bgmscriptsustain < 0)
689 self.bgmscriptsustain = 0;
692 self.atten = ATTN_NORM;
693 else if(self.atten < 0)
704 setorigin(self, self.origin + self.mins);
705 setsize(self, '0 0 0', self.maxs - self.mins);
708 self.cnt = particleeffectnum(self.mdl);
710 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
714 self.use = pointparticles_use;
715 self.reset = pointparticles_reset;
720 self.think = pointparticles_think;
721 self.nextthink = time;
724 void spawnfunc_func_sparks()
726 // self.cnt is the amount of sparks that one burst will spawn
728 self.cnt = 25.0; // nice default value
731 // self.wait is the probability that a sparkthink will spawn a spark shower
732 // range: 0 - 1, but 0 makes little sense, so...
733 if(self.wait < 0.05) {
734 self.wait = 0.25; // nice default value
737 self.count = self.cnt;
740 self.velocity = '0 0 -1';
741 self.mdl = "TE_SPARK";
742 self.impulse = 10 * self.wait; // by default 2.5/sec
744 self.cnt = 0; // use mdl
746 spawnfunc_func_pointparticles();
749 float rainsnow_SendEntity(entity to, float sf)
751 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
752 WriteByte(MSG_ENTITY, self.state);
753 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
754 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
755 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
756 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
757 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
758 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
759 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
760 WriteShort(MSG_ENTITY, self.count);
761 WriteByte(MSG_ENTITY, self.cnt);
765 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
766 This is an invisible area like a trigger, which rain falls inside of.
770 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
772 sets color of rain (default 12 - white)
774 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
776 void spawnfunc_func_rain()
778 self.dest = self.velocity;
779 self.velocity = '0 0 0';
781 self.dest = '0 0 -700';
782 self.angles = '0 0 0';
783 self.movetype = MOVETYPE_NONE;
784 self.solid = SOLID_NOT;
785 SetBrushEntityModel();
790 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
793 if(self.count > 65535)
796 self.state = 1; // 1 is rain, 0 is snow
799 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
803 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
804 This is an invisible area like a trigger, which snow falls inside of.
808 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
810 sets color of rain (default 12 - white)
812 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
814 void spawnfunc_func_snow()
816 self.dest = self.velocity;
817 self.velocity = '0 0 0';
819 self.dest = '0 0 -300';
820 self.angles = '0 0 0';
821 self.movetype = MOVETYPE_NONE;
822 self.solid = SOLID_NOT;
823 SetBrushEntityModel();
828 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
831 if(self.count > 65535)
834 self.state = 0; // 1 is rain, 0 is snow
837 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
841 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
844 void misc_laser_aim()
849 if(self.spawnflags & 2)
851 if(self.enemy.origin != self.mangle)
853 self.mangle = self.enemy.origin;
859 a = vectoangles(self.enemy.origin - self.origin);
870 if(self.angles != self.mangle)
872 self.mangle = self.angles;
876 if(self.origin != self.oldorigin)
879 self.oldorigin = self.origin;
883 void misc_laser_init()
885 if(self.target != "")
886 self.enemy = find(world, targetname, self.target);
890 void misc_laser_think()
895 self.nextthink = time;
904 o = self.enemy.origin;
905 if not(self.spawnflags & 2)
906 o = self.origin + normalize(o - self.origin) * 32768;
910 makevectors(self.mangle);
911 o = self.origin + v_forward * 32768;
917 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
919 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
922 if(self.enemy.target != "") // DETECTOR laser
924 traceline(self.origin, o, MOVE_NORMAL, self);
925 if(trace_ent.iscreature)
927 self.pusher = trace_ent;
934 activator = self.pusher;
947 activator = self.pusher;
955 float laser_SendEntity(entity to, float fl)
957 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
958 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
959 if(self.spawnflags & 2)
963 if(self.scale != 1 || self.modelscale != 1)
965 WriteByte(MSG_ENTITY, fl);
968 WriteCoord(MSG_ENTITY, self.origin_x);
969 WriteCoord(MSG_ENTITY, self.origin_y);
970 WriteCoord(MSG_ENTITY, self.origin_z);
974 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
975 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
976 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
978 WriteByte(MSG_ENTITY, self.alpha * 255.0);
981 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
982 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
984 WriteShort(MSG_ENTITY, self.cnt + 1);
990 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
991 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
992 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
996 WriteAngle(MSG_ENTITY, self.mangle_x);
997 WriteAngle(MSG_ENTITY, self.mangle_y);
1001 WriteByte(MSG_ENTITY, self.state);
1005 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1006 Any object touching the beam will be hurt
1009 spawnfunc_target_position where the laser ends
1011 name of beam end effect to use
1013 color of the beam (default: red)
1015 damage per second (-1 for a laser that kills immediately)
1019 self.state = !self.state;
1020 self.SendFlags |= 4;
1026 if(self.spawnflags & 1)
1032 void spawnfunc_misc_laser()
1036 if(self.mdl == "none")
1040 self.cnt = particleeffectnum(self.mdl);
1043 self.cnt = particleeffectnum("laser_deadly");
1049 self.cnt = particleeffectnum("laser_deadly");
1056 if(self.colormod == '0 0 0')
1058 self.colormod = '1 0 0';
1060 self.message = "saw the light";
1062 self.message2 = "was pushed into a laser by";
1065 if(!self.modelscale)
1066 self.modelscale = 1;
1067 self.think = misc_laser_think;
1068 self.nextthink = time;
1069 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1071 self.mangle = self.angles;
1073 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1077 self.reset = laser_reset;
1079 self.use = laser_use;
1085 // tZorks trigger impulse / gravity
1089 .float lastpushtime;
1091 // targeted (directional) mode
1092 void trigger_impulse_touch1()
1095 float pushdeltatime;
1098 // FIXME: Better checking for what to push and not.
1099 if not(other.iscreature)
1100 if (other.classname != "corpse")
1101 if (other.classname != "body")
1102 if (other.classname != "gib")
1103 if (other.classname != "missile")
1104 if (other.classname != "rocket")
1105 if (other.classname != "casing")
1106 if (other.classname != "grenade")
1107 if (other.classname != "plasma")
1108 if (other.classname != "plasma_prim")
1109 if (other.classname != "plasma_chain")
1110 if (other.classname != "droppedweapon")
1111 if (other.classname != "nexball_basketball")
1112 if (other.classname != "nexball_football")
1115 if (other.deadflag && other.iscreature)
1120 targ = find(world, targetname, self.target);
1123 objerror("trigger_force without a (valid) .target!\n");
1128 if(self.falloff == 1)
1129 str = (str / self.radius) * self.strength;
1130 else if(self.falloff == 2)
1131 str = (1 - (str / self.radius)) * self.strength;
1133 str = self.strength;
1135 pushdeltatime = time - other.lastpushtime;
1136 if (pushdeltatime > 0.15) pushdeltatime = 0;
1137 other.lastpushtime = time;
1138 if(!pushdeltatime) return;
1140 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1141 other.flags &~= FL_ONGROUND;
1144 // Directionless (accelerator/decelerator) mode
1145 void trigger_impulse_touch2()
1147 float pushdeltatime;
1149 // FIXME: Better checking for what to push and not.
1150 if not(other.iscreature)
1151 if (other.classname != "corpse")
1152 if (other.classname != "body")
1153 if (other.classname != "gib")
1154 if (other.classname != "missile")
1155 if (other.classname != "rocket")
1156 if (other.classname != "casing")
1157 if (other.classname != "grenade")
1158 if (other.classname != "plasma")
1159 if (other.classname != "plasma_prim")
1160 if (other.classname != "plasma_chain")
1161 if (other.classname != "droppedweapon")
1162 if (other.classname != "nexball_basketball")
1163 if (other.classname != "nexball_football")
1166 if (other.deadflag && other.iscreature)
1171 pushdeltatime = time - other.lastpushtime;
1172 if (pushdeltatime > 0.15) pushdeltatime = 0;
1173 other.lastpushtime = time;
1174 if(!pushdeltatime) return;
1176 // div0: ticrate independent, 1 = identity (not 20)
1177 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1180 // Spherical (gravity/repulsor) mode
1181 void trigger_impulse_touch3()
1183 float pushdeltatime;
1186 // FIXME: Better checking for what to push and not.
1187 if not(other.iscreature)
1188 if (other.classname != "corpse")
1189 if (other.classname != "body")
1190 if (other.classname != "gib")
1191 if (other.classname != "missile")
1192 if (other.classname != "rocket")
1193 if (other.classname != "casing")
1194 if (other.classname != "grenade")
1195 if (other.classname != "plasma")
1196 if (other.classname != "plasma_prim")
1197 if (other.classname != "plasma_chain")
1198 if (other.classname != "droppedweapon")
1199 if (other.classname != "nexball_basketball")
1200 if (other.classname != "nexball_football")
1203 if (other.deadflag && other.iscreature)
1208 pushdeltatime = time - other.lastpushtime;
1209 if (pushdeltatime > 0.15) pushdeltatime = 0;
1210 other.lastpushtime = time;
1211 if(!pushdeltatime) return;
1213 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1215 str = min(self.radius, vlen(self.origin - other.origin));
1217 if(self.falloff == 1)
1218 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1219 else if(self.falloff == 2)
1220 str = (str / self.radius) * self.strength; // 0 in the inside
1222 str = self.strength;
1224 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1227 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1228 -------- KEYS --------
1229 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1230 If not, this trigger acts like a damper/accelerator field.
1232 strength : This is how mutch force to add in the direction of .target each second
1233 when .target is set. If not, this is hoe mutch to slow down/accelerate
1234 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1236 radius : If set, act as a spherical device rather then a liniar one.
1238 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1240 -------- NOTES --------
1241 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1242 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1245 void spawnfunc_trigger_impulse()
1250 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1251 setorigin(self, self.origin);
1252 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1253 self.touch = trigger_impulse_touch3;
1259 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1260 self.touch = trigger_impulse_touch1;
1264 if(!self.strength) self.strength = 0.9;
1265 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1266 self.touch = trigger_impulse_touch2;
1271 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1272 "Flip-flop" trigger gate... lets only every second trigger event through
1276 self.state = !self.state;
1281 void spawnfunc_trigger_flipflop()
1283 if(self.spawnflags & 1)
1285 self.use = flipflop_use;
1286 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1289 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1290 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1294 self.nextthink = time + self.wait;
1295 self.enemy = activator;
1301 void monoflop_fixed_use()
1305 self.nextthink = time + self.wait;
1307 self.enemy = activator;
1311 void monoflop_think()
1314 activator = self.enemy;
1318 void monoflop_reset()
1324 void spawnfunc_trigger_monoflop()
1328 if(self.spawnflags & 1)
1329 self.use = monoflop_fixed_use;
1331 self.use = monoflop_use;
1332 self.think = monoflop_think;
1334 self.reset = monoflop_reset;
1337 void multivibrator_send()
1342 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1344 newstate = (time < cyclestart + self.wait);
1347 if(self.state != newstate)
1349 self.state = newstate;
1352 self.nextthink = cyclestart + self.wait + 0.01;
1354 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1357 void multivibrator_toggle()
1359 if(self.nextthink == 0)
1361 multivibrator_send();
1374 void multivibrator_reset()
1376 if(!(self.spawnflags & 1))
1377 self.nextthink = 0; // wait for a trigger event
1379 self.nextthink = max(1, time);
1382 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1383 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1384 -------- KEYS --------
1385 target: trigger all entities with this targetname when it goes off
1386 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1387 phase: offset of the timing
1388 wait: "on" cycle time (default: 1)
1389 respawntime: "off" cycle time (default: same as wait)
1390 -------- SPAWNFLAGS --------
1391 START_ON: assume it is already turned on (when targeted)
1393 void spawnfunc_trigger_multivibrator()
1397 if(!self.respawntime)
1398 self.respawntime = self.wait;
1401 self.use = multivibrator_toggle;
1402 self.think = multivibrator_send;
1403 self.nextthink = time;
1406 multivibrator_reset();
1415 if(self.killtarget != "")
1416 src = find(world, targetname, self.killtarget);
1417 if(self.target != "")
1418 dst = find(world, targetname, self.target);
1422 objerror("follow: could not find target/killtarget");
1428 // already done :P entity must stay
1432 else if(!src || !dst)
1434 objerror("follow: could not find target/killtarget");
1437 else if(self.spawnflags & 1)
1440 if(self.spawnflags & 2)
1442 setattachment(dst, src, self.message);
1446 attach_sameorigin(dst, src, self.message);
1453 if(self.spawnflags & 2)
1455 dst.movetype = MOVETYPE_FOLLOW;
1457 // dst.punchangle = '0 0 0'; // keep unchanged
1458 dst.view_ofs = dst.origin;
1459 dst.v_angle = dst.angles;
1463 follow_sameorigin(dst, src);
1470 void spawnfunc_misc_follow()
1472 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1477 void gamestart_use() {
1483 void spawnfunc_trigger_gamestart() {
1484 self.use = gamestart_use;
1485 self.reset2 = spawnfunc_trigger_gamestart;
1489 self.think = self.use;
1490 self.nextthink = game_starttime + self.wait;
1493 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1499 .entity voicescript; // attached voice script
1500 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1501 .float voicescript_nextthink; // time to play next voice
1502 .float voicescript_voiceend; // time when this voice ends
1504 void target_voicescript_clear(entity pl)
1506 pl.voicescript = world;
1509 void target_voicescript_use()
1511 if(activator.voicescript != self)
1513 activator.voicescript = self;
1514 activator.voicescript_index = 0;
1515 activator.voicescript_nextthink = time + self.delay;
1519 void target_voicescript_next(entity pl)
1524 vs = pl.voicescript;
1527 if(vs.message == "")
1529 if(pl.classname != "player")
1534 if(time >= pl.voicescript_voiceend)
1536 if(time >= pl.voicescript_nextthink)
1538 // get the next voice...
1539 n = tokenize_console(vs.message);
1541 if(pl.voicescript_index < vs.cnt)
1542 i = pl.voicescript_index * 2;
1543 else if(n > vs.cnt * 2)
1544 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1550 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1551 dt = stof(argv(i + 1));
1554 pl.voicescript_voiceend = time + dt;
1555 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1559 pl.voicescript_voiceend = time - dt;
1560 pl.voicescript_nextthink = pl.voicescript_voiceend;
1563 pl.voicescript_index += 1;
1567 pl.voicescript = world; // stop trying then
1573 void spawnfunc_target_voicescript()
1575 // netname: directory of the sound files
1576 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1577 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1578 // Here, a - in front of the duration means that no delay is to be
1579 // added after this message
1580 // wait: average time between messages
1581 // delay: initial delay before the first message
1584 self.use = target_voicescript_use;
1586 n = tokenize_console(self.message);
1588 for(i = 0; i+1 < n; i += 2)
1595 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1601 void trigger_relay_teamcheck_use()
1605 if(self.spawnflags & 2)
1607 if(activator.team != self.team)
1612 if(activator.team == self.team)
1618 if(self.spawnflags & 1)
1623 void trigger_relay_teamcheck_reset()
1625 self.team = self.team_saved;
1628 void spawnfunc_trigger_relay_teamcheck()
1630 self.team_saved = self.team;
1631 self.use = trigger_relay_teamcheck_use;
1632 self.reset = trigger_relay_teamcheck_reset;
1637 void trigger_disablerelay_use()
1644 for(e = world; (e = find(e, targetname, self.target)); )
1646 if(e.use == SUB_UseTargets)
1648 e.use = SUB_DontUseTargets;
1651 else if(e.use == SUB_DontUseTargets)
1653 e.use = SUB_UseTargets;
1659 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1662 void spawnfunc_trigger_disablerelay()
1664 self.use = trigger_disablerelay_use;
1667 float magicear_matched;
1668 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1670 float domatch, dotrigger, matchstart, l;
1674 magicear_matched = FALSE;
1676 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1677 domatch = ((ear.spawnflags & 32) || dotrigger);
1683 if(ear.spawnflags & 4)
1689 if(ear.spawnflags & 1)
1692 if(ear.spawnflags & 2)
1695 if(ear.spawnflags & 8)
1700 l = strlen(ear.message);
1702 if(self.spawnflags & 128)
1705 msg = strdecolorize(msgin);
1707 if(substring(ear.message, 0, 1) == "*")
1709 if(substring(ear.message, -1, 1) == "*")
1712 // as we need multi-replacement here...
1713 s = substring(ear.message, 1, -2);
1715 if(strstrofs(msg, s, 0) >= 0)
1716 matchstart = -2; // we use strreplace on s
1721 s = substring(ear.message, 1, -1);
1723 if(substring(msg, -l, l) == s)
1724 matchstart = strlen(msg) - l;
1729 if(substring(ear.message, -1, 1) == "*")
1732 s = substring(ear.message, 0, -2);
1734 if(substring(msg, 0, l) == s)
1741 if(msg == ear.message)
1746 if(matchstart == -1) // no match
1749 magicear_matched = TRUE;
1753 oldself = activator = self;
1759 if(ear.spawnflags & 16)
1763 else if(ear.netname != "")
1766 return strreplace(s, ear.netname, msg);
1769 substring(msg, 0, matchstart),
1771 substring(msg, matchstart + l, -1)
1779 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1783 for(ear = magicears; ear; ear = ear.enemy)
1785 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1786 if not(ear.spawnflags & 64)
1787 if(magicear_matched)
1794 void spawnfunc_trigger_magicear()
1796 self.enemy = magicears;
1799 // actually handled in "say" processing
1802 // 2 = ignore teamsay
1804 // 8 = ignore tell to unknown player
1805 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1806 // 32 = perform the replacement even if outside the radius or dead
1807 // 64 = continue replacing/triggering even if this one matched
1817 // if set, replacement for the matched text
1819 // "hearing distance"