1 #include "g_triggers.qh"
2 #include "t_jumppads.qh"
4 void SUB_DontUseTargets()
11 activator = self.enemy;
17 ==============================
20 the global "activator" should be set to the entity that initiated the firing.
22 If self.delay is set, a DelayedUse entity will be created that will actually
23 do the SUB_UseTargets after that many seconds have passed.
25 Centerprints any self.message to the activator.
27 Removes all entities with a targetname that match self.killtarget,
28 and removes them, so some events can remove other triggers.
30 Search for (string)targetname in all entities that
31 match (string)self.target and call their .use function
33 ==============================
37 entity t, stemp, otemp, act;
46 // create a temp object to fire at a later time
48 t.classname = "DelayedUse";
49 t.nextthink = time + self.delay;
52 t.message = self.message;
53 t.killtarget = self.killtarget;
54 t.target = self.target;
55 t.target2 = self.target2;
56 t.target3 = self.target3;
57 t.target4 = self.target4;
66 if(IS_PLAYER(activator) && self.message != "")
67 if(IS_REAL_CLIENT(activator))
69 centerprint(activator, self.message);
71 play2(activator, "misc/talk.wav");
75 // kill the killtagets
80 for(t = world; (t = find(t, targetname, s)); )
91 if(stemp.target_random)
92 RandomSelection_Init();
94 for(i = 0; i < 4; ++i)
99 case 0: s = stemp.target; break;
100 case 1: s = stemp.target2; break;
101 case 2: s = stemp.target3; break;
102 case 3: s = stemp.target4; break;
106 // Flag to set func_clientwall state
107 // 1 == deactivate, 2 == activate, 0 == do nothing
108 float aw_inactive = self.clientwall_flag;
109 for(t = world; (t = find(t, targetname, s)); )
112 if(stemp.target_random)
114 RandomSelection_Add(t, 0, string_null, 1, 0);
118 if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary")
120 if (aw_inactive == 1)
124 else if (aw_inactive == 2)
134 t.solid = t.default_solid;
146 if(stemp.target_random && RandomSelection_chosen_ent)
148 self = RandomSelection_chosen_ent;
160 //=============================================================================
162 // the wait time has passed, so set back up for another activation
167 self.health = self.max_health;
168 self.takedamage = DAMAGE_YES;
169 self.solid = SOLID_BBOX;
174 // the trigger was just touched/killed/used
175 // self.enemy should be set to the activator so it can be held through a delay
176 // so wait for the delay time before firing
179 if (self.nextthink > time)
181 return; // allready been triggered
184 if (self.classname == "trigger_secret")
186 if (!IS_PLAYER(self.enemy))
188 found_secrets = found_secrets + 1;
189 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
193 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
195 // don't trigger again until reset
196 self.takedamage = DAMAGE_NO;
198 activator = self.enemy;
199 other = self.goalentity;
204 self.think = multi_wait;
205 self.nextthink = time + self.wait;
207 else if (self.wait == 0)
209 multi_wait(); // waiting finished
212 { // we can't just remove (self) here, because this is a touch function
213 // called wheil C code is looping through area links...
214 self.touch = func_null;
220 self.goalentity = other;
221 self.enemy = activator;
227 if(!(self.spawnflags & 2))
228 if(!other.iscreature)
232 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
235 // if the trigger has an angles field, check player's facing direction
236 if (self.movedir != '0 0 0')
238 makevectors (other.angles);
239 if (v_forward * self.movedir < 0)
240 return; // not facing the right way
246 self.goalentity = other;
250 void multi_eventdamage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
252 if (!self.takedamage)
254 if(self.spawnflags & DOOR_NOSPLASH)
255 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
257 self.health = self.health - damage;
258 if (self.health <= 0)
260 self.enemy = attacker;
261 self.goalentity = inflictor;
268 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
269 self.touch = multi_touch;
272 self.health = self.max_health;
273 self.takedamage = DAMAGE_YES;
274 self.solid = SOLID_BBOX;
276 self.think = func_null;
278 self.team = self.team_saved;
281 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
282 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.
283 If "delay" is set, the trigger waits some time after activating before firing.
284 "wait" : Seconds between triggerings. (.2 default)
285 If notouch is set, the trigger is only fired by other entities, not by touching.
286 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
292 set "message" to text string
294 void spawnfunc_trigger_multiple()
296 self.reset = multi_reset;
297 if (self.sounds == 1)
299 precache_sound ("misc/secret.wav");
300 self.noise = "misc/secret.wav";
302 else if (self.sounds == 2)
304 precache_sound ("misc/talk.wav");
305 self.noise = "misc/talk.wav";
307 else if (self.sounds == 3)
309 precache_sound ("misc/trigger1.wav");
310 self.noise = "misc/trigger1.wav";
315 else if(self.wait < -1)
317 self.use = multi_use;
321 self.team_saved = self.team;
325 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
326 objerror ("health and notouch don't make sense\n");
327 self.max_health = self.health;
328 self.event_damage = multi_eventdamage;
329 self.takedamage = DAMAGE_YES;
330 self.solid = SOLID_BBOX;
331 setorigin (self, self.origin); // make sure it links into the world
335 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
337 self.touch = multi_touch;
338 setorigin (self, self.origin); // make sure it links into the world
344 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
345 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
346 "targetname". If "health" is set, the trigger must be killed to activate.
347 If notouch is set, the trigger is only fired by other entities, not by touching.
348 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
349 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.
355 set "message" to text string
357 void spawnfunc_trigger_once()
360 spawnfunc_trigger_multiple();
363 //=============================================================================
365 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
366 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
368 void spawnfunc_trigger_relay()
370 self.use = SUB_UseTargets;
371 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
376 self.think = SUB_UseTargets;
377 self.nextthink = self.wait;
382 self.think = func_null;
386 void spawnfunc_trigger_delay()
391 self.use = delay_use;
392 self.reset = delay_reset;
395 //=============================================================================
406 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
407 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
409 self.enemy = activator;
414 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
416 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
418 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
424 self.count = self.cnt;
428 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
429 Acts as an intermediary for an action that takes multiple inputs.
431 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
433 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
435 void spawnfunc_trigger_counter()
440 self.cnt = self.count;
442 self.use = counter_use;
443 self.reset = counter_reset;
446 void trigger_hurt_use()
448 if(IS_PLAYER(activator))
449 self.enemy = activator;
451 self.enemy = world; // let's just destroy it, if taking over is too much work
454 void trigger_hurt_touch()
456 if (self.active != ACTIVE_ACTIVE)
460 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
463 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
464 if (other.iscreature)
466 if (other.takedamage)
467 if (other.triggerhurttime < time)
470 other.triggerhurttime = time + 1;
477 self.enemy = world; // I still hate you all
480 Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
483 else if(other.damagedbytriggers)
488 Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
495 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
496 Any object touching this will be hurt
497 set dmg to damage amount
500 void spawnfunc_trigger_hurt()
503 self.active = ACTIVE_ACTIVE;
504 self.touch = trigger_hurt_touch;
505 self.use = trigger_hurt_use;
506 self.enemy = world; // I hate you all
509 if (self.message == "")
510 self.message = "was in the wrong place";
511 if (self.message2 == "")
512 self.message2 = "was thrown into a world of hurt by";
513 // self.message = "someone like %s always gets wrongplaced";
515 if(!trigger_hurt_first)
516 trigger_hurt_first = self;
517 if(trigger_hurt_last)
518 trigger_hurt_last.trigger_hurt_next = self;
519 trigger_hurt_last = self;
522 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
526 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
527 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
533 //////////////////////////////////////////////////////////////
537 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
539 //////////////////////////////////////////////////////////////
541 void trigger_heal_touch()
543 if (self.active != ACTIVE_ACTIVE)
546 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
547 if (other.iscreature)
549 if (other.takedamage)
551 if (other.triggerhealtime < time)
554 other.triggerhealtime = time + 1;
556 if (other.health < self.max_health)
558 other.health = min(other.health + self.health, self.max_health);
559 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
560 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
566 void spawnfunc_trigger_heal()
568 self.active = ACTIVE_ACTIVE;
571 self.touch = trigger_heal_touch;
574 if (!self.max_health)
575 self.max_health = 200; //Max health topoff for field
577 self.noise = "misc/mediumhealth.wav";
578 precache_sound(self.noise);
582 //////////////////////////////////////////////////////////////
588 //////////////////////////////////////////////////////////////
590 void trigger_gravity_remove(entity own)
592 if(own.trigger_gravity_check.owner == own)
594 UpdateCSQCProjectile(own);
595 own.gravity = own.trigger_gravity_check.gravity;
596 remove(own.trigger_gravity_check);
599 backtrace("Removing a trigger_gravity_check with no valid owner");
600 own.trigger_gravity_check = world;
602 void trigger_gravity_check_think()
604 // This spawns when a player enters the gravity zone and checks if he left.
605 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
606 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
609 if(self.owner.trigger_gravity_check == self)
610 trigger_gravity_remove(self.owner);
618 self.nextthink = time;
622 void trigger_gravity_use()
624 self.state = !self.state;
627 void trigger_gravity_touch()
631 if(self.state != true)
638 if (!(self.spawnflags & 1))
640 if(other.trigger_gravity_check)
642 if(self == other.trigger_gravity_check.enemy)
645 other.trigger_gravity_check.count = 2; // gravity one more frame...
650 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
651 trigger_gravity_remove(other);
655 other.trigger_gravity_check = spawn();
656 other.trigger_gravity_check.enemy = self;
657 other.trigger_gravity_check.owner = other;
658 other.trigger_gravity_check.gravity = other.gravity;
659 other.trigger_gravity_check.think = trigger_gravity_check_think;
660 other.trigger_gravity_check.nextthink = time;
661 other.trigger_gravity_check.count = 2;
666 if (other.gravity != g)
670 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
671 UpdateCSQCProjectile(self.owner);
675 void spawnfunc_trigger_gravity()
677 if(self.gravity == 1)
681 self.touch = trigger_gravity_touch;
683 precache_sound(self.noise);
688 self.use = trigger_gravity_use;
689 if(self.spawnflags & 2)
694 //=============================================================================
696 // TODO add a way to do looped sounds with sound(); then complete this entity
697 void target_speaker_use_activator()
699 if (!IS_REAL_CLIENT(activator))
702 if(substring(self.noise, 0, 1) == "*")
705 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
706 if(GetPlayerSoundSampleField_notFound)
707 snd = "misc/null.wav";
708 else if(activator.sample == "")
709 snd = "misc/null.wav";
712 tokenize_console(activator.sample);
716 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
718 snd = strcat(argv(0), ".wav"); // randomization
723 msg_entity = activator;
724 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
726 void target_speaker_use_on()
729 if(substring(self.noise, 0, 1) == "*")
732 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
733 if(GetPlayerSoundSampleField_notFound)
734 snd = "misc/null.wav";
735 else if(activator.sample == "")
736 snd = "misc/null.wav";
739 tokenize_console(activator.sample);
743 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
745 snd = strcat(argv(0), ".wav"); // randomization
750 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
751 if(self.spawnflags & 3)
752 self.use = target_speaker_use_off;
754 void target_speaker_use_off()
756 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
757 self.use = target_speaker_use_on;
759 void target_speaker_reset()
761 if(self.spawnflags & 1) // LOOPED_ON
763 if(self.use == target_speaker_use_on)
764 target_speaker_use_on();
766 else if(self.spawnflags & 2)
768 if(self.use == target_speaker_use_off)
769 target_speaker_use_off();
773 void spawnfunc_target_speaker()
775 // TODO: "*" prefix to sound file name
776 // TODO: wait and random (just, HOW? random is not a field)
778 precache_sound (self.noise);
780 if(!self.atten && !(self.spawnflags & 4))
783 self.atten = ATTEN_NORM;
785 self.atten = ATTEN_STATIC;
787 else if(self.atten < 0)
795 if(self.spawnflags & 8) // ACTIVATOR
796 self.use = target_speaker_use_activator;
797 else if(self.spawnflags & 1) // LOOPED_ON
799 target_speaker_use_on();
800 self.reset = target_speaker_reset;
802 else if(self.spawnflags & 2) // LOOPED_OFF
804 self.use = target_speaker_use_on;
805 self.reset = target_speaker_reset;
808 self.use = target_speaker_use_on;
810 else if(self.spawnflags & 1) // LOOPED_ON
812 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
815 else if(self.spawnflags & 2) // LOOPED_OFF
817 objerror("This sound entity can never be activated");
821 // Quake/Nexuiz fallback
822 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
828 void spawnfunc_func_stardust() {
829 self.effects = EF_STARDUST;
832 float pointparticles_SendEntity(entity to, float fl)
834 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
836 // optional features to save space
838 if(self.spawnflags & 2)
839 fl |= 0x10; // absolute count on toggle-on
840 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
841 fl |= 0x20; // 4 bytes - saves CPU
842 if(self.waterlevel || self.count != 1)
843 fl |= 0x40; // 4 bytes - obscure features almost never used
844 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
845 fl |= 0x80; // 14 bytes - saves lots of space
847 WriteByte(MSG_ENTITY, fl);
851 WriteCoord(MSG_ENTITY, self.impulse);
853 WriteCoord(MSG_ENTITY, 0); // off
857 WriteCoord(MSG_ENTITY, self.origin.x);
858 WriteCoord(MSG_ENTITY, self.origin.y);
859 WriteCoord(MSG_ENTITY, self.origin.z);
863 if(self.model != "null")
865 WriteShort(MSG_ENTITY, self.modelindex);
868 WriteCoord(MSG_ENTITY, self.mins.x);
869 WriteCoord(MSG_ENTITY, self.mins.y);
870 WriteCoord(MSG_ENTITY, self.mins.z);
871 WriteCoord(MSG_ENTITY, self.maxs.x);
872 WriteCoord(MSG_ENTITY, self.maxs.y);
873 WriteCoord(MSG_ENTITY, self.maxs.z);
878 WriteShort(MSG_ENTITY, 0);
881 WriteCoord(MSG_ENTITY, self.maxs.x);
882 WriteCoord(MSG_ENTITY, self.maxs.y);
883 WriteCoord(MSG_ENTITY, self.maxs.z);
886 WriteShort(MSG_ENTITY, self.cnt);
889 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
890 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
894 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
895 WriteByte(MSG_ENTITY, self.count * 16.0);
897 WriteString(MSG_ENTITY, self.noise);
900 WriteByte(MSG_ENTITY, floor(self.atten * 64));
901 WriteByte(MSG_ENTITY, floor(self.volume * 255));
903 WriteString(MSG_ENTITY, self.bgmscript);
904 if(self.bgmscript != "")
906 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
907 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
908 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
909 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
915 void pointparticles_use()
917 self.state = !self.state;
921 void pointparticles_think()
923 if(self.origin != self.oldorigin)
926 self.oldorigin = self.origin;
928 self.nextthink = time;
931 void pointparticles_reset()
933 if(self.spawnflags & 1)
939 void spawnfunc_func_pointparticles()
942 setmodel(self, self.model);
944 precache_sound (self.noise);
946 if(!self.bgmscriptsustain)
947 self.bgmscriptsustain = 1;
948 else if(self.bgmscriptsustain < 0)
949 self.bgmscriptsustain = 0;
952 self.atten = ATTEN_NORM;
953 else if(self.atten < 0)
964 setorigin(self, self.origin + self.mins);
965 setsize(self, '0 0 0', self.maxs - self.mins);
968 self.cnt = particleeffectnum(self.mdl);
970 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
974 self.use = pointparticles_use;
975 self.reset = pointparticles_reset;
980 self.think = pointparticles_think;
981 self.nextthink = time;
984 void spawnfunc_func_sparks()
986 // self.cnt is the amount of sparks that one burst will spawn
988 self.cnt = 25.0; // nice default value
991 // self.wait is the probability that a sparkthink will spawn a spark shower
992 // range: 0 - 1, but 0 makes little sense, so...
993 if(self.wait < 0.05) {
994 self.wait = 0.25; // nice default value
997 self.count = self.cnt;
1000 self.velocity = '0 0 -1';
1001 self.mdl = "TE_SPARK";
1002 self.impulse = 10 * self.wait; // by default 2.5/sec
1004 self.cnt = 0; // use mdl
1006 spawnfunc_func_pointparticles();
1009 float rainsnow_SendEntity(entity to, float sf)
1011 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1012 WriteByte(MSG_ENTITY, self.state);
1013 WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
1014 WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
1015 WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
1016 WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
1017 WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
1018 WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
1019 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1020 WriteShort(MSG_ENTITY, self.count);
1021 WriteByte(MSG_ENTITY, self.cnt);
1025 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1026 This is an invisible area like a trigger, which rain falls inside of.
1030 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1032 sets color of rain (default 12 - white)
1034 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1036 void spawnfunc_func_rain()
1038 self.dest = self.velocity;
1039 self.velocity = '0 0 0';
1041 self.dest = '0 0 -700';
1042 self.angles = '0 0 0';
1043 self.movetype = MOVETYPE_NONE;
1044 self.solid = SOLID_NOT;
1045 SetBrushEntityModel();
1050 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1053 if(self.count > 65535)
1056 self.state = 1; // 1 is rain, 0 is snow
1059 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1063 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1064 This is an invisible area like a trigger, which snow falls inside of.
1068 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1070 sets color of rain (default 12 - white)
1072 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1074 void spawnfunc_func_snow()
1076 self.dest = self.velocity;
1077 self.velocity = '0 0 0';
1079 self.dest = '0 0 -300';
1080 self.angles = '0 0 0';
1081 self.movetype = MOVETYPE_NONE;
1082 self.solid = SOLID_NOT;
1083 SetBrushEntityModel();
1088 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1091 if(self.count > 65535)
1094 self.state = 0; // 1 is rain, 0 is snow
1097 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1100 void misc_laser_aim()
1105 if(self.spawnflags & 2)
1107 if(self.enemy.origin != self.mangle)
1109 self.mangle = self.enemy.origin;
1110 self.SendFlags |= 2;
1115 a = vectoangles(self.enemy.origin - self.origin);
1117 if(a != self.mangle)
1120 self.SendFlags |= 2;
1126 if(self.angles != self.mangle)
1128 self.mangle = self.angles;
1129 self.SendFlags |= 2;
1132 if(self.origin != self.oldorigin)
1134 self.SendFlags |= 1;
1135 self.oldorigin = self.origin;
1139 void misc_laser_init()
1141 if(self.target != "")
1142 self.enemy = find(world, targetname, self.target);
1145 void misc_laser_think()
1152 self.nextthink = time;
1161 o = self.enemy.origin;
1162 if (!(self.spawnflags & 2))
1163 o = self.origin + normalize(o - self.origin) * 32768;
1167 makevectors(self.mangle);
1168 o = self.origin + v_forward * 32768;
1171 if(self.dmg || self.enemy.target != "")
1173 traceline(self.origin, o, MOVE_NORMAL, self);
1176 hitloc = trace_endpos;
1178 if(self.enemy.target != "") // DETECTOR laser
1180 if(trace_ent.iscreature)
1182 self.pusher = hitent;
1189 activator = self.pusher;
1202 activator = self.pusher;
1212 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1214 if(hitent.takedamage)
1215 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1219 float laser_SendEntity(entity to, float fl)
1221 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1222 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1223 if(self.spawnflags & 2)
1227 if(self.scale != 1 || self.modelscale != 1)
1229 if(self.spawnflags & 4)
1231 WriteByte(MSG_ENTITY, fl);
1234 WriteCoord(MSG_ENTITY, self.origin.x);
1235 WriteCoord(MSG_ENTITY, self.origin.y);
1236 WriteCoord(MSG_ENTITY, self.origin.z);
1240 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
1241 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
1242 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
1244 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1247 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1248 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1250 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1251 WriteShort(MSG_ENTITY, self.cnt + 1);
1257 WriteCoord(MSG_ENTITY, self.enemy.origin.x);
1258 WriteCoord(MSG_ENTITY, self.enemy.origin.y);
1259 WriteCoord(MSG_ENTITY, self.enemy.origin.z);
1263 WriteAngle(MSG_ENTITY, self.mangle.x);
1264 WriteAngle(MSG_ENTITY, self.mangle.y);
1268 WriteByte(MSG_ENTITY, self.state);
1272 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1273 Any object touching the beam will be hurt
1276 spawnfunc_target_position where the laser ends
1278 name of beam end effect to use
1280 color of the beam (default: red)
1282 damage per second (-1 for a laser that kills immediately)
1286 self.state = !self.state;
1287 self.SendFlags |= 4;
1293 if(self.spawnflags & 1)
1299 void spawnfunc_misc_laser()
1303 if(self.mdl == "none")
1307 self.cnt = particleeffectnum(self.mdl);
1310 self.cnt = particleeffectnum("laser_deadly");
1316 self.cnt = particleeffectnum("laser_deadly");
1323 if(self.colormod == '0 0 0')
1325 self.colormod = '1 0 0';
1326 if(self.message == "")
1327 self.message = "saw the light";
1328 if (self.message2 == "")
1329 self.message2 = "was pushed into a laser by";
1332 if(!self.modelscale)
1333 self.modelscale = 1;
1334 else if(self.modelscale < 0)
1335 self.modelscale = 0;
1336 self.think = misc_laser_think;
1337 self.nextthink = time;
1338 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1340 self.mangle = self.angles;
1342 Net_LinkEntity(self, false, 0, laser_SendEntity);
1346 self.reset = laser_reset;
1348 self.use = laser_use;
1354 // tZorks trigger impulse / gravity
1356 // targeted (directional) mode
1357 void trigger_impulse_touch1()
1360 float pushdeltatime;
1363 if (self.active != ACTIVE_ACTIVE)
1366 if (!isPushable(other))
1371 targ = find(world, targetname, self.target);
1374 objerror("trigger_force without a (valid) .target!\n");
1379 str = min(self.radius, vlen(self.origin - other.origin));
1381 if(self.falloff == 1)
1382 str = (str / self.radius) * self.strength;
1383 else if(self.falloff == 2)
1384 str = (1 - (str / self.radius)) * self.strength;
1386 str = self.strength;
1388 pushdeltatime = time - other.lastpushtime;
1389 if (pushdeltatime > 0.15) pushdeltatime = 0;
1390 other.lastpushtime = time;
1391 if(!pushdeltatime) return;
1393 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1394 other.flags &= ~FL_ONGROUND;
1395 UpdateCSQCProjectile(other);
1398 // Directionless (accelerator/decelerator) mode
1399 void trigger_impulse_touch2()
1401 float pushdeltatime;
1403 if (self.active != ACTIVE_ACTIVE)
1406 if (!isPushable(other))
1411 pushdeltatime = time - other.lastpushtime;
1412 if (pushdeltatime > 0.15) pushdeltatime = 0;
1413 other.lastpushtime = time;
1414 if(!pushdeltatime) return;
1416 // div0: ticrate independent, 1 = identity (not 20)
1417 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1418 UpdateCSQCProjectile(other);
1421 // Spherical (gravity/repulsor) mode
1422 void trigger_impulse_touch3()
1424 float pushdeltatime;
1427 if (self.active != ACTIVE_ACTIVE)
1430 if (!isPushable(other))
1435 pushdeltatime = time - other.lastpushtime;
1436 if (pushdeltatime > 0.15) pushdeltatime = 0;
1437 other.lastpushtime = time;
1438 if(!pushdeltatime) return;
1440 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1442 str = min(self.radius, vlen(self.origin - other.origin));
1444 if(self.falloff == 1)
1445 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1446 else if(self.falloff == 2)
1447 str = (str / self.radius) * self.strength; // 0 in the inside
1449 str = self.strength;
1451 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1452 UpdateCSQCProjectile(other);
1455 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1456 -------- KEYS --------
1457 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1458 If not, this trigger acts like a damper/accelerator field.
1460 strength : This is how mutch force to add in the direction of .target each second
1461 when .target is set. If not, this is hoe mutch to slow down/accelerate
1462 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1464 radius : If set, act as a spherical device rather then a liniar one.
1466 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1468 -------- NOTES --------
1469 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1470 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1473 void spawnfunc_trigger_impulse()
1475 self.active = ACTIVE_ACTIVE;
1480 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1481 setorigin(self, self.origin);
1482 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1483 self.touch = trigger_impulse_touch3;
1489 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1490 self.touch = trigger_impulse_touch1;
1494 if(!self.strength) self.strength = 0.9;
1495 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1496 self.touch = trigger_impulse_touch2;
1501 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1502 "Flip-flop" trigger gate... lets only every second trigger event through
1506 self.state = !self.state;
1511 void spawnfunc_trigger_flipflop()
1513 if(self.spawnflags & 1)
1515 self.use = flipflop_use;
1516 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1519 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1520 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1524 self.nextthink = time + self.wait;
1525 self.enemy = activator;
1531 void monoflop_fixed_use()
1535 self.nextthink = time + self.wait;
1537 self.enemy = activator;
1541 void monoflop_think()
1544 activator = self.enemy;
1548 void monoflop_reset()
1554 void spawnfunc_trigger_monoflop()
1558 if(self.spawnflags & 1)
1559 self.use = monoflop_fixed_use;
1561 self.use = monoflop_use;
1562 self.think = monoflop_think;
1564 self.reset = monoflop_reset;
1567 void multivibrator_send()
1572 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1574 newstate = (time < cyclestart + self.wait);
1577 if(self.state != newstate)
1579 self.state = newstate;
1582 self.nextthink = cyclestart + self.wait + 0.01;
1584 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1587 void multivibrator_toggle()
1589 if(self.nextthink == 0)
1591 multivibrator_send();
1604 void multivibrator_reset()
1606 if(!(self.spawnflags & 1))
1607 self.nextthink = 0; // wait for a trigger event
1609 self.nextthink = max(1, time);
1612 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1613 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1614 -------- KEYS --------
1615 target: trigger all entities with this targetname when it goes off
1616 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1617 phase: offset of the timing
1618 wait: "on" cycle time (default: 1)
1619 respawntime: "off" cycle time (default: same as wait)
1620 -------- SPAWNFLAGS --------
1621 START_ON: assume it is already turned on (when targeted)
1623 void spawnfunc_trigger_multivibrator()
1627 if(!self.respawntime)
1628 self.respawntime = self.wait;
1631 self.use = multivibrator_toggle;
1632 self.think = multivibrator_send;
1633 self.nextthink = max(1, time);
1636 multivibrator_reset();
1645 if(self.killtarget != "")
1646 src = find(world, targetname, self.killtarget);
1647 if(self.target != "")
1648 dst = find(world, targetname, self.target);
1652 objerror("follow: could not find target/killtarget");
1658 // already done :P entity must stay
1662 else if(!src || !dst)
1664 objerror("follow: could not find target/killtarget");
1667 else if(self.spawnflags & 1)
1670 if(self.spawnflags & 2)
1672 setattachment(dst, src, self.message);
1676 attach_sameorigin(dst, src, self.message);
1679 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1684 if(self.spawnflags & 2)
1686 dst.movetype = MOVETYPE_FOLLOW;
1688 // dst.punchangle = '0 0 0'; // keep unchanged
1689 dst.view_ofs = dst.origin;
1690 dst.v_angle = dst.angles;
1694 follow_sameorigin(dst, src);
1701 void spawnfunc_misc_follow()
1703 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1708 void gamestart_use() {
1714 void spawnfunc_trigger_gamestart() {
1715 self.use = gamestart_use;
1716 self.reset2 = spawnfunc_trigger_gamestart;
1720 self.think = self.use;
1721 self.nextthink = game_starttime + self.wait;
1724 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1730 void target_voicescript_clear(entity pl)
1732 pl.voicescript = world;
1735 void target_voicescript_use()
1737 if(activator.voicescript != self)
1739 activator.voicescript = self;
1740 activator.voicescript_index = 0;
1741 activator.voicescript_nextthink = time + self.delay;
1745 void target_voicescript_next(entity pl)
1750 vs = pl.voicescript;
1753 if(vs.message == "")
1760 if(time >= pl.voicescript_voiceend)
1762 if(time >= pl.voicescript_nextthink)
1764 // get the next voice...
1765 n = tokenize_console(vs.message);
1767 if(pl.voicescript_index < vs.cnt)
1768 i = pl.voicescript_index * 2;
1769 else if(n > vs.cnt * 2)
1770 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1776 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1777 dt = stof(argv(i + 1));
1780 pl.voicescript_voiceend = time + dt;
1781 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1785 pl.voicescript_voiceend = time - dt;
1786 pl.voicescript_nextthink = pl.voicescript_voiceend;
1789 pl.voicescript_index += 1;
1793 pl.voicescript = world; // stop trying then
1799 void spawnfunc_target_voicescript()
1801 // netname: directory of the sound files
1802 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1803 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1804 // Here, a - in front of the duration means that no delay is to be
1805 // added after this message
1806 // wait: average time between messages
1807 // delay: initial delay before the first message
1810 self.use = target_voicescript_use;
1812 n = tokenize_console(self.message);
1814 for(i = 0; i+1 < n; i += 2)
1821 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1827 void trigger_relay_teamcheck_use()
1831 if(self.spawnflags & 2)
1833 if(activator.team != self.team)
1838 if(activator.team == self.team)
1844 if(self.spawnflags & 1)
1849 void trigger_relay_teamcheck_reset()
1851 self.team = self.team_saved;
1854 void spawnfunc_trigger_relay_teamcheck()
1856 self.team_saved = self.team;
1857 self.use = trigger_relay_teamcheck_use;
1858 self.reset = trigger_relay_teamcheck_reset;
1863 void trigger_disablerelay_use()
1870 for(e = world; (e = find(e, targetname, self.target)); )
1872 if(e.use == SUB_UseTargets)
1874 e.use = SUB_DontUseTargets;
1877 else if(e.use == SUB_DontUseTargets)
1879 e.use = SUB_UseTargets;
1885 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1888 void spawnfunc_trigger_disablerelay()
1890 self.use = trigger_disablerelay_use;
1893 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1895 float domatch, dotrigger, matchstart, l;
1900 magicear_matched = false;
1902 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1903 domatch = ((ear.spawnflags & 32) || dotrigger);
1910 // we are in TUBA mode!
1911 if (!(ear.spawnflags & 256))
1914 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
1917 magicear_matched = true;
1924 savemessage = self.message;
1925 self.message = string_null;
1927 self.message = savemessage;
1931 if(ear.netname != "")
1937 if(ear.spawnflags & 256) // ENOTUBA
1942 if(ear.spawnflags & 4)
1948 if(ear.spawnflags & 1)
1951 if(ear.spawnflags & 2)
1954 if(ear.spawnflags & 8)
1959 l = strlen(ear.message);
1961 if(ear.spawnflags & 128)
1964 msg = strdecolorize(msgin);
1966 if(substring(ear.message, 0, 1) == "*")
1968 if(substring(ear.message, -1, 1) == "*")
1971 // as we need multi-replacement here...
1972 s = substring(ear.message, 1, -2);
1974 if(strstrofs(msg, s, 0) >= 0)
1975 matchstart = -2; // we use strreplace on s
1980 s = substring(ear.message, 1, -1);
1982 if(substring(msg, -l, l) == s)
1983 matchstart = strlen(msg) - l;
1988 if(substring(ear.message, -1, 1) == "*")
1991 s = substring(ear.message, 0, -2);
1993 if(substring(msg, 0, l) == s)
2000 if(msg == ear.message)
2005 if(matchstart == -1) // no match
2008 magicear_matched = true;
2015 savemessage = self.message;
2016 self.message = string_null;
2018 self.message = savemessage;
2022 if(ear.spawnflags & 16)
2026 else if(ear.netname != "")
2029 return strreplace(s, ear.netname, msg);
2032 substring(msg, 0, matchstart),
2034 substring(msg, matchstart + l, -1)
2041 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2045 for(ear = magicears; ear; ear = ear.enemy)
2047 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2048 if(!(ear.spawnflags & 64))
2049 if(magicear_matched)
2056 void spawnfunc_trigger_magicear()
2058 self.enemy = magicears;
2061 // actually handled in "say" processing
2064 // 2 = ignore teamsay
2066 // 8 = ignore tell to unknown player
2067 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2068 // 32 = perform the replacement even if outside the radius or dead
2069 // 64 = continue replacing/triggering even if this one matched
2070 // 128 = don't decolorize message before matching
2071 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2072 // 512 = tuba notes must be exact right pitch, no transposing
2082 // if set, replacement for the matched text
2084 // "hearing distance"
2088 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2090 self.movedir_x -= 1; // map to tuba instrument numbers
2093 void relay_activators_use()
2099 for(trg = world; (trg = find(trg, targetname, os.target)); )
2103 trg.setactive(os.cnt);
2106 //bprint("Not using setactive\n");
2107 if(os.cnt == ACTIVE_TOGGLE)
2108 if(trg.active == ACTIVE_ACTIVE)
2109 trg.active = ACTIVE_NOT;
2111 trg.active = ACTIVE_ACTIVE;
2113 trg.active = os.cnt;
2119 void spawnfunc_relay_activate()
2121 self.cnt = ACTIVE_ACTIVE;
2122 self.use = relay_activators_use;
2125 void spawnfunc_relay_deactivate()
2127 self.cnt = ACTIVE_NOT;
2128 self.use = relay_activators_use;
2131 void spawnfunc_relay_activatetoggle()
2133 self.cnt = ACTIVE_TOGGLE;
2134 self.use = relay_activators_use;
2137 void spawnfunc_target_changelevel_use()
2139 if(self.gametype != "")
2140 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2142 if (self.chmap == "")
2143 localcmd("endmatch\n");
2145 localcmd(strcat("changelevel ", self.chmap, "\n"));
2148 void spawnfunc_target_changelevel()
2150 self.use = spawnfunc_target_changelevel_use;