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 // apply size-based weight
\r
1140 str *= (cvar("g_healthsize") && other.classname == "player") ? pow(other.scale, cvar("g_healthsize_weight") * 0.5) : 1;
\r
1142 other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
\r
1143 other.flags &~= FL_ONGROUND;
\r
1146 // Directionless (accelerator/decelerator) mode
\r
1147 void trigger_impulse_touch2()
\r
1149 float pushdeltatime;
\r
1152 // FIXME: Better checking for what to push and not.
\r
1153 if not(other.iscreature)
\r
1154 if (other.classname != "corpse")
\r
1155 if (other.classname != "body")
\r
1156 if (other.classname != "gib")
\r
1157 if (other.classname != "missile")
\r
1158 if (other.classname != "rocket")
\r
1159 if (other.classname != "casing")
\r
1160 if (other.classname != "grenade")
\r
1161 if (other.classname != "plasma")
\r
1162 if (other.classname != "plasma_prim")
\r
1163 if (other.classname != "plasma_chain")
\r
1164 if (other.classname != "droppedweapon")
\r
1167 if (other.deadflag && other.iscreature)
\r
1170 EXACTTRIGGER_TOUCH;
\r
1172 pushdeltatime = time - other.lastpushtime;
\r
1173 if (pushdeltatime > 0.15) pushdeltatime = 0;
\r
1174 other.lastpushtime = time;
\r
1175 if(!pushdeltatime) return;
\r
1177 str = pow(self.strength, pushdeltatime);
\r
1179 // apply size-based weight
\r
1180 str *= (cvar("g_healthsize") && other.classname == "player") ? pow(other.scale, cvar("g_healthsize_weight") * 0.5) : 1;
\r
1182 // div0: ticrate independent, 1 = identity (not 20)
\r
1183 other.velocity = other.velocity * str;
\r
1186 // Spherical (gravity/repulsor) mode
\r
1187 void trigger_impulse_touch3()
\r
1189 float pushdeltatime;
\r
1192 // FIXME: Better checking for what to push and not.
\r
1193 if not(other.iscreature)
\r
1194 if (other.classname != "corpse")
\r
1195 if (other.classname != "body")
\r
1196 if (other.classname != "gib")
\r
1197 if (other.classname != "missile")
\r
1198 if (other.classname != "rocket")
\r
1199 if (other.classname != "casing")
\r
1200 if (other.classname != "grenade")
\r
1201 if (other.classname != "plasma")
\r
1202 if (other.classname != "plasma_prim")
\r
1203 if (other.classname != "plasma_chain")
\r
1204 if (other.classname != "droppedweapon")
\r
1207 if (other.deadflag && other.iscreature)
\r
1210 EXACTTRIGGER_TOUCH;
\r
1212 pushdeltatime = time - other.lastpushtime;
\r
1213 if (pushdeltatime > 0.15) pushdeltatime = 0;
\r
1214 other.lastpushtime = time;
\r
1215 if(!pushdeltatime) return;
\r
1217 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
\r
1219 str = min(self.radius, vlen(self.origin - other.origin));
\r
1221 if(self.falloff == 1)
\r
1222 str = (1 - str / self.radius) * self.strength; // 1 in the inside
\r
1223 else if(self.falloff == 2)
\r
1224 str = (str / self.radius) * self.strength; // 0 in the inside
\r
1226 str = self.strength;
\r
1228 // apply size-based weight
\r
1229 str *= (cvar("g_healthsize") && other.classname == "player") ? pow(other.scale, cvar("g_healthsize_weight") * 0.5) : 1;
\r
1231 other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
\r
1234 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
\r
1235 -------- KEYS --------
\r
1236 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
\r
1237 If not, this trigger acts like a damper/accelerator field.
\r
1239 strength : This is how mutch force to add in the direction of .target each second
\r
1240 when .target is set. If not, this is hoe mutch to slow down/accelerate
\r
1241 someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
\r
1243 radius : If set, act as a spherical device rather then a liniar one.
\r
1245 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
\r
1247 -------- NOTES --------
\r
1248 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
\r
1249 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
\r
1252 void spawnfunc_trigger_impulse()
\r
1254 EXACTTRIGGER_INIT;
\r
1257 if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
\r
1258 setorigin(self, self.origin);
\r
1259 setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
\r
1260 self.touch = trigger_impulse_touch3;
\r
1266 if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
\r
1267 self.touch = trigger_impulse_touch1;
\r
1271 if(!self.strength) self.strength = 0.9;
\r
1272 self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
\r
1273 self.touch = trigger_impulse_touch2;
\r
1278 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
\r
1279 "Flip-flop" trigger gate... lets only every second trigger event through
\r
1281 void flipflop_use()
\r
1283 self.state = !self.state;
\r
1288 void spawnfunc_trigger_flipflop()
\r
1290 if(self.spawnflags & 1)
\r
1292 self.use = flipflop_use;
\r
1293 self.reset = spawnfunc_trigger_flipflop; // perfect resetter
\r
1296 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
\r
1297 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
\r
1299 void monoflop_use()
\r
1301 self.nextthink = time + self.wait;
\r
1302 self.enemy = activator;
\r
1308 void monoflop_fixed_use()
\r
1312 self.nextthink = time + self.wait;
\r
1314 self.enemy = activator;
\r
1318 void monoflop_think()
\r
1321 activator = self.enemy;
\r
1325 void monoflop_reset()
\r
1328 self.nextthink = 0;
\r
1331 void spawnfunc_trigger_monoflop()
\r
1335 if(self.spawnflags & 1)
\r
1336 self.use = monoflop_fixed_use;
\r
1338 self.use = monoflop_use;
\r
1339 self.think = monoflop_think;
\r
1341 self.reset = monoflop_reset;
\r
1344 void multivibrator_send()
\r
1349 cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
\r
1351 newstate = (time < cyclestart + self.wait);
\r
1354 if(self.state != newstate)
\r
1356 self.state = newstate;
\r
1359 self.nextthink = cyclestart + self.wait + 0.01;
\r
1361 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
\r
1364 void multivibrator_toggle()
\r
1366 if(self.nextthink == 0)
\r
1368 multivibrator_send();
\r
1377 self.nextthink = 0;
\r
1381 void multivibrator_reset()
\r
1383 if(!(self.spawnflags & 1))
\r
1384 self.nextthink = 0; // wait for a trigger event
\r
1386 self.nextthink = max(1, time);
\r
1389 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
\r
1390 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
\r
1391 -------- KEYS --------
\r
1392 target: trigger all entities with this targetname when it goes off
\r
1393 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
\r
1394 phase: offset of the timing
\r
1395 wait: "on" cycle time (default: 1)
\r
1396 respawntime: "off" cycle time (default: same as wait)
\r
1397 -------- SPAWNFLAGS --------
\r
1398 START_ON: assume it is already turned on (when targeted)
\r
1400 void spawnfunc_trigger_multivibrator()
\r
1404 if(!self.respawntime)
\r
1405 self.respawntime = self.wait;
\r
1408 self.use = multivibrator_toggle;
\r
1409 self.think = multivibrator_send;
\r
1410 self.nextthink = time;
\r
1413 multivibrator_reset();
\r
1417 void follow_init()
\r
1422 if(self.killtarget != "")
\r
1423 src = find(world, targetname, self.killtarget);
\r
1424 if(self.target != "")
\r
1425 dst = find(world, targetname, self.target);
\r
1429 objerror("follow: could not find target/killtarget");
\r
1433 if(self.jointtype)
\r
1435 // already done :P entity must stay
\r
1436 self.aiment = src;
\r
1439 else if(!src || !dst)
\r
1441 objerror("follow: could not find target/killtarget");
\r
1444 else if(self.spawnflags & 1)
\r
1447 if(self.spawnflags & 2)
\r
1449 setattachment(dst, src, self.message);
\r
1453 attach_sameorigin(dst, src, self.message);
\r
1460 if(self.spawnflags & 2)
\r
1462 dst.movetype = MOVETYPE_FOLLOW;
\r
1464 // dst.punchangle = '0 0 0'; // keep unchanged
\r
1465 dst.view_ofs = dst.origin;
\r
1466 dst.v_angle = dst.angles;
\r
1470 follow_sameorigin(dst, src);
\r
1477 void spawnfunc_misc_follow()
\r
1479 InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
\r
1484 void gamestart_use() {
\r
1490 void spawnfunc_trigger_gamestart() {
\r
1491 self.use = gamestart_use;
\r
1492 self.reset2 = spawnfunc_trigger_gamestart;
\r
1496 self.think = self.use;
\r
1497 self.nextthink = game_starttime + self.wait;
\r
1500 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
\r
1506 .entity voicescript; // attached voice script
\r
1507 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
\r
1508 .float voicescript_nextthink; // time to play next voice
\r
1509 .float voicescript_voiceend; // time when this voice ends
\r
1511 void target_voicescript_clear(entity pl)
\r
1513 pl.voicescript = world;
\r
1516 void target_voicescript_use()
\r
1518 if(activator.voicescript != self)
\r
1520 activator.voicescript = self;
\r
1521 activator.voicescript_index = 0;
\r
1522 activator.voicescript_nextthink = time + self.delay;
\r
1526 void target_voicescript_next(entity pl)
\r
1531 vs = pl.voicescript;
\r
1534 if(vs.message == "")
\r
1536 if(pl.classname != "player")
\r
1541 if(time >= pl.voicescript_voiceend)
\r
1543 if(time >= pl.voicescript_nextthink)
\r
1545 // get the next voice...
\r
1546 n = tokenize_console(vs.message);
\r
1548 if(pl.voicescript_index < vs.cnt)
\r
1549 i = pl.voicescript_index * 2;
\r
1550 else if(n > vs.cnt * 2)
\r
1551 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
\r
1557 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
\r
1558 dt = stof(argv(i + 1));
\r
1561 pl.voicescript_voiceend = time + dt;
\r
1562 pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
\r
1566 pl.voicescript_voiceend = time - dt;
\r
1567 pl.voicescript_nextthink = pl.voicescript_voiceend;
\r
1570 pl.voicescript_index += 1;
\r
1574 pl.voicescript = world; // stop trying then
\r
1580 void spawnfunc_target_voicescript()
\r
1582 // netname: directory of the sound files
\r
1583 // message: list of "sound file" duration "sound file" duration, a *, and again a list
\r
1584 // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
\r
1585 // Here, a - in front of the duration means that no delay is to be
\r
1586 // added after this message
\r
1587 // wait: average time between messages
\r
1588 // delay: initial delay before the first message
\r
1591 self.use = target_voicescript_use;
\r
1593 n = tokenize_console(self.message);
\r
1595 for(i = 0; i+1 < n; i += 2)
\r
1597 if(argv(i) == "*")
\r
1602 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
\r
1608 void trigger_relay_teamcheck_use()
\r
1610 if(activator.team)
\r
1612 if(self.spawnflags & 2)
\r
1614 if(activator.team != self.team)
\r
1619 if(activator.team == self.team)
\r
1625 if(self.spawnflags & 1)
\r
1630 void trigger_relay_teamcheck_reset()
\r
1632 self.team = self.team_saved;
\r
1635 void spawnfunc_trigger_relay_teamcheck()
\r
1637 self.team_saved = self.team;
\r
1638 self.use = trigger_relay_teamcheck_use;
\r
1639 self.reset = trigger_relay_teamcheck_reset;
\r
1644 void trigger_disablerelay_use()
\r
1651 for(e = world; (e = find(e, targetname, self.target)); )
\r
1653 if(e.use == SUB_UseTargets)
\r
1655 e.use = SUB_DontUseTargets;
\r
1658 else if(e.use == SUB_DontUseTargets)
\r
1660 e.use = SUB_UseTargets;
\r
1666 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
\r
1669 void spawnfunc_trigger_disablerelay()
\r
1671 self.use = trigger_disablerelay_use;
\r
1674 float magicear_matched;
\r
1675 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
\r
1677 float domatch, dotrigger, matchstart, l;
\r
1681 magicear_matched = FALSE;
\r
1683 dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
\r
1684 domatch = ((ear.spawnflags & 32) || dotrigger);
\r
1690 if(ear.spawnflags & 4)
\r
1696 if(ear.spawnflags & 1)
\r
1699 if(ear.spawnflags & 2)
\r
1702 if(ear.spawnflags & 8)
\r
1707 l = strlen(ear.message);
\r
1709 if(self.spawnflags & 128)
\r
1712 msg = strdecolorize(msgin);
\r
1714 if(substring(ear.message, 0, 1) == "*")
\r
1716 if(substring(ear.message, -1, 1) == "*")
\r
1719 // as we need multi-replacement here...
\r
1720 s = substring(ear.message, 1, -2);
\r
1722 if(strstrofs(msg, s, 0) >= 0)
\r
1723 matchstart = -2; // we use strreplace on s
\r
1728 s = substring(ear.message, 1, -1);
\r
1730 if(substring(msg, -l, l) == s)
\r
1731 matchstart = strlen(msg) - l;
\r
1736 if(substring(ear.message, -1, 1) == "*")
\r
1739 s = substring(ear.message, 0, -2);
\r
1741 if(substring(msg, 0, l) == s)
\r
1748 if(msg == ear.message)
\r
1753 if(matchstart == -1) // no match
\r
1756 magicear_matched = TRUE;
\r
1760 oldself = activator = self;
\r
1766 if(ear.spawnflags & 16)
\r
1768 return ear.netname;
\r
1770 else if(ear.netname != "")
\r
1772 if(matchstart < 0)
\r
1773 return strreplace(s, ear.netname, msg);
\r
1776 substring(msg, 0, matchstart),
\r
1778 substring(msg, matchstart + l, -1)
\r
1786 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
\r
1790 for(ear = magicears; ear; ear = ear.enemy)
\r
1792 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
\r
1793 if not(ear.spawnflags & 64)
\r
1794 if(magicear_matched)
\r
1801 void spawnfunc_trigger_magicear()
\r
1803 self.enemy = magicears;
\r
1806 // actually handled in "say" processing
\r
1809 // 2 = ignore teamsay
\r
1810 // 4 = ignore tell
\r
1811 // 8 = ignore tell to unknown player
\r
1812 // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
\r
1813 // 32 = perform the replacement even if outside the radius or dead
\r
1814 // 64 = continue replacing/triggering even if this one matched
\r
1815 // message: either
\r
1824 // if set, replacement for the matched text
\r
1826 // "hearing distance"
\r
1828 // what to trigger
\r
1831 .string chmap, gametype;
\r
1832 void spawnfunc_target_changelevel_use()
\r
1834 if(self.gametype != "")
\r
1835 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
\r
1837 if (self.chmap == "")
\r
1838 localcmd("endmatch\n");
\r
1840 localcmd(strcat("changelevel ", self.chmap, "\n"));
\r
1843 void spawnfunc_target_changelevel()
\r
1845 self.use = spawnfunc_target_changelevel_use;
\r