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 if (self.active != ACTIVE_ACTIVE)
419 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
420 if (other.iscreature)
422 if (other.takedamage)
423 if (other.triggerhurttime < time)
426 other.triggerhurttime = time + 1;
427 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
434 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
437 other.pain_finished = min(other.pain_finished, time + 2);
439 else if (other.classname == "rune") // reset runes
442 other.nextthink = min(other.nextthink, time + 1);
450 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
451 Any object touching this will be hurt
452 set dmg to damage amount
455 .entity trigger_hurt_next;
456 entity trigger_hurt_last;
457 entity trigger_hurt_first;
458 void spawnfunc_trigger_hurt()
461 self.active = ACTIVE_ACTIVE;
462 self.touch = trigger_hurt_touch;
466 self.message = "was in the wrong place";
468 self.message2 = "was thrown into a world of hurt by";
469 // self.message = "someone like %s always gets wrongplaced";
471 if(!trigger_hurt_first)
472 trigger_hurt_first = self;
473 if(trigger_hurt_last)
474 trigger_hurt_last.trigger_hurt_next = self;
475 trigger_hurt_last = self;
478 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
482 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
483 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
489 //////////////////////////////////////////////////////////////
493 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
495 //////////////////////////////////////////////////////////////
497 .float triggerhealtime;
498 void trigger_heal_touch()
500 if (self.active != ACTIVE_ACTIVE)
503 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
504 if (other.iscreature)
506 if (other.takedamage)
507 if (other.triggerhealtime < time)
510 other.triggerhealtime = time + 1;
512 if (other.health < self.max_health)
514 other.health = min(other.health + self.health, self.max_health);
515 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
516 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
522 void spawnfunc_trigger_heal()
524 self.active = ACTIVE_ACTIVE;
527 self.touch = trigger_heal_touch;
530 if (!self.max_health)
531 self.max_health = 200; //Max health topoff for field
533 self.noise = "misc/mediumhealth.wav";
534 precache_sound(self.noise);
538 //////////////////////////////////////////////////////////////
544 //////////////////////////////////////////////////////////////
546 .entity trigger_gravity_check;
547 void trigger_gravity_remove(entity own)
549 if(own.trigger_gravity_check.owner == own)
551 UpdateCSQCProjectile(own);
552 own.gravity = own.trigger_gravity_check.gravity;
553 remove(own.trigger_gravity_check);
556 backtrace("Removing a trigger_gravity_check with no valid owner");
557 own.trigger_gravity_check = world;
559 void trigger_gravity_check_think()
561 // This spawns when a player enters the gravity zone and checks if he left.
562 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
563 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
566 if(self.owner.trigger_gravity_check == self)
567 trigger_gravity_remove(self.owner);
575 self.nextthink = time;
579 void trigger_gravity_use()
581 self.state = !self.state;
584 void trigger_gravity_touch()
588 if(self.state != TRUE)
595 if not(self.spawnflags & 1)
597 if(other.trigger_gravity_check)
599 if(self == other.trigger_gravity_check.enemy)
602 other.trigger_gravity_check.count = 2; // gravity one more frame...
607 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
608 trigger_gravity_remove(other);
612 other.trigger_gravity_check = spawn();
613 other.trigger_gravity_check.enemy = self;
614 other.trigger_gravity_check.owner = other;
615 other.trigger_gravity_check.gravity = other.gravity;
616 other.trigger_gravity_check.think = trigger_gravity_check_think;
617 other.trigger_gravity_check.nextthink = time;
618 other.trigger_gravity_check.count = 2;
623 if (other.gravity != g)
627 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
628 UpdateCSQCProjectile(self.owner);
632 void spawnfunc_trigger_gravity()
634 if(self.gravity == 1)
638 self.touch = trigger_gravity_touch;
640 precache_sound(self.noise);
645 self.use = trigger_gravity_use;
646 if(self.spawnflags & 2)
651 //=============================================================================
653 // TODO add a way to do looped sounds with sound(); then complete this entity
654 .float volume, atten;
655 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
657 void spawnfunc_target_speaker()
660 precache_sound (self.noise);
664 self.atten = ATTN_NORM;
665 else if(self.atten < 0)
669 self.use = target_speaker_use;
674 self.atten = ATTN_STATIC;
675 else if(self.atten < 0)
679 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
684 void spawnfunc_func_stardust() {
685 self.effects = EF_STARDUST;
689 .float bgmscriptattack;
690 .float bgmscriptdecay;
691 .float bgmscriptsustain;
692 .float bgmscriptrelease;
693 float pointparticles_SendEntity(entity to, float fl)
695 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
697 // optional features to save space
699 if(self.spawnflags & 2)
700 fl |= 0x10; // absolute count on toggle-on
701 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
702 fl |= 0x20; // 4 bytes - saves CPU
703 if(self.waterlevel || self.count != 1)
704 fl |= 0x40; // 4 bytes - obscure features almost never used
705 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
706 fl |= 0x80; // 14 bytes - saves lots of space
708 WriteByte(MSG_ENTITY, fl);
712 WriteCoord(MSG_ENTITY, self.impulse);
714 WriteCoord(MSG_ENTITY, 0); // off
718 WriteCoord(MSG_ENTITY, self.origin_x);
719 WriteCoord(MSG_ENTITY, self.origin_y);
720 WriteCoord(MSG_ENTITY, self.origin_z);
724 if(self.model != "null")
726 WriteShort(MSG_ENTITY, self.modelindex);
729 WriteCoord(MSG_ENTITY, self.mins_x);
730 WriteCoord(MSG_ENTITY, self.mins_y);
731 WriteCoord(MSG_ENTITY, self.mins_z);
732 WriteCoord(MSG_ENTITY, self.maxs_x);
733 WriteCoord(MSG_ENTITY, self.maxs_y);
734 WriteCoord(MSG_ENTITY, self.maxs_z);
739 WriteShort(MSG_ENTITY, 0);
742 WriteCoord(MSG_ENTITY, self.maxs_x);
743 WriteCoord(MSG_ENTITY, self.maxs_y);
744 WriteCoord(MSG_ENTITY, self.maxs_z);
747 WriteShort(MSG_ENTITY, self.cnt);
750 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
751 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
755 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
756 WriteByte(MSG_ENTITY, self.count * 16.0);
758 WriteString(MSG_ENTITY, self.noise);
761 WriteByte(MSG_ENTITY, floor(self.atten * 64));
762 WriteByte(MSG_ENTITY, floor(self.volume * 255));
764 WriteString(MSG_ENTITY, self.bgmscript);
765 if(self.bgmscript != "")
767 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
768 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
769 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
770 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
776 void pointparticles_use()
778 self.state = !self.state;
782 void pointparticles_think()
784 if(self.origin != self.oldorigin)
787 self.oldorigin = self.origin;
789 self.nextthink = time;
792 void pointparticles_reset()
794 if(self.spawnflags & 1)
800 void spawnfunc_func_pointparticles()
803 setmodel(self, self.model);
805 precache_sound (self.noise);
807 if(!self.bgmscriptsustain)
808 self.bgmscriptsustain = 1;
809 else if(self.bgmscriptsustain < 0)
810 self.bgmscriptsustain = 0;
813 self.atten = ATTN_NORM;
814 else if(self.atten < 0)
825 setorigin(self, self.origin + self.mins);
826 setsize(self, '0 0 0', self.maxs - self.mins);
829 self.cnt = particleeffectnum(self.mdl);
831 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
835 self.use = pointparticles_use;
836 self.reset = pointparticles_reset;
841 self.think = pointparticles_think;
842 self.nextthink = time;
845 void spawnfunc_func_sparks()
847 // self.cnt is the amount of sparks that one burst will spawn
849 self.cnt = 25.0; // nice default value
852 // self.wait is the probability that a sparkthink will spawn a spark shower
853 // range: 0 - 1, but 0 makes little sense, so...
854 if(self.wait < 0.05) {
855 self.wait = 0.25; // nice default value
858 self.count = self.cnt;
861 self.velocity = '0 0 -1';
862 self.mdl = "TE_SPARK";
863 self.impulse = 10 * self.wait; // by default 2.5/sec
865 self.cnt = 0; // use mdl
867 spawnfunc_func_pointparticles();
870 float rainsnow_SendEntity(entity to, float sf)
872 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
873 WriteByte(MSG_ENTITY, self.state);
874 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
875 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
876 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
877 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
878 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
879 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
880 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
881 WriteShort(MSG_ENTITY, self.count);
882 WriteByte(MSG_ENTITY, self.cnt);
886 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
887 This is an invisible area like a trigger, which rain falls inside of.
891 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
893 sets color of rain (default 12 - white)
895 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
897 void spawnfunc_func_rain()
899 self.dest = self.velocity;
900 self.velocity = '0 0 0';
902 self.dest = '0 0 -700';
903 self.angles = '0 0 0';
904 self.movetype = MOVETYPE_NONE;
905 self.solid = SOLID_NOT;
906 SetBrushEntityModel();
911 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
914 if(self.count > 65535)
917 self.state = 1; // 1 is rain, 0 is snow
920 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
924 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
925 This is an invisible area like a trigger, which snow falls inside of.
929 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
931 sets color of rain (default 12 - white)
933 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
935 void spawnfunc_func_snow()
937 self.dest = self.velocity;
938 self.velocity = '0 0 0';
940 self.dest = '0 0 -300';
941 self.angles = '0 0 0';
942 self.movetype = MOVETYPE_NONE;
943 self.solid = SOLID_NOT;
944 SetBrushEntityModel();
949 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
952 if(self.count > 65535)
955 self.state = 0; // 1 is rain, 0 is snow
958 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
962 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
965 void misc_laser_aim()
970 if(self.spawnflags & 2)
972 if(self.enemy.origin != self.mangle)
974 self.mangle = self.enemy.origin;
980 a = vectoangles(self.enemy.origin - self.origin);
991 if(self.angles != self.mangle)
993 self.mangle = self.angles;
997 if(self.origin != self.oldorigin)
1000 self.oldorigin = self.origin;
1004 void misc_laser_init()
1006 if(self.target != "")
1007 self.enemy = find(world, targetname, self.target);
1011 void misc_laser_think()
1016 self.nextthink = time;
1025 o = self.enemy.origin;
1026 if not(self.spawnflags & 2)
1027 o = self.origin + normalize(o - self.origin) * 32768;
1031 makevectors(self.mangle);
1032 o = self.origin + v_forward * 32768;
1038 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1040 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
1043 if(self.enemy.target != "") // DETECTOR laser
1045 traceline(self.origin, o, MOVE_NORMAL, self);
1046 if(trace_ent.iscreature)
1048 self.pusher = trace_ent;
1055 activator = self.pusher;
1068 activator = self.pusher;
1076 float laser_SendEntity(entity to, float fl)
1078 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1079 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1080 if(self.spawnflags & 2)
1084 if(self.scale != 1 || self.modelscale != 1)
1086 if(self.spawnflags & 4)
1088 WriteByte(MSG_ENTITY, fl);
1091 WriteCoord(MSG_ENTITY, self.origin_x);
1092 WriteCoord(MSG_ENTITY, self.origin_y);
1093 WriteCoord(MSG_ENTITY, self.origin_z);
1097 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1098 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1099 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1101 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1104 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1105 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1107 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1108 WriteShort(MSG_ENTITY, self.cnt + 1);
1114 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1115 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1116 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1120 WriteAngle(MSG_ENTITY, self.mangle_x);
1121 WriteAngle(MSG_ENTITY, self.mangle_y);
1125 WriteByte(MSG_ENTITY, self.state);
1129 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1130 Any object touching the beam will be hurt
1133 spawnfunc_target_position where the laser ends
1135 name of beam end effect to use
1137 color of the beam (default: red)
1139 damage per second (-1 for a laser that kills immediately)
1143 self.state = !self.state;
1144 self.SendFlags |= 4;
1150 if(self.spawnflags & 1)
1156 void spawnfunc_misc_laser()
1160 if(self.mdl == "none")
1164 self.cnt = particleeffectnum(self.mdl);
1167 self.cnt = particleeffectnum("laser_deadly");
1173 self.cnt = particleeffectnum("laser_deadly");
1180 if(self.colormod == '0 0 0')
1182 self.colormod = '1 0 0';
1184 self.message = "saw the light";
1186 self.message2 = "was pushed into a laser by";
1189 if(!self.modelscale)
1190 self.modelscale = 1;
1191 else if(self.modelscale < 0)
1192 self.modelscale = 0;
1193 self.think = misc_laser_think;
1194 self.nextthink = time;
1195 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1197 self.mangle = self.angles;
1199 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1203 self.reset = laser_reset;
1205 self.use = laser_use;
1211 // tZorks trigger impulse / gravity
1215 .float lastpushtime;
1217 // targeted (directional) mode
1218 void trigger_impulse_touch1()
1221 float pushdeltatime;
1224 if (self.active != ACTIVE_ACTIVE)
1227 // FIXME: Better checking for what to push and not.
1228 if not(other.iscreature)
1229 if (other.classname != "corpse")
1230 if (other.classname != "body")
1231 if (other.classname != "gib")
1232 if (other.classname != "missile")
1233 if (other.classname != "rocket")
1234 if (other.classname != "casing")
1235 if (other.classname != "grenade")
1236 if (other.classname != "plasma")
1237 if (other.classname != "plasma_prim")
1238 if (other.classname != "plasma_chain")
1239 if (other.classname != "droppedweapon")
1240 if (other.classname != "nexball_basketball")
1241 if (other.classname != "nexball_football")
1244 if (other.deadflag && other.iscreature)
1249 targ = find(world, targetname, self.target);
1252 objerror("trigger_force without a (valid) .target!\n");
1257 if(self.falloff == 1)
1258 str = (str / self.radius) * self.strength;
1259 else if(self.falloff == 2)
1260 str = (1 - (str / self.radius)) * self.strength;
1262 str = self.strength;
1264 pushdeltatime = time - other.lastpushtime;
1265 if (pushdeltatime > 0.15) pushdeltatime = 0;
1266 other.lastpushtime = time;
1267 if(!pushdeltatime) return;
1269 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1270 other.flags &~= FL_ONGROUND;
1271 UpdateCSQCProjectile(other);
1274 // Directionless (accelerator/decelerator) mode
1275 void trigger_impulse_touch2()
1277 float pushdeltatime;
1279 if (self.active != ACTIVE_ACTIVE)
1282 // FIXME: Better checking for what to push and not.
1283 if not(other.iscreature)
1284 if (other.classname != "corpse")
1285 if (other.classname != "body")
1286 if (other.classname != "gib")
1287 if (other.classname != "missile")
1288 if (other.classname != "rocket")
1289 if (other.classname != "casing")
1290 if (other.classname != "grenade")
1291 if (other.classname != "plasma")
1292 if (other.classname != "plasma_prim")
1293 if (other.classname != "plasma_chain")
1294 if (other.classname != "droppedweapon")
1295 if (other.classname != "nexball_basketball")
1296 if (other.classname != "nexball_football")
1299 if (other.deadflag && other.iscreature)
1304 pushdeltatime = time - other.lastpushtime;
1305 if (pushdeltatime > 0.15) pushdeltatime = 0;
1306 other.lastpushtime = time;
1307 if(!pushdeltatime) return;
1309 // div0: ticrate independent, 1 = identity (not 20)
1310 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1311 UpdateCSQCProjectile(other);
1314 // Spherical (gravity/repulsor) mode
1315 void trigger_impulse_touch3()
1317 float pushdeltatime;
1320 if (self.active != ACTIVE_ACTIVE)
1323 // FIXME: Better checking for what to push and not.
1324 if not(other.iscreature)
1325 if (other.classname != "corpse")
1326 if (other.classname != "body")
1327 if (other.classname != "gib")
1328 if (other.classname != "missile")
1329 if (other.classname != "rocket")
1330 if (other.classname != "casing")
1331 if (other.classname != "grenade")
1332 if (other.classname != "plasma")
1333 if (other.classname != "plasma_prim")
1334 if (other.classname != "plasma_chain")
1335 if (other.classname != "droppedweapon")
1336 if (other.classname != "nexball_basketball")
1337 if (other.classname != "nexball_football")
1340 if (other.deadflag && other.iscreature)
1345 pushdeltatime = time - other.lastpushtime;
1346 if (pushdeltatime > 0.15) pushdeltatime = 0;
1347 other.lastpushtime = time;
1348 if(!pushdeltatime) return;
1350 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1352 str = min(self.radius, vlen(self.origin - other.origin));
1354 if(self.falloff == 1)
1355 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1356 else if(self.falloff == 2)
1357 str = (str / self.radius) * self.strength; // 0 in the inside
1359 str = self.strength;
1361 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1362 UpdateCSQCProjectile(other);
1365 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1366 -------- KEYS --------
1367 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1368 If not, this trigger acts like a damper/accelerator field.
1370 strength : This is how mutch force to add in the direction of .target each second
1371 when .target is set. If not, this is hoe mutch to slow down/accelerate
1372 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1374 radius : If set, act as a spherical device rather then a liniar one.
1376 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1378 -------- NOTES --------
1379 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1380 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1383 void spawnfunc_trigger_impulse()
1385 self.active = ACTIVE_ACTIVE;
1390 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
1391 setorigin(self, self.origin);
1392 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1393 self.touch = trigger_impulse_touch3;
1399 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
1400 self.touch = trigger_impulse_touch1;
1404 if(!self.strength) self.strength = 0.9;
1405 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
1406 self.touch = trigger_impulse_touch2;
1411 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1412 "Flip-flop" trigger gate... lets only every second trigger event through
1416 self.state = !self.state;
1421 void spawnfunc_trigger_flipflop()
1423 if(self.spawnflags & 1)
1425 self.use = flipflop_use;
1426 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1429 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1430 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1434 self.nextthink = time + self.wait;
1435 self.enemy = activator;
1441 void monoflop_fixed_use()
1445 self.nextthink = time + self.wait;
1447 self.enemy = activator;
1451 void monoflop_think()
1454 activator = self.enemy;
1458 void monoflop_reset()
1464 void spawnfunc_trigger_monoflop()
1468 if(self.spawnflags & 1)
1469 self.use = monoflop_fixed_use;
1471 self.use = monoflop_use;
1472 self.think = monoflop_think;
1474 self.reset = monoflop_reset;
1477 void multivibrator_send()
1482 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1484 newstate = (time < cyclestart + self.wait);
1487 if(self.state != newstate)
1489 self.state = newstate;
1492 self.nextthink = cyclestart + self.wait + 0.01;
1494 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1497 void multivibrator_toggle()
1499 if(self.nextthink == 0)
1501 multivibrator_send();
1514 void multivibrator_reset()
1516 if(!(self.spawnflags & 1))
1517 self.nextthink = 0; // wait for a trigger event
1519 self.nextthink = max(1, time);
1522 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1523 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1524 -------- KEYS --------
1525 target: trigger all entities with this targetname when it goes off
1526 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1527 phase: offset of the timing
1528 wait: "on" cycle time (default: 1)
1529 respawntime: "off" cycle time (default: same as wait)
1530 -------- SPAWNFLAGS --------
1531 START_ON: assume it is already turned on (when targeted)
1533 void spawnfunc_trigger_multivibrator()
1537 if(!self.respawntime)
1538 self.respawntime = self.wait;
1541 self.use = multivibrator_toggle;
1542 self.think = multivibrator_send;
1543 self.nextthink = time;
1546 multivibrator_reset();
1555 if(self.killtarget != "")
1556 src = find(world, targetname, self.killtarget);
1557 if(self.target != "")
1558 dst = find(world, targetname, self.target);
1562 objerror("follow: could not find target/killtarget");
1568 // already done :P entity must stay
1572 else if(!src || !dst)
1574 objerror("follow: could not find target/killtarget");
1577 else if(self.spawnflags & 1)
1580 if(self.spawnflags & 2)
1582 setattachment(dst, src, self.message);
1586 attach_sameorigin(dst, src, self.message);
1593 if(self.spawnflags & 2)
1595 dst.movetype = MOVETYPE_FOLLOW;
1597 // dst.punchangle = '0 0 0'; // keep unchanged
1598 dst.view_ofs = dst.origin;
1599 dst.v_angle = dst.angles;
1603 follow_sameorigin(dst, src);
1610 void spawnfunc_misc_follow()
1612 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1617 void gamestart_use() {
1623 void spawnfunc_trigger_gamestart() {
1624 self.use = gamestart_use;
1625 self.reset2 = spawnfunc_trigger_gamestart;
1629 self.think = self.use;
1630 self.nextthink = game_starttime + self.wait;
1633 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1639 .entity voicescript; // attached voice script
1640 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1641 .float voicescript_nextthink; // time to play next voice
1642 .float voicescript_voiceend; // time when this voice ends
1644 void target_voicescript_clear(entity pl)
1646 pl.voicescript = world;
1649 void target_voicescript_use()
1651 if(activator.voicescript != self)
1653 activator.voicescript = self;
1654 activator.voicescript_index = 0;
1655 activator.voicescript_nextthink = time + self.delay;
1659 void target_voicescript_next(entity pl)
1664 vs = pl.voicescript;
1667 if(vs.message == "")
1669 if(pl.classname != "player")
1674 if(time >= pl.voicescript_voiceend)
1676 if(time >= pl.voicescript_nextthink)
1678 // get the next voice...
1679 n = tokenize_console(vs.message);
1681 if(pl.voicescript_index < vs.cnt)
1682 i = pl.voicescript_index * 2;
1683 else if(n > vs.cnt * 2)
1684 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1690 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1691 dt = stof(argv(i + 1));
1694 pl.voicescript_voiceend = time + dt;
1695 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1699 pl.voicescript_voiceend = time - dt;
1700 pl.voicescript_nextthink = pl.voicescript_voiceend;
1703 pl.voicescript_index += 1;
1707 pl.voicescript = world; // stop trying then
1713 void spawnfunc_target_voicescript()
1715 // netname: directory of the sound files
1716 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1717 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1718 // Here, a - in front of the duration means that no delay is to be
1719 // added after this message
1720 // wait: average time between messages
1721 // delay: initial delay before the first message
1724 self.use = target_voicescript_use;
1726 n = tokenize_console(self.message);
1728 for(i = 0; i+1 < n; i += 2)
1735 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1741 void trigger_relay_teamcheck_use()
1745 if(self.spawnflags & 2)
1747 if(activator.team != self.team)
1752 if(activator.team == self.team)
1758 if(self.spawnflags & 1)
1763 void trigger_relay_teamcheck_reset()
1765 self.team = self.team_saved;
1768 void spawnfunc_trigger_relay_teamcheck()
1770 self.team_saved = self.team;
1771 self.use = trigger_relay_teamcheck_use;
1772 self.reset = trigger_relay_teamcheck_reset;
1777 void trigger_disablerelay_use()
1784 for(e = world; (e = find(e, targetname, self.target)); )
1786 if(e.use == SUB_UseTargets)
1788 e.use = SUB_DontUseTargets;
1791 else if(e.use == SUB_DontUseTargets)
1793 e.use = SUB_UseTargets;
1799 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1802 void spawnfunc_trigger_disablerelay()
1804 self.use = trigger_disablerelay_use;
1807 float magicear_matched;
1808 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1810 float domatch, dotrigger, matchstart, l;
1814 magicear_matched = FALSE;
1816 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1817 domatch = ((ear.spawnflags & 32) || dotrigger);
1823 if(ear.spawnflags & 4)
1829 if(ear.spawnflags & 1)
1832 if(ear.spawnflags & 2)
1835 if(ear.spawnflags & 8)
1840 l = strlen(ear.message);
1842 if(self.spawnflags & 128)
1845 msg = strdecolorize(msgin);
1847 if(substring(ear.message, 0, 1) == "*")
1849 if(substring(ear.message, -1, 1) == "*")
1852 // as we need multi-replacement here...
1853 s = substring(ear.message, 1, -2);
1855 if(strstrofs(msg, s, 0) >= 0)
1856 matchstart = -2; // we use strreplace on s
1861 s = substring(ear.message, 1, -1);
1863 if(substring(msg, -l, l) == s)
1864 matchstart = strlen(msg) - l;
1869 if(substring(ear.message, -1, 1) == "*")
1872 s = substring(ear.message, 0, -2);
1874 if(substring(msg, 0, l) == s)
1881 if(msg == ear.message)
1886 if(matchstart == -1) // no match
1889 magicear_matched = TRUE;
1893 oldself = activator = self;
1899 if(ear.spawnflags & 16)
1903 else if(ear.netname != "")
1906 return strreplace(s, ear.netname, msg);
1909 substring(msg, 0, matchstart),
1911 substring(msg, matchstart + l, -1)
1919 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
1923 for(ear = magicears; ear; ear = ear.enemy)
1925 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
1926 if not(ear.spawnflags & 64)
1927 if(magicear_matched)
1934 void spawnfunc_trigger_magicear()
1936 self.enemy = magicears;
1939 // actually handled in "say" processing
1942 // 2 = ignore teamsay
1944 // 8 = ignore tell to unknown player
1945 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
1946 // 32 = perform the replacement even if outside the radius or dead
1947 // 64 = continue replacing/triggering even if this one matched
1957 // if set, replacement for the matched text
1959 // "hearing distance"
1964 void relay_activators_use()
1970 for(trg = world; (trg = find(trg, targetname, os.target)); )
1974 trg.setactive(os.cnt);
1977 //bprint("Not using setactive\n");
1978 if(os.cnt == ACTIVE_TOGGLE)
1979 if(trg.active == ACTIVE_ACTIVE)
1980 trg.active = ACTIVE_NOT;
1982 trg.active = ACTIVE_ACTIVE;
1984 trg.active = os.cnt;
1990 void spawnfunc_relay_activate()
1992 self.cnt = ACTIVE_ACTIVE;
1993 self.use = relay_activators_use;
1996 void spawnfunc_relay_deactivate()
1998 self.cnt = ACTIVE_NOT;
1999 self.use = relay_activators_use;
2002 void spawnfunc_relay_activatetoggle()
2004 self.cnt = ACTIVE_TOGGLE;
2005 self.use = relay_activators_use;
2008 .string chmap, gametype;
2009 void spawnfunc_target_changelevel_use()
2011 if(self.gametype != "")
2012 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2014 if (self.chmap == "")
2015 localcmd("endmatch\n");
2017 localcmd(strcat("changelevel ", self.chmap, "\n"));
2020 void spawnfunc_target_changelevel()
2022 self.use = spawnfunc_target_changelevel_use;