1 void SUB_DontUseTargets()
\r
6 void() SUB_UseTargets;
\r
10 activator = self.enemy;
\r
16 ==============================
\r
19 the global "activator" should be set to the entity that initiated the firing.
\r
21 If self.delay is set, a DelayedUse entity will be created that will actually
\r
22 do the SUB_UseTargets after that many seconds have passed.
\r
24 Centerprints any self.message to the activator.
\r
26 Removes all entities with a targetname that match self.killtarget,
\r
27 and removes them, so some events can remove other triggers.
\r
29 Search for (string)targetname in all entities that
\r
30 match (string)self.target and call their .use function
\r
32 ==============================
\r
34 void SUB_UseTargets()
\r
36 local entity t, stemp, otemp, act;
\r
41 // check for a delay
\r
45 // create a temp object to fire at a later time
\r
47 t.classname = "DelayedUse";
\r
48 t.nextthink = time + self.delay;
\r
49 t.think = DelayThink;
\r
50 t.enemy = activator;
\r
51 t.message = self.message;
\r
52 t.killtarget = self.killtarget;
\r
53 t.target = self.target;
\r
59 // print the message
\r
61 if (activator.classname == "player" && self.message != "")
\r
63 if(clienttype(activator) == CLIENTTYPE_REAL)
\r
65 centerprint (activator, self.message);
\r
67 play2(activator, "misc/talk.wav");
\r
72 // kill the killtagets
\r
74 s = self.killtarget;
\r
77 for(t = world; (t = find(t, targetname, s)); )
\r
88 for(i = 0; i < 4; ++i)
\r
93 case 0: s = stemp.target; break;
\r
94 case 1: s = stemp.target2; break;
\r
95 case 2: s = stemp.target3; break;
\r
96 case 3: s = stemp.target4; break;
\r
100 for(t = world; (t = find(t, targetname, s)); )
\r
103 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
\r
118 //=============================================================================
\r
120 float SPAWNFLAG_NOMESSAGE = 1;
\r
121 float SPAWNFLAG_NOTOUCH = 1;
\r
123 // the wait time has passed, so set back up for another activation
\r
126 if (self.max_health)
\r
128 self.health = self.max_health;
\r
129 self.takedamage = DAMAGE_YES;
\r
130 self.solid = SOLID_BBOX;
\r
135 // the trigger was just touched/killed/used
\r
136 // self.enemy should be set to the activator so it can be held through a delay
\r
137 // so wait for the delay time before firing
\r
138 void multi_trigger()
\r
140 if (self.nextthink > time)
\r
142 return; // allready been triggered
\r
145 if (self.classname == "trigger_secret")
\r
147 if (self.enemy.classname != "player")
\r
149 found_secrets = found_secrets + 1;
\r
150 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
\r
154 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
\r
156 // don't trigger again until reset
\r
157 self.takedamage = DAMAGE_NO;
\r
159 activator = self.enemy;
\r
160 other = self.goalentity;
\r
165 self.think = multi_wait;
\r
166 self.nextthink = time + self.wait;
\r
168 else if (self.wait == 0)
\r
170 multi_wait(); // waiting finished
\r
173 { // we can't just remove (self) here, because this is a touch function
\r
174 // called wheil C code is looping through area links...
\r
175 self.touch = SUB_Null;
\r
181 self.goalentity = other;
\r
182 self.enemy = activator;
\r
188 if not(self.spawnflags & 2)
\r
190 if not(other.iscreature)
\r
194 if(self.team == other.team)
\r
198 // if the trigger has an angles field, check player's facing direction
\r
199 if (self.movedir != '0 0 0')
\r
201 makevectors (other.angles);
\r
202 if (v_forward * self.movedir < 0)
\r
203 return; // not facing the right way
\r
206 EXACTTRIGGER_TOUCH;
\r
208 self.enemy = other;
\r
209 self.goalentity = other;
\r
213 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
\r
215 if (!self.takedamage)
\r
217 if(self.spawnflags & DOOR_NOSPLASH)
\r
218 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
\r
220 self.health = self.health - damage;
\r
221 if (self.health <= 0)
\r
223 self.enemy = attacker;
\r
224 self.goalentity = inflictor;
\r
231 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
\r
232 self.touch = multi_touch;
\r
233 if (self.max_health)
\r
235 self.health = self.max_health;
\r
236 self.takedamage = DAMAGE_YES;
\r
237 self.solid = SOLID_BBOX;
\r
239 self.think = SUB_Null;
\r
240 self.team = self.team_saved;
\r
243 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
\r
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.
\r
245 If "delay" is set, the trigger waits some time after activating before firing.
\r
246 "wait" : Seconds between triggerings. (.2 default)
\r
247 If notouch is set, the trigger is only fired by other entities, not by touching.
\r
248 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
\r
254 set "message" to text string
\r
256 void spawnfunc_trigger_multiple()
\r
258 self.reset = multi_reset;
\r
259 if (self.sounds == 1)
\r
261 precache_sound ("misc/secret.wav");
\r
262 self.noise = "misc/secret.wav";
\r
264 else if (self.sounds == 2)
\r
266 precache_sound ("misc/talk.wav");
\r
267 self.noise = "misc/talk.wav";
\r
269 else if (self.sounds == 3)
\r
271 precache_sound ("misc/trigger1.wav");
\r
272 self.noise = "misc/trigger1.wav";
\r
277 else if(self.wait < -1)
\r
279 self.use = multi_use;
\r
283 self.team_saved = self.team;
\r
287 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
\r
288 objerror ("health and notouch don't make sense\n");
\r
289 self.max_health = self.health;
\r
290 self.event_damage = multi_eventdamage;
\r
291 self.takedamage = DAMAGE_YES;
\r
292 self.solid = SOLID_BBOX;
\r
293 setorigin (self, self.origin); // make sure it links into the world
\r
297 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
\r
299 self.touch = multi_touch;
\r
300 setorigin (self, self.origin); // make sure it links into the world
\r
306 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
\r
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
\r
308 "targetname". If "health" is set, the trigger must be killed to activate.
\r
309 If notouch is set, the trigger is only fired by other entities, not by touching.
\r
310 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
\r
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.
\r
317 set "message" to text string
\r
319 void spawnfunc_trigger_once()
\r
322 spawnfunc_trigger_multiple();
\r
325 //=============================================================================
\r
327 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
\r
328 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
\r
330 void spawnfunc_trigger_relay()
\r
332 self.use = SUB_UseTargets;
\r
333 self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
\r
338 self.think = SUB_UseTargets;
\r
339 self.nextthink = self.wait;
\r
344 self.think = SUB_Null;
\r
347 void spawnfunc_trigger_delay()
\r
352 self.use = delay_use;
\r
353 self.reset = delay_reset;
\r
356 //=============================================================================
\r
361 self.count = self.count - 1;
\r
362 if (self.count < 0)
\r
365 if (self.count != 0)
\r
367 if (activator.classname == "player"
\r
368 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
\r
370 if (self.count >= 4)
\r
371 centerprint (activator, "There are more to go...");
\r
372 else if (self.count == 3)
\r
373 centerprint (activator, "Only 3 more to go...");
\r
374 else if (self.count == 2)
\r
375 centerprint (activator, "Only 2 more to go...");
\r
377 centerprint (activator, "Only 1 more to go...");
\r
382 if (activator.classname == "player"
\r
383 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
\r
384 centerprint(activator, "Sequence completed!");
\r
385 self.enemy = activator;
\r
389 void counter_reset()
\r
391 self.count = self.cnt;
\r
395 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
\r
396 Acts as an intermediary for an action that takes multiple inputs.
\r
398 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
\r
400 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
\r
402 void spawnfunc_trigger_counter()
\r
407 self.cnt = self.count;
\r
409 self.use = counter_use;
\r
410 self.reset = counter_reset;
\r
413 .float triggerhurttime;
\r
414 void trigger_hurt_touch()
\r
416 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
\r
417 if (other.iscreature)
\r
419 if (other.takedamage)
\r
420 if (other.triggerhurttime < time)
\r
422 EXACTTRIGGER_TOUCH;
\r
423 other.triggerhurttime = time + 1;
\r
424 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
\r
431 if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
\r
433 EXACTTRIGGER_TOUCH;
\r
434 other.pain_finished = min(other.pain_finished, time + 2);
\r
442 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
\r
443 Any object touching this will be hurt
\r
444 set dmg to damage amount
\r
447 .entity trigger_hurt_next;
\r
448 entity trigger_hurt_last;
\r
449 entity trigger_hurt_first;
\r
450 void spawnfunc_trigger_hurt()
\r
453 self.touch = trigger_hurt_touch;
\r
457 self.message = "was in the wrong place";
\r
458 if (!self.message2)
\r
459 self.message2 = "was thrown into a world of hurt by";
\r
461 if(!trigger_hurt_first)
\r
462 trigger_hurt_first = self;
\r
463 if(trigger_hurt_last)
\r
464 trigger_hurt_last.trigger_hurt_next = self;
\r
465 trigger_hurt_last = self;
\r
468 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
\r
472 for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
\r
473 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
\r
479 //////////////////////////////////////////////////////////////
\r
483 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
\r
485 //////////////////////////////////////////////////////////////
\r
487 .float triggerhealtime;
\r
488 void trigger_heal_touch()
\r
490 // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
\r
491 if (other.iscreature)
\r
493 if (other.takedamage)
\r
494 if (other.triggerhealtime < time)
\r
496 EXACTTRIGGER_TOUCH;
\r
497 other.triggerhealtime = time + 1;
\r
499 if (other.health < self.max_health)
\r
501 other.health = min(other.health + self.health, self.max_health);
\r
502 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
\r
503 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
\r
509 void spawnfunc_trigger_heal()
\r
512 self.touch = trigger_heal_touch;
\r
515 if (!self.max_health)
\r
516 self.max_health = 200; //Max health topoff for field
\r
517 if(self.noise == "")
\r
518 self.noise = "misc/mediumhealth.wav";
\r
519 precache_sound(self.noise);
\r
523 //////////////////////////////////////////////////////////////
\r
529 //////////////////////////////////////////////////////////////
\r
533 // TODO add a way to do looped sounds with sound(); then complete this entity
\r
534 .float volume, atten;
\r
535 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
\r
537 void spawnfunc_target_speaker()
\r
540 precache_sound (self.noise);
\r
544 self.atten = ATTN_NORM;
\r
545 else if(self.atten < 0)
\r
549 self.use = target_speaker_use;
\r
554 self.atten = ATTN_STATIC;
\r
555 else if(self.atten < 0)
\r
559 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
\r
564 void spawnfunc_func_stardust() {
\r
565 self.effects = EF_STARDUST;
\r
569 .float bgmscriptattack;
\r
570 .float bgmscriptdecay;
\r
571 .float bgmscriptsustain;
\r
572 .float bgmscriptrelease;
\r
573 float pointparticles_SendEntity(entity to, float fl)
\r
575 WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
\r
577 // optional features to save space
\r
579 if(self.spawnflags & 2)
\r
580 fl |= 0x10; // absolute count on toggle-on
\r
581 if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
\r
582 fl |= 0x20; // 4 bytes - saves CPU
\r
583 if(self.waterlevel || self.count != 1)
\r
584 fl |= 0x40; // 4 bytes - obscure features almost never used
\r
585 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
\r
586 fl |= 0x80; // 14 bytes - saves lots of space
\r
588 WriteByte(MSG_ENTITY, fl);
\r
592 WriteCoord(MSG_ENTITY, self.impulse);
\r
594 WriteCoord(MSG_ENTITY, 0); // off
\r
598 WriteCoord(MSG_ENTITY, self.origin_x);
\r
599 WriteCoord(MSG_ENTITY, self.origin_y);
\r
600 WriteCoord(MSG_ENTITY, self.origin_z);
\r
604 if(self.model != "null")
\r
606 WriteShort(MSG_ENTITY, self.modelindex);
\r
609 WriteCoord(MSG_ENTITY, self.mins_x);
\r
610 WriteCoord(MSG_ENTITY, self.mins_y);
\r
611 WriteCoord(MSG_ENTITY, self.mins_z);
\r
612 WriteCoord(MSG_ENTITY, self.maxs_x);
\r
613 WriteCoord(MSG_ENTITY, self.maxs_y);
\r
614 WriteCoord(MSG_ENTITY, self.maxs_z);
\r
619 WriteShort(MSG_ENTITY, 0);
\r
622 WriteCoord(MSG_ENTITY, self.maxs_x);
\r
623 WriteCoord(MSG_ENTITY, self.maxs_y);
\r
624 WriteCoord(MSG_ENTITY, self.maxs_z);
\r
627 WriteShort(MSG_ENTITY, self.cnt);
\r
630 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
\r
631 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
\r
635 WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
\r
636 WriteByte(MSG_ENTITY, self.count * 16.0);
\r
638 WriteString(MSG_ENTITY, self.noise);
\r
639 if(self.noise != "")
\r
641 WriteByte(MSG_ENTITY, floor(self.atten * 64));
\r
642 WriteByte(MSG_ENTITY, floor(self.volume * 255));
\r
644 WriteString(MSG_ENTITY, self.bgmscript);
\r
645 if(self.bgmscript != "")
\r
647 WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
\r
648 WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
\r
649 WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
\r
650 WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
\r
656 void pointparticles_use()
\r
658 self.state = !self.state;
\r
659 self.SendFlags |= 2;
\r
662 void pointparticles_think()
\r
664 if(self.origin != self.oldorigin)
\r
666 self.SendFlags |= 4;
\r
667 self.oldorigin = self.origin;
\r
669 self.nextthink = time;
\r
672 void pointparticles_reset()
\r
674 if(self.spawnflags & 1)
\r
680 void spawnfunc_func_pointparticles()
\r
682 if(self.model != "")
\r
683 setmodel(self, self.model);
\r
684 if(self.noise != "")
\r
685 precache_sound (self.noise);
\r
687 if(!self.bgmscriptsustain)
\r
688 self.bgmscriptsustain = 1;
\r
689 else if(self.bgmscriptsustain < 0)
\r
690 self.bgmscriptsustain = 0;
\r
693 self.atten = ATTN_NORM;
\r
694 else if(self.atten < 0)
\r
703 if(!self.modelindex)
\r
705 setorigin(self, self.origin + self.mins);
\r
706 setsize(self, '0 0 0', self.maxs - self.mins);
\r
709 self.cnt = particleeffectnum(self.mdl);
\r
711 Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
\r
715 self.use = pointparticles_use;
\r
716 self.reset = pointparticles_reset;
\r
721 self.think = pointparticles_think;
\r
722 self.nextthink = time;
\r
725 void spawnfunc_func_sparks()
\r
727 // self.cnt is the amount of sparks that one burst will spawn
\r
729 self.cnt = 25.0; // nice default value
\r
732 // self.wait is the probability that a sparkthink will spawn a spark shower
\r
733 // range: 0 - 1, but 0 makes little sense, so...
\r
734 if(self.wait < 0.05) {
\r
735 self.wait = 0.25; // nice default value
\r
738 self.count = self.cnt;
\r
739 self.mins = '0 0 0';
\r
740 self.maxs = '0 0 0';
\r
741 self.velocity = '0 0 -1';
\r
742 self.mdl = "TE_SPARK";
\r
743 self.impulse = 10 * self.wait; // by default 2.5/sec
\r
745 self.cnt = 0; // use mdl
\r
747 spawnfunc_func_pointparticles();
\r
750 float rainsnow_SendEntity(entity to, float sf)
\r
752 WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
\r
753 WriteByte(MSG_ENTITY, self.state);
\r
754 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
\r
755 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
\r
756 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
\r
757 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
\r
758 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
\r
759 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
\r
760 WriteShort(MSG_ENTITY, compressShortVector(self.dest));
\r
761 WriteShort(MSG_ENTITY, self.count);
\r
762 WriteByte(MSG_ENTITY, self.cnt);
\r
766 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
\r
767 This is an invisible area like a trigger, which rain falls inside of.
\r
771 falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
\r
773 sets color of rain (default 12 - white)
\r
775 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
\r
777 void spawnfunc_func_rain()
\r
779 self.dest = self.velocity;
\r
780 self.velocity = '0 0 0';
\r
782 self.dest = '0 0 -700';
\r
783 self.angles = '0 0 0';
\r
784 self.movetype = MOVETYPE_NONE;
\r
785 self.solid = SOLID_NOT;
\r
786 SetBrushEntityModel();
\r
791 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
\r
792 if (self.count < 1)
\r
794 if(self.count > 65535)
\r
795 self.count = 65535;
\r
797 self.state = 1; // 1 is rain, 0 is snow
\r
800 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
\r
804 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
\r
805 This is an invisible area like a trigger, which snow falls inside of.
\r
809 falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
\r
811 sets color of rain (default 12 - white)
\r
813 adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
\r
815 void spawnfunc_func_snow()
\r
817 self.dest = self.velocity;
\r
818 self.velocity = '0 0 0';
\r
820 self.dest = '0 0 -300';
\r
821 self.angles = '0 0 0';
\r
822 self.movetype = MOVETYPE_NONE;
\r
823 self.solid = SOLID_NOT;
\r
824 SetBrushEntityModel();
\r
829 self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
\r
830 if (self.count < 1)
\r
832 if(self.count > 65535)
\r
833 self.count = 65535;
\r
835 self.state = 0; // 1 is rain, 0 is snow
\r
838 Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
\r
842 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
\r
845 void misc_laser_aim()
\r
850 if(self.spawnflags & 2)
\r
852 if(self.enemy.origin != self.mangle)
\r
854 self.mangle = self.enemy.origin;
\r
855 self.SendFlags |= 2;
\r
860 a = vectoangles(self.enemy.origin - self.origin);
\r
862 if(a != self.mangle)
\r
865 self.SendFlags |= 2;
\r
871 if(self.angles != self.mangle)
\r
873 self.mangle = self.angles;
\r
874 self.SendFlags |= 2;
\r
877 if(self.origin != self.oldorigin)
\r
879 self.SendFlags |= 1;
\r
880 self.oldorigin = self.origin;
\r
884 void misc_laser_init()
\r
886 if(self.target != "")
\r
887 self.enemy = find(world, targetname, self.target);
\r
891 void misc_laser_think()
\r
896 self.nextthink = time;
\r
905 o = self.enemy.origin;
\r
906 if not(self.spawnflags & 2)
\r
907 o = self.origin + normalize(o - self.origin) * 32768;
\r
911 makevectors(self.mangle);
\r
912 o = self.origin + v_forward * 32768;
\r
918 FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
\r
920 FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
\r
923 if(self.enemy.target != "") // DETECTOR laser
\r
925 traceline(self.origin, o, MOVE_NORMAL, self);
\r
926 if(trace_ent.iscreature)
\r
928 self.pusher = trace_ent;
\r
935 activator = self.pusher;
\r
948 activator = self.pusher;
\r
956 float laser_SendEntity(entity to, float fl)
\r
958 WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
\r
959 fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
\r
960 if(self.spawnflags & 2)
\r
964 if(self.scale != 1 || self.modelscale != 1)
\r
966 WriteByte(MSG_ENTITY, fl);
\r
969 WriteCoord(MSG_ENTITY, self.origin_x);
\r
970 WriteCoord(MSG_ENTITY, self.origin_y);
\r
971 WriteCoord(MSG_ENTITY, self.origin_z);
\r
975 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
\r
976 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
\r
977 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
\r
979 WriteByte(MSG_ENTITY, self.alpha * 255.0);
\r
982 WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
\r
983 WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
\r
985 WriteShort(MSG_ENTITY, self.cnt + 1);
\r
991 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
\r
992 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
\r
993 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
\r
997 WriteAngle(MSG_ENTITY, self.mangle_x);
\r
998 WriteAngle(MSG_ENTITY, self.mangle_y);
\r
1002 WriteByte(MSG_ENTITY, self.state);
\r
1006 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
\r
1007 Any object touching the beam will be hurt
\r
1010 spawnfunc_target_position where the laser ends
\r
1012 name of beam end effect to use
\r
1014 color of the beam (default: red)
\r
1016 damage per second (-1 for a laser that kills immediately)
\r
1020 self.state = !self.state;
\r
1021 self.SendFlags |= 4;
\r
1025 void laser_reset()
\r
1027 if(self.spawnflags & 1)
\r
1033 void spawnfunc_misc_laser()
\r
1037 if(self.mdl == "none")
\r
1041 self.cnt = particleeffectnum(self.mdl);
\r
1044 self.cnt = particleeffectnum("laser_deadly");
\r
1047 else if(!self.cnt)
\r
1050 self.cnt = particleeffectnum("laser_deadly");
\r
1057 if(self.colormod == '0 0 0')
\r
1059 self.colormod = '1 0 0';
\r
1061 self.message = "saw the light";
\r
1062 if (!self.message2)
\r
1063 self.message2 = "was pushed into a laser by";
\r
1066 if(!self.modelscale)
\r
1067 self.modelscale = 1;
\r
1068 self.think = misc_laser_think;
\r
1069 self.nextthink = time;
\r
1070 InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
\r
1072 self.mangle = self.angles;
\r
1074 Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
\r
1078 self.reset = laser_reset;
\r
1080 self.use = laser_use;
\r
1086 // tZorks trigger impulse / gravity
\r
1090 .float lastpushtime;
\r
1092 // targeted (directional) mode
\r
1093 void trigger_impulse_touch1()
\r
1096 float pushdeltatime;
\r
1099 // FIXME: Better checking for what to push and not.
\r
1100 if not(other.iscreature)
\r
1101 if (other.classname != "corpse")
\r
1102 if (other.classname != "body")
\r
1103 if (other.classname != "gib")
\r
1104 if (other.classname != "missile")
\r
1105 if (other.classname != "rocket")
\r
1106 if (other.classname != "casing")
\r
1107 if (other.classname != "grenade")
\r
1108 if (other.classname != "plasma")
\r
1109 if (other.classname != "plasma_prim")
\r
1110 if (other.classname != "plasma_chain")
\r
1111 if (other.classname != "droppedweapon")
\r
1114 if (other.deadflag && other.iscreature)
\r
1117 EXACTTRIGGER_TOUCH;
\r
1119 targ = find(world, targetname, self.target);
\r
1122 objerror("trigger_force without a (valid) .target!\n");
\r
1127 if(self.falloff == 1)
\r
1128 str = (str / self.radius) * self.strength;
\r
1129 else if(self.falloff == 2)
\r
1130 str = (1 - (str / self.radius)) * self.strength;
\r
1132 str = self.strength;
\r
1134 pushdeltatime = time - other.lastpushtime;
\r
1135 if (pushdeltatime > 0.15) pushdeltatime = 0;
\r
1136 other.lastpushtime = time;
\r
1137 if(!pushdeltatime) return;
\r
1139 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
\r
1140 other.flags &~= FL_ONGROUND;
\r
1143 // Directionless (accelerator/decelerator) mode
\r
1144 void trigger_impulse_touch2()
\r
1146 float pushdeltatime;
\r
1148 // FIXME: Better checking for what to push and not.
\r
1149 if not(other.iscreature)
\r
1150 if (other.classname != "corpse")
\r
1151 if (other.classname != "body")
\r
1152 if (other.classname != "gib")
\r
1153 if (other.classname != "missile")
\r
1154 if (other.classname != "rocket")
\r
1155 if (other.classname != "casing")
\r
1156 if (other.classname != "grenade")
\r
1157 if (other.classname != "plasma")
\r
1158 if (other.classname != "plasma_prim")
\r
1159 if (other.classname != "plasma_chain")
\r
1160 if (other.classname != "droppedweapon")
\r
1163 if (other.deadflag && other.iscreature)
\r
1166 EXACTTRIGGER_TOUCH;
\r
1168 pushdeltatime = time - other.lastpushtime;
\r
1169 if (pushdeltatime > 0.15) pushdeltatime = 0;
\r
1170 other.lastpushtime = time;
\r
1171 if(!pushdeltatime) return;
\r
1173 // div0: ticrate independent, 1 = identity (not 20)
\r
1174 other.velocity = other.velocity * pow(self.strength, pushdeltatime);
\r
1177 // Spherical (gravity/repulsor) mode
\r
1178 void trigger_impulse_touch3()
\r
1180 float pushdeltatime;
\r
1183 // FIXME: Better checking for what to push and not.
\r
1184 if not(other.iscreature)
\r
1185 if (other.classname != "corpse")
\r
1186 if (other.classname != "body")
\r
1187 if (other.classname != "gib")
\r
1188 if (other.classname != "missile")
\r
1189 if (other.classname != "rocket")
\r
1190 if (other.classname != "casing")
\r
1191 if (other.classname != "grenade")
\r
1192 if (other.classname != "plasma")
\r
1193 if (other.classname != "plasma_prim")
\r
1194 if (other.classname != "plasma_chain")
\r
1195 if (other.classname != "droppedweapon")
\r
1198 if (other.deadflag && other.iscreature)
\r
1201 EXACTTRIGGER_TOUCH;
\r
1203 pushdeltatime = time - other.lastpushtime;
\r
1204 if (pushdeltatime > 0.15) pushdeltatime = 0;
\r
1205 other.lastpushtime = time;
\r
1206 if(!pushdeltatime) return;
\r
1208 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
\r
1210 str = min(self.radius, vlen(self.origin - other.origin));
\r
1212 if(self.falloff == 1)
\r
1213 str = (1 - str / self.radius) * self.strength; // 1 in the inside
\r
1214 else if(self.falloff == 2)
\r
1215 str = (str / self.radius) * self.strength; // 0 in the inside
\r
1217 str = self.strength;
\r
1219 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
\r
1222 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
\r
1223 -------- KEYS --------
\r
1224 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
\r
1225 If not, this trigger acts like a damper/accelerator field.
\r
1227 strength : This is how mutch force to add in the direction of .target each second
\r
1228 when .target is set. If not, this is hoe mutch to slow down/accelerate
\r
1229 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
\r
1231 radius : If set, act as a spherical device rather then a liniar one.
\r
1233 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
\r
1235 -------- NOTES --------
\r
1236 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
\r
1237 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
\r
1240 void spawnfunc_trigger_impulse()
\r
1242 EXACTTRIGGER_INIT;
\r
1245 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
\r
1246 setorigin(self, self.origin);
\r
1247 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
\r
1248 self.touch = trigger_impulse_touch3;
\r
1254 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
\r
1255 self.touch = trigger_impulse_touch1;
\r
1259 if(!self.strength) self.strength = 0.9;
\r
1260 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
\r
1261 self.touch = trigger_impulse_touch2;
\r
1266 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
\r
1267 "Flip-flop" trigger gate... lets only every second trigger event through
\r
1269 void flipflop_use()
\r
1271 self.state = !self.state;
\r
1276 void spawnfunc_trigger_flipflop()
\r
1278 if(self.spawnflags & 1)
\r
1280 self.use = flipflop_use;
\r
1281 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
\r
1284 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
\r
1285 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
\r
1287 void monoflop_use()
\r
1289 self.nextthink = time + self.wait;
\r
1290 self.enemy = activator;
\r
1296 void monoflop_fixed_use()
\r
1300 self.nextthink = time + self.wait;
\r
1302 self.enemy = activator;
\r
1306 void monoflop_think()
\r
1309 activator = self.enemy;
\r
1313 void monoflop_reset()
\r
1316 self.nextthink = 0;
\r
1319 void spawnfunc_trigger_monoflop()
\r
1323 if(self.spawnflags & 1)
\r
1324 self.use = monoflop_fixed_use;
\r
1326 self.use = monoflop_use;
\r
1327 self.think = monoflop_think;
\r
1329 self.reset = monoflop_reset;
\r
1332 void multivibrator_send()
\r
1337 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
\r
1339 newstate = (time < cyclestart + self.wait);
\r
1342 if(self.state != newstate)
\r
1344 self.state = newstate;
\r
1347 self.nextthink = cyclestart + self.wait + 0.01;
\r
1349 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
\r
1352 void multivibrator_toggle()
\r
1354 if(self.nextthink == 0)
\r
1356 multivibrator_send();
\r
1365 self.nextthink = 0;
\r
1369 void multivibrator_reset()
\r
1371 if(!(self.spawnflags & 1))
\r
1372 self.nextthink = 0; // wait for a trigger event
\r
1374 self.nextthink = max(1, time);
\r
1377 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
\r
1378 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
\r
1379 -------- KEYS --------
\r
1380 target: trigger all entities with this targetname when it goes off
\r
1381 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
\r
1382 phase: offset of the timing
\r
1383 wait: "on" cycle time (default: 1)
\r
1384 respawntime: "off" cycle time (default: same as wait)
\r
1385 -------- SPAWNFLAGS --------
\r
1386 START_ON: assume it is already turned on (when targeted)
\r
1388 void spawnfunc_trigger_multivibrator()
\r
1392 if(!self.respawntime)
\r
1393 self.respawntime = self.wait;
\r
1396 self.use = multivibrator_toggle;
\r
1397 self.think = multivibrator_send;
\r
1398 self.nextthink = time;
\r
1401 multivibrator_reset();
\r
1405 void follow_init()
\r
1410 if(self.killtarget != "")
\r
1411 src = find(world, targetname, self.killtarget);
\r
1412 if(self.target != "")
\r
1413 dst = find(world, targetname, self.target);
\r
1417 objerror("follow: could not find target/killtarget");
\r
1421 if(self.jointtype)
\r
1423 // already done :P entity must stay
\r
1424 self.aiment = src;
\r
1427 else if(!src || !dst)
\r
1429 objerror("follow: could not find target/killtarget");
\r
1432 else if(self.spawnflags & 1)
\r
1435 if(self.spawnflags & 2)
\r
1437 setattachment(dst, src, self.message);
\r
1441 attach_sameorigin(dst, src, self.message);
\r
1448 if(self.spawnflags & 2)
\r
1450 dst.movetype = MOVETYPE_FOLLOW;
\r
1452 // dst.punchangle = '0 0 0'; // keep unchanged
\r
1453 dst.view_ofs = dst.origin;
\r
1454 dst.v_angle = dst.angles;
\r
1458 follow_sameorigin(dst, src);
\r
1465 void spawnfunc_misc_follow()
\r
1467 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
\r
1472 void gamestart_use() {
\r
1478 void spawnfunc_trigger_gamestart() {
\r
1479 self.use = gamestart_use;
\r
1480 self.reset2 = spawnfunc_trigger_gamestart;
\r
1484 self.think = self.use;
\r
1485 self.nextthink = game_starttime + self.wait;
\r
1488 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
\r
1494 .entity voicescript; // attached voice script
\r
1495 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
\r
1496 .float voicescript_nextthink; // time to play next voice
\r
1497 .float voicescript_voiceend; // time when this voice ends
\r
1499 void target_voicescript_clear(entity pl)
\r
1501 pl.voicescript = world;
\r
1504 void target_voicescript_use()
\r
1506 if(activator.voicescript != self)
\r
1508 activator.voicescript = self;
\r
1509 activator.voicescript_index = 0;
\r
1510 activator.voicescript_nextthink = time + self.delay;
\r
1514 void target_voicescript_next(entity pl)
\r
1519 vs = pl.voicescript;
\r
1522 if(vs.message == "")
\r
1524 if(pl.classname != "player")
\r
1529 if(time >= pl.voicescript_voiceend)
\r
1531 if(time >= pl.voicescript_nextthink)
\r
1533 // get the next voice...
\r
1534 n = tokenize_console(vs.message);
\r
1536 if(pl.voicescript_index < vs.cnt)
\r
1537 i = pl.voicescript_index * 2;
\r
1538 else if(n > vs.cnt * 2)
\r
1539 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
\r
1545 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
\r
1546 dt = stof(argv(i + 1));
\r
1549 pl.voicescript_voiceend = time + dt;
\r
1550 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
\r
1554 pl.voicescript_voiceend = time - dt;
\r
1555 pl.voicescript_nextthink = pl.voicescript_voiceend;
\r
1558 pl.voicescript_index += 1;
\r
1562 pl.voicescript = world; // stop trying then
\r
1568 void spawnfunc_target_voicescript()
\r
1570 // netname: directory of the sound files
\r
1571 // message: list of "sound file" duration "sound file" duration, a *, and again a list
\r
1572 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
\r
1573 // Here, a - in front of the duration means that no delay is to be
\r
1574 // added after this message
\r
1575 // wait: average time between messages
\r
1576 // delay: initial delay before the first message
\r
1579 self.use = target_voicescript_use;
\r
1581 n = tokenize_console(self.message);
\r
1583 for(i = 0; i+1 < n; i += 2)
\r
1585 if(argv(i) == "*")
\r
1590 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
\r
1596 void trigger_relay_teamcheck_use()
\r
1598 if(activator.team)
\r
1600 if(self.spawnflags & 2)
\r
1602 if(activator.team != self.team)
\r
1607 if(activator.team == self.team)
\r
1613 if(self.spawnflags & 1)
\r
1618 void trigger_relay_teamcheck_reset()
\r
1620 self.team = self.team_saved;
\r
1623 void spawnfunc_trigger_relay_teamcheck()
\r
1625 self.team_saved = self.team;
\r
1626 self.use = trigger_relay_teamcheck_use;
\r
1627 self.reset = trigger_relay_teamcheck_reset;
\r
1632 void trigger_disablerelay_use()
\r
1639 for(e = world; (e = find(e, targetname, self.target)); )
\r
1641 if(e.use == SUB_UseTargets)
\r
1643 e.use = SUB_DontUseTargets;
\r
1646 else if(e.use == SUB_DontUseTargets)
\r
1648 e.use = SUB_UseTargets;
\r
1654 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
\r
1657 void spawnfunc_trigger_disablerelay()
\r
1659 self.use = trigger_disablerelay_use;
\r
1662 float magicear_matched;
\r
1663 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
\r
1665 float domatch, dotrigger, matchstart, l;
\r
1669 magicear_matched = FALSE;
\r
1671 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
\r
1672 domatch = ((ear.spawnflags & 32) || dotrigger);
\r
1678 if(ear.spawnflags & 4)
\r
1684 if(ear.spawnflags & 1)
\r
1687 if(ear.spawnflags & 2)
\r
1690 if(ear.spawnflags & 8)
\r
1695 l = strlen(ear.message);
\r
1697 if(self.spawnflags & 128)
\r
1700 msg = strdecolorize(msgin);
\r
1702 if(substring(ear.message, 0, 1) == "*")
\r
1704 if(substring(ear.message, -1, 1) == "*")
\r
1707 // as we need multi-replacement here...
\r
1708 s = substring(ear.message, 1, -2);
\r
1710 if(strstrofs(msg, s, 0) >= 0)
\r
1711 matchstart = -2; // we use strreplace on s
\r
1716 s = substring(ear.message, 1, -1);
\r
1718 if(substring(msg, -l, l) == s)
\r
1719 matchstart = strlen(msg) - l;
\r
1724 if(substring(ear.message, -1, 1) == "*")
\r
1727 s = substring(ear.message, 0, -2);
\r
1729 if(substring(msg, 0, l) == s)
\r
1736 if(msg == ear.message)
\r
1741 if(matchstart == -1) // no match
\r
1744 magicear_matched = TRUE;
\r
1748 oldself = activator = self;
\r
1754 if(ear.spawnflags & 16)
\r
1756 return ear.netname;
\r
1758 else if(ear.netname != "")
\r
1760 if(matchstart < 0)
\r
1761 return strreplace(s, ear.netname, msg);
\r
1764 substring(msg, 0, matchstart),
\r
1766 substring(msg, matchstart + l, -1)
\r
1774 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
\r
1778 for(ear = magicears; ear; ear = ear.enemy)
\r
1780 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
\r
1781 if not(ear.spawnflags & 64)
\r
1782 if(magicear_matched)
\r
1789 void spawnfunc_trigger_magicear()
\r
1791 self.enemy = magicears;
\r
1794 // actually handled in "say" processing
\r
1797 // 2 = ignore teamsay
\r
1798 // 4 = ignore tell
\r
1799 // 8 = ignore tell to unknown player
\r
1800 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
\r
1801 // 32 = perform the replacement even if outside the radius or dead
\r
1802 // 64 = continue replacing/triggering even if this one matched
\r
1803 // message: either
\r
1812 // if set, replacement for the matched text
\r
1814 // "hearing distance"
\r
1816 // what to trigger
\r
1819 .string chmap, gametype;
\r
1820 void spawnfunc_target_changelevel_use()
\r
1822 if(self.gametype != "")
\r
1823 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
\r
1825 if (self.chmap == "")
\r
1826 localcmd("endmatch\n");
\r
1828 localcmd(strcat("changelevel ", self.chmap, "\n"));
\r
1831 void spawnfunc_target_changelevel()
\r
1833 self.use = spawnfunc_target_changelevel_use;
\r