4 #include "../dpdefs/progsdefs.qh"
5 #include "../dpdefs/dpextensions.qh"
6 #include "../warpzonelib/util_server.qh"
7 #include "../common/constants.qh"
8 #include "../common/util.qh"
9 #include "../common/monsters/monsters.qh"
10 #include "../common/weapons/weapons.qh"
11 #include "weapons/csqcprojectile.qh"
12 #include "autocvars.qh"
13 #include "constants.qh"
15 #include "../common/notifications.qh"
16 #include "../common/deathtypes.qh"
17 #include "../common/mapinfo.qh"
20 void SUB_DontUseTargets()
25 void() SUB_UseTargets;
29 activator = self.enemy;
35 ==============================
38 the global "activator" should be set to the entity that initiated the firing.
40 If self.delay is set, a DelayedUse entity will be created that will actually
41 do the SUB_UseTargets after that many seconds have passed.
43 Centerprints any self.message to the activator.
45 Removes all entities with a targetname that match self.killtarget,
46 and removes them, so some events can remove other triggers.
48 Search for (string)targetname in all entities that
49 match (string)self.target and call their .use function
51 ==============================
55 entity t, stemp, otemp, act;
64 // create a temp object to fire at a later time
66 t.classname = "DelayedUse";
67 t.nextthink = time + self.delay;
70 t.message = self.message;
71 t.killtarget = self.killtarget;
72 t.target = self.target;
73 t.target2 = self.target2;
74 t.target3 = self.target3;
75 t.target4 = self.target4;
84 if(IS_PLAYER(activator) && self.message != "")
85 if(IS_REAL_CLIENT(activator))
87 centerprint(activator, self.message);
89 play2(activator, "misc/talk.wav");
93 // kill the killtagets
98 for(t = world; (t = find(t, targetname, s)); )
109 if(stemp.target_random)
110 RandomSelection_Init();
112 for(i = 0; i < 4; ++i)
117 case 0: s = stemp.target; break;
118 case 1: s = stemp.target2; break;
119 case 2: s = stemp.target3; break;
120 case 3: s = stemp.target4; break;
124 for(t = world; (t = find(t, targetname, s)); )
127 if(stemp.target_random)
129 RandomSelection_Add(t, 0, string_null, 1, 0);
142 if(stemp.target_random && RandomSelection_chosen_ent)
144 self = RandomSelection_chosen_ent;
156 //=============================================================================
158 const float SPAWNFLAG_NOMESSAGE = 1;
159 const float SPAWNFLAG_NOTOUCH = 1;
161 // the wait time has passed, so set back up for another activation
166 self.health = self.max_health;
167 self.takedamage = DAMAGE_YES;
168 self.solid = SOLID_BBOX;
173 // the trigger was just touched/killed/used
174 // self.enemy should be set to the activator so it can be held through a delay
175 // so wait for the delay time before firing
178 if (self.nextthink > time)
180 return; // allready been triggered
183 if (self.classname == "trigger_secret")
185 if (!IS_PLAYER(self.enemy))
187 found_secrets = found_secrets + 1;
188 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
192 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
194 // don't trigger again until reset
195 self.takedamage = DAMAGE_NO;
197 activator = self.enemy;
198 other = self.goalentity;
203 self.think = multi_wait;
204 self.nextthink = time + self.wait;
206 else if (self.wait == 0)
208 multi_wait(); // waiting finished
211 { // we can't just remove (self) here, because this is a touch function
212 // called wheil C code is looping through area links...
213 self.touch = func_null;
219 self.goalentity = other;
220 self.enemy = activator;
226 if(!(self.spawnflags & 2))
227 if(!other.iscreature)
231 if(((self.spawnflags & 4) == 0) == (self.team != other.team))
234 // if the trigger has an angles field, check player's facing direction
235 if (self.movedir != '0 0 0')
237 makevectors (other.angles);
238 if (v_forward * self.movedir < 0)
239 return; // not facing the right way
245 self.goalentity = other;
249 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
251 if (!self.takedamage)
253 if(self.spawnflags & DOOR_NOSPLASH)
254 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
256 self.health = self.health - damage;
257 if (self.health <= 0)
259 self.enemy = attacker;
260 self.goalentity = inflictor;
267 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
268 self.touch = multi_touch;
271 self.health = self.max_health;
272 self.takedamage = DAMAGE_YES;
273 self.solid = SOLID_BBOX;
275 self.think = func_null;
277 self.team = self.team_saved;
280 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
281 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.
282 If "delay" is set, the trigger waits some time after activating before firing.
283 "wait" : Seconds between triggerings. (.2 default)
284 If notouch is set, the trigger is only fired by other entities, not by touching.
285 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
291 set "message" to text string
293 void spawnfunc_trigger_multiple()
295 self.reset = multi_reset;
296 if (self.sounds == 1)
298 precache_sound ("misc/secret.wav");
299 self.noise = "misc/secret.wav";
301 else if (self.sounds == 2)
303 precache_sound ("misc/talk.wav");
304 self.noise = "misc/talk.wav";
306 else if (self.sounds == 3)
308 precache_sound ("misc/trigger1.wav");
309 self.noise = "misc/trigger1.wav";
314 else if(self.wait < -1)
316 self.use = multi_use;
320 self.team_saved = self.team;
324 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
325 objerror ("health and notouch don't make sense\n");
326 self.max_health = self.health;
327 self.event_damage = multi_eventdamage;
328 self.takedamage = DAMAGE_YES;
329 self.solid = SOLID_BBOX;
330 setorigin (self, self.origin); // make sure it links into the world
334 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
336 self.touch = multi_touch;
337 setorigin (self, self.origin); // make sure it links into the world
343 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
344 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
345 "targetname". If "health" is set, the trigger must be killed to activate.
346 If notouch is set, the trigger is only fired by other entities, not by touching.
347 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
348 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.
354 set "message" to text string
356 void spawnfunc_trigger_once()
359 spawnfunc_trigger_multiple();
362 //=============================================================================
364 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
365 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
367 void spawnfunc_trigger_relay()
369 self.use = SUB_UseTargets;
370 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
375 self.think = SUB_UseTargets;
376 self.nextthink = self.wait;
381 self.think = func_null;
385 void spawnfunc_trigger_delay()
390 self.use = delay_use;
391 self.reset = delay_reset;
394 //=============================================================================
405 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
406 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
408 self.enemy = activator;
413 if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
415 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
417 Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
423 self.count = self.cnt;
427 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
428 Acts as an intermediary for an action that takes multiple inputs.
430 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
432 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
434 void spawnfunc_trigger_counter()
439 self.cnt = self.count;
441 self.use = counter_use;
442 self.reset = counter_reset;
445 void trigger_hurt_use()
447 if(IS_PLAYER(activator))
448 self.enemy = activator;
450 self.enemy = world; // let's just destroy it, if taking over is too much work
453 .float triggerhurttime;
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 .entity trigger_hurt_next;
501 entity trigger_hurt_last;
502 entity trigger_hurt_first;
503 void spawnfunc_trigger_hurt()
506 self.active = ACTIVE_ACTIVE;
507 self.touch = trigger_hurt_touch;
508 self.use = trigger_hurt_use;
509 self.enemy = world; // I hate you all
512 if (self.message == "")
513 self.message = "was in the wrong place";
514 if (self.message2 == "")
515 self.message2 = "was thrown into a world of hurt by";
516 // self.message = "someone like %s always gets wrongplaced";
518 if(!trigger_hurt_first)
519 trigger_hurt_first = self;
520 if(trigger_hurt_last)
521 trigger_hurt_last.trigger_hurt_next = self;
522 trigger_hurt_last = self;
525 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
529 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
530 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
536 //////////////////////////////////////////////////////////////
540 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
542 //////////////////////////////////////////////////////////////
544 .float triggerhealtime;
545 void trigger_heal_touch()
547 if (self.active != ACTIVE_ACTIVE)
550 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
551 if (other.iscreature)
553 if (other.takedamage)
555 if (other.triggerhealtime < time)
558 other.triggerhealtime = time + 1;
560 if (other.health < self.max_health)
562 other.health = min(other.health + self.health, self.max_health);
563 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
564 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
570 void spawnfunc_trigger_heal()
572 self.active = ACTIVE_ACTIVE;
575 self.touch = trigger_heal_touch;
578 if (!self.max_health)
579 self.max_health = 200; //Max health topoff for field
581 self.noise = "misc/mediumhealth.wav";
582 precache_sound(self.noise);
586 //////////////////////////////////////////////////////////////
592 //////////////////////////////////////////////////////////////
594 .entity trigger_gravity_check;
595 void trigger_gravity_remove(entity own)
597 if(own.trigger_gravity_check.owner == own)
599 UpdateCSQCProjectile(own);
600 own.gravity = own.trigger_gravity_check.gravity;
601 remove(own.trigger_gravity_check);
604 backtrace("Removing a trigger_gravity_check with no valid owner");
605 own.trigger_gravity_check = world;
607 void trigger_gravity_check_think()
609 // This spawns when a player enters the gravity zone and checks if he left.
610 // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
611 // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
614 if(self.owner.trigger_gravity_check == self)
615 trigger_gravity_remove(self.owner);
623 self.nextthink = time;
627 void trigger_gravity_use()
629 self.state = !self.state;
632 void trigger_gravity_touch()
636 if(self.state != true)
643 if (!(self.spawnflags & 1))
645 if(other.trigger_gravity_check)
647 if(self == other.trigger_gravity_check.enemy)
650 other.trigger_gravity_check.count = 2; // gravity one more frame...
655 if(self.cnt > other.trigger_gravity_check.enemy.cnt)
656 trigger_gravity_remove(other);
660 other.trigger_gravity_check = spawn();
661 other.trigger_gravity_check.enemy = self;
662 other.trigger_gravity_check.owner = other;
663 other.trigger_gravity_check.gravity = other.gravity;
664 other.trigger_gravity_check.think = trigger_gravity_check_think;
665 other.trigger_gravity_check.nextthink = time;
666 other.trigger_gravity_check.count = 2;
671 if (other.gravity != g)
675 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
676 UpdateCSQCProjectile(self.owner);
680 void spawnfunc_trigger_gravity()
682 if(self.gravity == 1)
686 self.touch = trigger_gravity_touch;
688 precache_sound(self.noise);
693 self.use = trigger_gravity_use;
694 if(self.spawnflags & 2)
699 //=============================================================================
701 // TODO add a way to do looped sounds with sound(); then complete this entity
702 .float volume, atten;
703 void target_speaker_use_off();
704 void target_speaker_use_activator()
706 if (!IS_REAL_CLIENT(activator))
709 if(substring(self.noise, 0, 1) == "*")
712 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
713 if(GetPlayerSoundSampleField_notFound)
714 snd = "misc/null.wav";
715 else if(activator.sample == "")
716 snd = "misc/null.wav";
719 tokenize_console(activator.sample);
723 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
725 snd = strcat(argv(0), ".wav"); // randomization
730 msg_entity = activator;
731 soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
733 void target_speaker_use_on()
736 if(substring(self.noise, 0, 1) == "*")
739 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
740 if(GetPlayerSoundSampleField_notFound)
741 snd = "misc/null.wav";
742 else if(activator.sample == "")
743 snd = "misc/null.wav";
746 tokenize_console(activator.sample);
750 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
752 snd = strcat(argv(0), ".wav"); // randomization
757 sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
758 if(self.spawnflags & 3)
759 self.use = target_speaker_use_off;
761 void target_speaker_use_off()
763 sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
764 self.use = target_speaker_use_on;
766 void target_speaker_reset()
768 if(self.spawnflags & 1) // LOOPED_ON
770 if(self.use == target_speaker_use_on)
771 target_speaker_use_on();
773 else if(self.spawnflags & 2)
775 if(self.use == target_speaker_use_off)
776 target_speaker_use_off();
780 void spawnfunc_target_speaker()
782 // TODO: "*" prefix to sound file name
783 // TODO: wait and random (just, HOW? random is not a field)
785 precache_sound (self.noise);
787 if(!self.atten && !(self.spawnflags & 4))
790 self.atten = ATTEN_NORM;
792 self.atten = ATTEN_STATIC;
794 else if(self.atten < 0)
802 if(self.spawnflags & 8) // ACTIVATOR
803 self.use = target_speaker_use_activator;
804 else if(self.spawnflags & 1) // LOOPED_ON
806 target_speaker_use_on();
807 self.reset = target_speaker_reset;
809 else if(self.spawnflags & 2) // LOOPED_OFF
811 self.use = target_speaker_use_on;
812 self.reset = target_speaker_reset;
815 self.use = target_speaker_use_on;
817 else if(self.spawnflags & 1) // LOOPED_ON
819 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
822 else if(self.spawnflags & 2) // LOOPED_OFF
824 objerror("This sound entity can never be activated");
828 // Quake/Nexuiz fallback
829 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
835 void spawnfunc_func_stardust() {
836 self.effects = EF_STARDUST;
840 .float bgmscriptattack;
841 .float bgmscriptdecay;
842 .float bgmscriptsustain;
843 .float bgmscriptrelease;
844 float pointparticles_SendEntity(entity to, float fl)
846 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
848 // optional features to save space
850 if(self.spawnflags & 2)
851 fl |= 0x10; // absolute count on toggle-on
852 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
853 fl |= 0x20; // 4 bytes - saves CPU
854 if(self.waterlevel || self.count != 1)
855 fl |= 0x40; // 4 bytes - obscure features almost never used
856 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
857 fl |= 0x80; // 14 bytes - saves lots of space
859 WriteByte(MSG_ENTITY, fl);
863 WriteCoord(MSG_ENTITY, self.impulse);
865 WriteCoord(MSG_ENTITY, 0); // off
869 WriteCoord(MSG_ENTITY, self.origin.x);
870 WriteCoord(MSG_ENTITY, self.origin.y);
871 WriteCoord(MSG_ENTITY, self.origin.z);
875 if(self.model != "null")
877 WriteShort(MSG_ENTITY, self.modelindex);
880 WriteCoord(MSG_ENTITY, self.mins.x);
881 WriteCoord(MSG_ENTITY, self.mins.y);
882 WriteCoord(MSG_ENTITY, self.mins.z);
883 WriteCoord(MSG_ENTITY, self.maxs.x);
884 WriteCoord(MSG_ENTITY, self.maxs.y);
885 WriteCoord(MSG_ENTITY, self.maxs.z);
890 WriteShort(MSG_ENTITY, 0);
893 WriteCoord(MSG_ENTITY, self.maxs.x);
894 WriteCoord(MSG_ENTITY, self.maxs.y);
895 WriteCoord(MSG_ENTITY, self.maxs.z);
898 WriteShort(MSG_ENTITY, self.cnt);
901 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
902 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
906 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
907 WriteByte(MSG_ENTITY, self.count * 16.0);
909 WriteString(MSG_ENTITY, self.noise);
912 WriteByte(MSG_ENTITY, floor(self.atten * 64));
913 WriteByte(MSG_ENTITY, floor(self.volume * 255));
915 WriteString(MSG_ENTITY, self.bgmscript);
916 if(self.bgmscript != "")
918 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
919 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
920 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
921 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
927 void pointparticles_use()
929 self.state = !self.state;
933 void pointparticles_think()
935 if(self.origin != self.oldorigin)
938 self.oldorigin = self.origin;
940 self.nextthink = time;
943 void pointparticles_reset()
945 if(self.spawnflags & 1)
951 void spawnfunc_func_pointparticles()
954 setmodel(self, self.model);
956 precache_sound (self.noise);
958 if(!self.bgmscriptsustain)
959 self.bgmscriptsustain = 1;
960 else if(self.bgmscriptsustain < 0)
961 self.bgmscriptsustain = 0;
964 self.atten = ATTEN_NORM;
965 else if(self.atten < 0)
976 setorigin(self, self.origin + self.mins);
977 setsize(self, '0 0 0', self.maxs - self.mins);
980 self.cnt = particleeffectnum(self.mdl);
982 Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
986 self.use = pointparticles_use;
987 self.reset = pointparticles_reset;
992 self.think = pointparticles_think;
993 self.nextthink = time;
996 void spawnfunc_func_sparks()
998 // self.cnt is the amount of sparks that one burst will spawn
1000 self.cnt = 25.0; // nice default value
1003 // self.wait is the probability that a sparkthink will spawn a spark shower
1004 // range: 0 - 1, but 0 makes little sense, so...
1005 if(self.wait < 0.05) {
1006 self.wait = 0.25; // nice default value
1009 self.count = self.cnt;
1010 self.mins = '0 0 0';
1011 self.maxs = '0 0 0';
1012 self.velocity = '0 0 -1';
1013 self.mdl = "TE_SPARK";
1014 self.impulse = 10 * self.wait; // by default 2.5/sec
1016 self.cnt = 0; // use mdl
1018 spawnfunc_func_pointparticles();
1021 float rainsnow_SendEntity(entity to, float sf)
1023 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1024 WriteByte(MSG_ENTITY, self.state);
1025 WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
1026 WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
1027 WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
1028 WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
1029 WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
1030 WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
1031 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1032 WriteShort(MSG_ENTITY, self.count);
1033 WriteByte(MSG_ENTITY, self.cnt);
1037 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1038 This is an invisible area like a trigger, which rain falls inside of.
1042 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1044 sets color of rain (default 12 - white)
1046 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1048 void spawnfunc_func_rain()
1050 self.dest = self.velocity;
1051 self.velocity = '0 0 0';
1053 self.dest = '0 0 -700';
1054 self.angles = '0 0 0';
1055 self.movetype = MOVETYPE_NONE;
1056 self.solid = SOLID_NOT;
1057 SetBrushEntityModel();
1062 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1065 if(self.count > 65535)
1068 self.state = 1; // 1 is rain, 0 is snow
1071 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1075 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1076 This is an invisible area like a trigger, which snow falls inside of.
1080 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1082 sets color of rain (default 12 - white)
1084 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1086 void spawnfunc_func_snow()
1088 self.dest = self.velocity;
1089 self.velocity = '0 0 0';
1091 self.dest = '0 0 -300';
1092 self.angles = '0 0 0';
1093 self.movetype = MOVETYPE_NONE;
1094 self.solid = SOLID_NOT;
1095 SetBrushEntityModel();
1100 self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
1103 if(self.count > 65535)
1106 self.state = 0; // 1 is rain, 0 is snow
1109 Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
1113 void misc_laser_aim()
1118 if(self.spawnflags & 2)
1120 if(self.enemy.origin != self.mangle)
1122 self.mangle = self.enemy.origin;
1123 self.SendFlags |= 2;
1128 a = vectoangles(self.enemy.origin - self.origin);
1130 if(a != self.mangle)
1133 self.SendFlags |= 2;
1139 if(self.angles != self.mangle)
1141 self.mangle = self.angles;
1142 self.SendFlags |= 2;
1145 if(self.origin != self.oldorigin)
1147 self.SendFlags |= 1;
1148 self.oldorigin = self.origin;
1152 void misc_laser_init()
1154 if(self.target != "")
1155 self.enemy = find(world, targetname, self.target);
1159 void misc_laser_think()
1166 self.nextthink = time;
1175 o = self.enemy.origin;
1176 if (!(self.spawnflags & 2))
1177 o = self.origin + normalize(o - self.origin) * 32768;
1181 makevectors(self.mangle);
1182 o = self.origin + v_forward * 32768;
1185 if(self.dmg || self.enemy.target != "")
1187 traceline(self.origin, o, MOVE_NORMAL, self);
1190 hitloc = trace_endpos;
1192 if(self.enemy.target != "") // DETECTOR laser
1194 if(trace_ent.iscreature)
1196 self.pusher = hitent;
1203 activator = self.pusher;
1216 activator = self.pusher;
1226 if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
1228 if(hitent.takedamage)
1229 Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1233 float laser_SendEntity(entity to, float fl)
1235 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1236 fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1237 if(self.spawnflags & 2)
1241 if(self.scale != 1 || self.modelscale != 1)
1243 if(self.spawnflags & 4)
1245 WriteByte(MSG_ENTITY, fl);
1248 WriteCoord(MSG_ENTITY, self.origin.x);
1249 WriteCoord(MSG_ENTITY, self.origin.y);
1250 WriteCoord(MSG_ENTITY, self.origin.z);
1254 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
1255 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
1256 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
1258 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1261 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1262 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1264 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1265 WriteShort(MSG_ENTITY, self.cnt + 1);
1271 WriteCoord(MSG_ENTITY, self.enemy.origin.x);
1272 WriteCoord(MSG_ENTITY, self.enemy.origin.y);
1273 WriteCoord(MSG_ENTITY, self.enemy.origin.z);
1277 WriteAngle(MSG_ENTITY, self.mangle.x);
1278 WriteAngle(MSG_ENTITY, self.mangle.y);
1282 WriteByte(MSG_ENTITY, self.state);
1286 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1287 Any object touching the beam will be hurt
1290 spawnfunc_target_position where the laser ends
1292 name of beam end effect to use
1294 color of the beam (default: red)
1296 damage per second (-1 for a laser that kills immediately)
1300 self.state = !self.state;
1301 self.SendFlags |= 4;
1307 if(self.spawnflags & 1)
1313 void spawnfunc_misc_laser()
1317 if(self.mdl == "none")
1321 self.cnt = particleeffectnum(self.mdl);
1324 self.cnt = particleeffectnum("laser_deadly");
1330 self.cnt = particleeffectnum("laser_deadly");
1337 if(self.colormod == '0 0 0')
1339 self.colormod = '1 0 0';
1340 if(self.message == "")
1341 self.message = "saw the light";
1342 if (self.message2 == "")
1343 self.message2 = "was pushed into a laser by";
1346 if(!self.modelscale)
1347 self.modelscale = 1;
1348 else if(self.modelscale < 0)
1349 self.modelscale = 0;
1350 self.think = misc_laser_think;
1351 self.nextthink = time;
1352 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1354 self.mangle = self.angles;
1356 Net_LinkEntity(self, false, 0, laser_SendEntity);
1360 self.reset = laser_reset;
1362 self.use = laser_use;
1368 // tZorks trigger impulse / gravity
1372 .float lastpushtime;
1374 // targeted (directional) mode
1375 void trigger_impulse_touch1()
1378 float pushdeltatime;
1381 if (self.active != ACTIVE_ACTIVE)
1384 if (!isPushable(other))
1389 targ = find(world, targetname, self.target);
1392 objerror("trigger_force without a (valid) .target!\n");
1397 str = min(self.radius, vlen(self.origin - other.origin));
1399 if(self.falloff == 1)
1400 str = (str / self.radius) * self.strength;
1401 else if(self.falloff == 2)
1402 str = (1 - (str / self.radius)) * self.strength;
1404 str = self.strength;
1406 pushdeltatime = time - other.lastpushtime;
1407 if (pushdeltatime > 0.15) pushdeltatime = 0;
1408 other.lastpushtime = time;
1409 if(!pushdeltatime) return;
1411 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1412 other.flags &= ~FL_ONGROUND;
1413 UpdateCSQCProjectile(other);
1416 // Directionless (accelerator/decelerator) mode
1417 void trigger_impulse_touch2()
1419 float pushdeltatime;
1421 if (self.active != ACTIVE_ACTIVE)
1424 if (!isPushable(other))
1429 pushdeltatime = time - other.lastpushtime;
1430 if (pushdeltatime > 0.15) pushdeltatime = 0;
1431 other.lastpushtime = time;
1432 if(!pushdeltatime) return;
1434 // div0: ticrate independent, 1 = identity (not 20)
1435 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1436 UpdateCSQCProjectile(other);
1439 // Spherical (gravity/repulsor) mode
1440 void trigger_impulse_touch3()
1442 float pushdeltatime;
1445 if (self.active != ACTIVE_ACTIVE)
1448 if (!isPushable(other))
1453 pushdeltatime = time - other.lastpushtime;
1454 if (pushdeltatime > 0.15) pushdeltatime = 0;
1455 other.lastpushtime = time;
1456 if(!pushdeltatime) return;
1458 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1460 str = min(self.radius, vlen(self.origin - other.origin));
1462 if(self.falloff == 1)
1463 str = (1 - str / self.radius) * self.strength; // 1 in the inside
1464 else if(self.falloff == 2)
1465 str = (str / self.radius) * self.strength; // 0 in the inside
1467 str = self.strength;
1469 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1470 UpdateCSQCProjectile(other);
1473 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1474 -------- KEYS --------
1475 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1476 If not, this trigger acts like a damper/accelerator field.
1478 strength : This is how mutch force to add in the direction of .target each second
1479 when .target is set. If not, this is hoe mutch to slow down/accelerate
1480 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1482 radius : If set, act as a spherical device rather then a liniar one.
1484 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1486 -------- NOTES --------
1487 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1488 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1491 void spawnfunc_trigger_impulse()
1493 self.active = ACTIVE_ACTIVE;
1498 if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1499 setorigin(self, self.origin);
1500 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1501 self.touch = trigger_impulse_touch3;
1507 if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1508 self.touch = trigger_impulse_touch1;
1512 if(!self.strength) self.strength = 0.9;
1513 self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1514 self.touch = trigger_impulse_touch2;
1519 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1520 "Flip-flop" trigger gate... lets only every second trigger event through
1524 self.state = !self.state;
1529 void spawnfunc_trigger_flipflop()
1531 if(self.spawnflags & 1)
1533 self.use = flipflop_use;
1534 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1537 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1538 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1542 self.nextthink = time + self.wait;
1543 self.enemy = activator;
1549 void monoflop_fixed_use()
1553 self.nextthink = time + self.wait;
1555 self.enemy = activator;
1559 void monoflop_think()
1562 activator = self.enemy;
1566 void monoflop_reset()
1572 void spawnfunc_trigger_monoflop()
1576 if(self.spawnflags & 1)
1577 self.use = monoflop_fixed_use;
1579 self.use = monoflop_use;
1580 self.think = monoflop_think;
1582 self.reset = monoflop_reset;
1585 void multivibrator_send()
1590 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1592 newstate = (time < cyclestart + self.wait);
1595 if(self.state != newstate)
1597 self.state = newstate;
1600 self.nextthink = cyclestart + self.wait + 0.01;
1602 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1605 void multivibrator_toggle()
1607 if(self.nextthink == 0)
1609 multivibrator_send();
1622 void multivibrator_reset()
1624 if(!(self.spawnflags & 1))
1625 self.nextthink = 0; // wait for a trigger event
1627 self.nextthink = max(1, time);
1630 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1631 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1632 -------- KEYS --------
1633 target: trigger all entities with this targetname when it goes off
1634 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1635 phase: offset of the timing
1636 wait: "on" cycle time (default: 1)
1637 respawntime: "off" cycle time (default: same as wait)
1638 -------- SPAWNFLAGS --------
1639 START_ON: assume it is already turned on (when targeted)
1641 void spawnfunc_trigger_multivibrator()
1645 if(!self.respawntime)
1646 self.respawntime = self.wait;
1649 self.use = multivibrator_toggle;
1650 self.think = multivibrator_send;
1651 self.nextthink = max(1, time);
1654 multivibrator_reset();
1663 if(self.killtarget != "")
1664 src = find(world, targetname, self.killtarget);
1665 if(self.target != "")
1666 dst = find(world, targetname, self.target);
1670 objerror("follow: could not find target/killtarget");
1676 // already done :P entity must stay
1680 else if(!src || !dst)
1682 objerror("follow: could not find target/killtarget");
1685 else if(self.spawnflags & 1)
1688 if(self.spawnflags & 2)
1690 setattachment(dst, src, self.message);
1694 attach_sameorigin(dst, src, self.message);
1697 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1702 if(self.spawnflags & 2)
1704 dst.movetype = MOVETYPE_FOLLOW;
1706 // dst.punchangle = '0 0 0'; // keep unchanged
1707 dst.view_ofs = dst.origin;
1708 dst.v_angle = dst.angles;
1712 follow_sameorigin(dst, src);
1719 void spawnfunc_misc_follow()
1721 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1726 void gamestart_use() {
1732 void spawnfunc_trigger_gamestart() {
1733 self.use = gamestart_use;
1734 self.reset2 = spawnfunc_trigger_gamestart;
1738 self.think = self.use;
1739 self.nextthink = game_starttime + self.wait;
1742 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1748 .entity voicescript; // attached voice script
1749 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1750 .float voicescript_nextthink; // time to play next voice
1751 .float voicescript_voiceend; // time when this voice ends
1753 void target_voicescript_clear(entity pl)
1755 pl.voicescript = world;
1758 void target_voicescript_use()
1760 if(activator.voicescript != self)
1762 activator.voicescript = self;
1763 activator.voicescript_index = 0;
1764 activator.voicescript_nextthink = time + self.delay;
1768 void target_voicescript_next(entity pl)
1773 vs = pl.voicescript;
1776 if(vs.message == "")
1783 if(time >= pl.voicescript_voiceend)
1785 if(time >= pl.voicescript_nextthink)
1787 // get the next voice...
1788 n = tokenize_console(vs.message);
1790 if(pl.voicescript_index < vs.cnt)
1791 i = pl.voicescript_index * 2;
1792 else if(n > vs.cnt * 2)
1793 i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
1799 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1800 dt = stof(argv(i + 1));
1803 pl.voicescript_voiceend = time + dt;
1804 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1808 pl.voicescript_voiceend = time - dt;
1809 pl.voicescript_nextthink = pl.voicescript_voiceend;
1812 pl.voicescript_index += 1;
1816 pl.voicescript = world; // stop trying then
1822 void spawnfunc_target_voicescript()
1824 // netname: directory of the sound files
1825 // message: list of "sound file" duration "sound file" duration, a *, and again a list
1826 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1827 // Here, a - in front of the duration means that no delay is to be
1828 // added after this message
1829 // wait: average time between messages
1830 // delay: initial delay before the first message
1833 self.use = target_voicescript_use;
1835 n = tokenize_console(self.message);
1837 for(i = 0; i+1 < n; i += 2)
1844 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1850 void trigger_relay_teamcheck_use()
1854 if(self.spawnflags & 2)
1856 if(activator.team != self.team)
1861 if(activator.team == self.team)
1867 if(self.spawnflags & 1)
1872 void trigger_relay_teamcheck_reset()
1874 self.team = self.team_saved;
1877 void spawnfunc_trigger_relay_teamcheck()
1879 self.team_saved = self.team;
1880 self.use = trigger_relay_teamcheck_use;
1881 self.reset = trigger_relay_teamcheck_reset;
1886 void trigger_disablerelay_use()
1893 for(e = world; (e = find(e, targetname, self.target)); )
1895 if(e.use == SUB_UseTargets)
1897 e.use = SUB_DontUseTargets;
1900 else if(e.use == SUB_DontUseTargets)
1902 e.use = SUB_UseTargets;
1908 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1911 void spawnfunc_trigger_disablerelay()
1913 self.use = trigger_disablerelay_use;
1916 float magicear_matched;
1917 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1918 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1920 float domatch, dotrigger, matchstart, l;
1925 magicear_matched = false;
1927 dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1928 domatch = ((ear.spawnflags & 32) || dotrigger);
1935 // we are in TUBA mode!
1936 if (!(ear.spawnflags & 256))
1939 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
1942 magicear_matched = true;
1949 savemessage = self.message;
1950 self.message = string_null;
1952 self.message = savemessage;
1956 if(ear.netname != "")
1962 if(ear.spawnflags & 256) // ENOTUBA
1967 if(ear.spawnflags & 4)
1973 if(ear.spawnflags & 1)
1976 if(ear.spawnflags & 2)
1979 if(ear.spawnflags & 8)
1984 l = strlen(ear.message);
1986 if(ear.spawnflags & 128)
1989 msg = strdecolorize(msgin);
1991 if(substring(ear.message, 0, 1) == "*")
1993 if(substring(ear.message, -1, 1) == "*")
1996 // as we need multi-replacement here...
1997 s = substring(ear.message, 1, -2);
1999 if(strstrofs(msg, s, 0) >= 0)
2000 matchstart = -2; // we use strreplace on s
2005 s = substring(ear.message, 1, -1);
2007 if(substring(msg, -l, l) == s)
2008 matchstart = strlen(msg) - l;
2013 if(substring(ear.message, -1, 1) == "*")
2016 s = substring(ear.message, 0, -2);
2018 if(substring(msg, 0, l) == s)
2025 if(msg == ear.message)
2030 if(matchstart == -1) // no match
2033 magicear_matched = true;
2040 savemessage = self.message;
2041 self.message = string_null;
2043 self.message = savemessage;
2047 if(ear.spawnflags & 16)
2051 else if(ear.netname != "")
2054 return strreplace(s, ear.netname, msg);
2057 substring(msg, 0, matchstart),
2059 substring(msg, matchstart + l, -1)
2067 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2071 for(ear = magicears; ear; ear = ear.enemy)
2073 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2074 if(!(ear.spawnflags & 64))
2075 if(magicear_matched)
2082 void spawnfunc_trigger_magicear()
2084 self.enemy = magicears;
2087 // actually handled in "say" processing
2090 // 2 = ignore teamsay
2092 // 8 = ignore tell to unknown player
2093 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2094 // 32 = perform the replacement even if outside the radius or dead
2095 // 64 = continue replacing/triggering even if this one matched
2096 // 128 = don't decolorize message before matching
2097 // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2098 // 512 = tuba notes must be exact right pitch, no transposing
2108 // if set, replacement for the matched text
2110 // "hearing distance"
2114 // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2116 self.movedir_x -= 1; // map to tuba instrument numbers
2119 void relay_activators_use()
2125 for(trg = world; (trg = find(trg, targetname, os.target)); )
2129 trg.setactive(os.cnt);
2132 //bprint("Not using setactive\n");
2133 if(os.cnt == ACTIVE_TOGGLE)
2134 if(trg.active == ACTIVE_ACTIVE)
2135 trg.active = ACTIVE_NOT;
2137 trg.active = ACTIVE_ACTIVE;
2139 trg.active = os.cnt;
2145 void spawnfunc_relay_activate()
2147 self.cnt = ACTIVE_ACTIVE;
2148 self.use = relay_activators_use;
2151 void spawnfunc_relay_deactivate()
2153 self.cnt = ACTIVE_NOT;
2154 self.use = relay_activators_use;
2157 void spawnfunc_relay_activatetoggle()
2159 self.cnt = ACTIVE_TOGGLE;
2160 self.use = relay_activators_use;
2163 .string chmap, gametype;
2164 void spawnfunc_target_changelevel_use()
2166 if(self.gametype != "")
2167 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2169 if (self.chmap == "")
2170 localcmd("endmatch\n");
2172 localcmd(strcat("changelevel ", self.chmap, "\n"));
2175 void spawnfunc_target_changelevel()
2177 self.use = spawnfunc_target_changelevel_use;