]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
func_clientweall, func_clientillusionary (with BGM script control)
[divverent/nexuiz.git] / data / qcsrc / server / g_triggers.qc
1 void SUB_DontUseTargets()
2 {
3 }
4
5
6 void() SUB_UseTargets;
7
8 void DelayThink()
9 {
10         activator = self.enemy;
11         SUB_UseTargets ();
12         remove(self);
13 };
14
15 /*
16 ==============================
17 SUB_UseTargets
18
19 the global "activator" should be set to the entity that initiated the firing.
20
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
23
24 Centerprints any self.message to the activator.
25
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
28
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
31
32 ==============================
33 */
34 void SUB_UseTargets()
35 {
36         local entity t, stemp, otemp, act;
37         string s;
38         float i;
39
40 //
41 // check for a delay
42 //
43         if (self.delay)
44         {
45         // create a temp object to fire at a later time
46                 t = spawn();
47                 t.classname = "DelayedUse";
48                 t.nextthink = time + self.delay;
49                 t.think = DelayThink;
50                 t.enemy = activator;
51                 t.message = self.message;
52                 t.killtarget = self.killtarget;
53                 t.target = self.target;
54                 return;
55         }
56
57
58 //
59 // print the message
60 //
61         if (activator.classname == "player" && self.message != "")
62         {
63                 if(clienttype(activator) == CLIENTTYPE_REAL)
64                 {
65                         centerprint (activator, self.message);
66                         if (!self.noise)
67                                 play2(activator, "misc/talk.wav");
68                 }
69         }
70
71 //
72 // kill the killtagets
73 //
74         s = self.killtarget;
75         if (s != "")
76         {
77                 for(t = world; (t = find(t, targetname, s)); )
78                         remove(t);
79         }
80
81 //
82 // fire targets
83 //
84         act = activator;
85         stemp = self;
86         otemp = other;
87
88         for(i = 0; i < 4; ++i)
89         {
90                 switch(i)
91                 {
92                         default:
93                         case 0: s = stemp.target; break;
94                         case 1: s = stemp.target2; break;
95                         case 2: s = stemp.target3; break;
96                         case 3: s = stemp.target4; break;
97                 }
98                 if (s != "")
99                 {
100                         for(t = world; (t = find(t, targetname, s)); )
101                         if(t.use)
102                         {
103                                 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
104                                 self = t;
105                                 other = stemp;
106                                 activator = act;
107                                 self.use();
108                         }
109                 }
110         }
111
112         activator = act;
113         self = stemp;
114         other = otemp;
115 };
116
117
118 //=============================================================================
119
120 float   SPAWNFLAG_NOMESSAGE = 1;
121 float   SPAWNFLAG_NOTOUCH = 1;
122
123 // the wait time has passed, so set back up for another activation
124 void multi_wait()
125 {
126         if (self.max_health)
127         {
128                 self.health = self.max_health;
129                 self.takedamage = DAMAGE_YES;
130                 self.solid = SOLID_BBOX;
131         }
132 };
133
134
135 // the trigger was just touched/killed/used
136 // self.enemy should be set to the activator so it can be held through a delay
137 // so wait for the delay time before firing
138 void multi_trigger()
139 {
140         if (self.nextthink > time)
141         {
142                 return;         // allready been triggered
143         }
144
145         if (self.classname == "trigger_secret")
146         {
147                 if (self.enemy.classname != "player")
148                         return;
149                 found_secrets = found_secrets + 1;
150                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
151         }
152
153         if (self.noise)
154                 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
155
156 // don't trigger again until reset
157         self.takedamage = DAMAGE_NO;
158
159         activator = self.enemy;
160
161         SUB_UseTargets();
162
163         if (self.wait > 0)
164         {
165                 self.think = multi_wait;
166                 self.nextthink = time + self.wait;
167         }
168         else
169         {       // we can't just remove (self) here, because this is a touch function
170                 // called wheil C code is looping through area links...
171                 self.touch = SUB_Null;
172         }
173 };
174
175 void multi_use()
176 {
177         self.enemy = activator;
178         multi_trigger();
179 };
180
181 void multi_touch()
182 {
183         if not(self.spawnflags & 2)
184         {
185                 if not(other.iscreature)
186                         return;
187
188                 if(self.team)
189                 if(self.team == other.team)
190                         return;
191         }
192
193 // if the trigger has an angles field, check player's facing direction
194         if (self.movedir != '0 0 0')
195         {
196                 makevectors (other.angles);
197                 if (v_forward * self.movedir < 0)
198                         return;         // not facing the right way
199         }
200
201         EXACTTRIGGER_TOUCH;
202
203         self.enemy = other;
204         multi_trigger ();
205 };
206
207 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
208 {
209         if (!self.takedamage)
210                 return;
211         self.health = self.health - damage;
212         if (self.health <= 0)
213         {
214                 self.enemy = attacker;
215                 multi_trigger();
216         }
217 }
218
219 void multi_reset()
220 {
221         self.touch = multi_touch;
222         self.health = self.max_health;
223         self.takedamage = DAMAGE_YES;
224         self.solid = SOLID_BBOX;
225         self.think = SUB_Null;
226         self.team = self.team_saved;
227 }
228
229 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
230 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.
231 If "delay" is set, the trigger waits some time after activating before firing.
232 "wait" : Seconds between triggerings. (.2 default)
233 If notouch is set, the trigger is only fired by other entities, not by touching.
234 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
235 sounds
236 1)      secret
237 2)      beep beep
238 3)      large switch
239 4)
240 set "message" to text string
241 */
242 void spawnfunc_trigger_multiple()
243 {
244         self.reset = multi_reset;
245         if (self.sounds == 1)
246         {
247                 precache_sound ("misc/secret.wav");
248                 self.noise = "misc/secret.wav";
249         }
250         else if (self.sounds == 2)
251         {
252                 precache_sound ("misc/talk.wav");
253                 self.noise = "misc/talk.wav";
254         }
255         else if (self.sounds == 3)
256         {
257                 precache_sound ("misc/trigger1.wav");
258                 self.noise = "misc/trigger1.wav";
259         }
260
261         if (!self.wait)
262                 self.wait = 0.2;
263         self.use = multi_use;
264
265         EXACTTRIGGER_INIT;
266
267         self.team_saved = self.team;
268
269         if (self.health)
270         {
271                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
272                         objerror ("health and notouch don't make sense\n");
273                 self.max_health = self.health;
274                 self.event_damage = multi_eventdamage;
275                 self.takedamage = DAMAGE_YES;
276                 self.solid = SOLID_BBOX;
277                 setorigin (self, self.origin);  // make sure it links into the world
278         }
279         else
280         {
281                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
282                 {
283                         self.touch = multi_touch;
284                         setorigin (self, self.origin);  // make sure it links into the world
285                 }
286         }
287 };
288
289
290 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
291 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
292 "targetname".  If "health" is set, the trigger must be killed to activate.
293 If notouch is set, the trigger is only fired by other entities, not by touching.
294 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
295 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.
296 sounds
297 1)      secret
298 2)      beep beep
299 3)      large switch
300 4)
301 set "message" to text string
302 */
303 void spawnfunc_trigger_once()
304 {
305         self.wait = -1;
306         spawnfunc_trigger_multiple();
307 };
308
309 //=============================================================================
310
311 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
312 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
313 */
314 void spawnfunc_trigger_relay()
315 {
316         self.use = SUB_UseTargets;
317         self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
318 };
319
320 void delay_use()
321 {
322     self.think = SUB_UseTargets;
323     self.nextthink = self.wait;
324 }
325
326 void delay_reset()
327 {
328         self.think = SUB_Null;
329 }
330
331 void spawnfunc_trigger_delay()
332 {
333     if(!self.wait)
334         self.wait = 1;
335
336     self.use = delay_use;
337     self.reset = delay_reset;
338 }
339
340 //=============================================================================
341
342
343 void counter_use()
344 {
345         self.count = self.count - 1;
346         if (self.count < 0)
347                 return;
348
349         if (self.count != 0)
350         {
351                 if (activator.classname == "player"
352                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
353                 {
354                         if (self.count >= 4)
355                                 centerprint (activator, "There are more to go...");
356                         else if (self.count == 3)
357                                 centerprint (activator, "Only 3 more to go...");
358                         else if (self.count == 2)
359                                 centerprint (activator, "Only 2 more to go...");
360                         else
361                                 centerprint (activator, "Only 1 more to go...");
362                 }
363                 return;
364         }
365
366         if (activator.classname == "player"
367         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
368                 centerprint(activator, "Sequence completed!");
369         self.enemy = activator;
370         multi_trigger ();
371 };
372
373 void counter_reset()
374 {
375         self.count = self.cnt;
376         multi_reset();
377 }
378
379 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
380 Acts as an intermediary for an action that takes multiple inputs.
381
382 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
383
384 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
385 */
386 void spawnfunc_trigger_counter()
387 {
388         self.wait = -1;
389         if (!self.count)
390                 self.count = 2;
391         self.cnt = self.count;
392
393         self.use = counter_use;
394         self.reset = counter_reset;
395 };
396
397 .float triggerhurttime;
398 void trigger_hurt_touch()
399 {
400         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
401         if (other.iscreature)
402         {
403                 if (other.takedamage)
404                 if (other.triggerhurttime < time)
405                 {
406                         EXACTTRIGGER_TOUCH;
407                         other.triggerhurttime = time + 1;
408                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
409                 }
410         }
411         else
412         {
413                 if (!other.owner)
414                 {
415                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
416                         {
417                                 EXACTTRIGGER_TOUCH;
418                                 other.pain_finished = min(other.pain_finished, time + 2);
419                         }
420                         else if (other.classname == "rune")                     // reset runes
421                         {
422                                 EXACTTRIGGER_TOUCH;
423                                 other.nextthink = min(other.nextthink, time + 1);
424                         }
425                 }
426         }
427
428         return;
429 };
430
431 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
432 Any object touching this will be hurt
433 set dmg to damage amount
434 defalt dmg = 5
435 */
436 .entity trigger_hurt_next;
437 entity trigger_hurt_last;
438 entity trigger_hurt_first;
439 void spawnfunc_trigger_hurt()
440 {
441         EXACTTRIGGER_INIT;
442         self.touch = trigger_hurt_touch;
443         if (!self.dmg)
444                 self.dmg = 1000;
445         if (!self.message)
446                 self.message = "was in the wrong place";
447         if (!self.message2)
448                 self.message2 = "was thrown into a world of hurt by";
449
450         if(!trigger_hurt_first)
451                 trigger_hurt_first = self;
452         if(trigger_hurt_last)
453                 trigger_hurt_last.trigger_hurt_next = self;
454         trigger_hurt_last = self;
455 };
456
457 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
458 {
459         entity th;
460
461         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
462                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
463                         return TRUE;
464
465         return FALSE;
466 }
467
468 //////////////////////////////////////////////////////////////
469 //
470 //
471 //
472 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
473 //
474 //////////////////////////////////////////////////////////////
475
476 .float triggerhealtime;
477 void trigger_heal_touch()
478 {
479         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
480         if (other.iscreature)
481         {
482                 if (other.takedamage)
483                 if (other.triggerhealtime < time)
484                 {
485                         EXACTTRIGGER_TOUCH;
486                         other.triggerhealtime = time + 1;
487                         
488                         if (other.health < self.max_health)
489                         {
490                                 other.health = min(other.health + self.health, self.max_health);
491                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
492                                 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
493                         }
494                 }
495         }
496 };
497
498 void spawnfunc_trigger_heal()
499 {
500         EXACTTRIGGER_INIT;
501         self.touch = trigger_heal_touch;
502         if (!self.health)
503                 self.health = 10;
504         if (!self.max_health)
505                 self.max_health = 200; //Max health topoff for field
506         if(self.noise == "")
507                 self.noise = "misc/mediumhealth.wav";
508         precache_sound(self.noise);
509 };
510
511
512 //////////////////////////////////////////////////////////////
513 //
514 //
515 //
516 //End trigger_heal
517 //
518 //////////////////////////////////////////////////////////////
519
520
521
522 // TODO add a way to do looped sounds with sound(); then complete this entity
523 .float volume, atten;
524 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
525
526 void spawnfunc_target_speaker()
527 {
528         if(self.noise)
529                 precache_sound (self.noise);
530         IFTARGETED
531         {
532                 if(!self.atten)
533                         self.atten = ATTN_NORM;
534                 else if(self.atten < 0)
535                         self.atten = 0;
536                 if(!self.volume)
537                         self.volume = 1;
538                 self.use = target_speaker_use;
539         }
540         else
541         {
542                 if(!self.atten)
543                         self.atten = ATTN_STATIC;
544                 else if(self.atten < 0)
545                         self.atten = 0;
546                 if(!self.volume)
547                         self.volume = 1;
548                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
549         }
550 };
551
552
553 void spawnfunc_func_stardust() {
554         self.effects = EF_STARDUST;
555 }
556
557 .string bgmscript;
558 .float bgmscriptattack;
559 .float bgmscriptdecay;
560 .float bgmscriptsustain;
561 .float bgmscriptrelease;
562 float pointparticles_SendEntity(entity to, float fl)
563 {
564         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
565
566         // optional features to save space
567         fl = fl & 0x0F;
568         if(self.spawnflags & 2)
569                 fl |= 0x10; // absolute count on toggle-on
570         if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
571                 fl |= 0x20; // 4 bytes - saves CPU
572         if(self.waterlevel || self.count != 1)
573                 fl |= 0x40; // 4 bytes - obscure features almost never used
574         if(self.mins != '0 0 0' || self.maxs != '0 0 0')
575                 fl |= 0x80; // 14 bytes - saves lots of space
576
577         WriteByte(MSG_ENTITY, fl);
578         if(fl & 2)
579         {
580                 if(self.state)
581                         WriteCoord(MSG_ENTITY, self.impulse);
582                 else
583                         WriteCoord(MSG_ENTITY, 0); // off
584         }
585         if(fl & 4)
586         {
587                 WriteCoord(MSG_ENTITY, self.origin_x);
588                 WriteCoord(MSG_ENTITY, self.origin_y);
589                 WriteCoord(MSG_ENTITY, self.origin_z);
590         }
591         if(fl & 1)
592         {
593                 if(self.model != "null")
594                 {
595                         WriteShort(MSG_ENTITY, self.modelindex);
596                         if(fl & 0x80)
597                         {
598                                 WriteCoord(MSG_ENTITY, self.mins_x);
599                                 WriteCoord(MSG_ENTITY, self.mins_y);
600                                 WriteCoord(MSG_ENTITY, self.mins_z);
601                                 WriteCoord(MSG_ENTITY, self.maxs_x);
602                                 WriteCoord(MSG_ENTITY, self.maxs_y);
603                                 WriteCoord(MSG_ENTITY, self.maxs_z);
604                         }
605                 }
606                 else
607                 {
608                         WriteShort(MSG_ENTITY, 0);
609                         if(fl & 0x80)
610                         {
611                                 WriteCoord(MSG_ENTITY, self.maxs_x);
612                                 WriteCoord(MSG_ENTITY, self.maxs_y);
613                                 WriteCoord(MSG_ENTITY, self.maxs_z);
614                         }
615                 }
616                 WriteShort(MSG_ENTITY, self.cnt);
617                 if(fl & 0x20)
618                 {
619                         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
620                         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
621                 }
622                 if(fl & 0x40)
623                 {
624                         WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
625                         WriteByte(MSG_ENTITY, self.count * 16.0);
626                 }
627                 WriteString(MSG_ENTITY, self.noise);
628                 if(self.noise != "")
629                 {
630                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
631                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
632                 }
633                 WriteString(MSG_ENTITY, self.bgmscript);
634                 if(self.bgmscript != "")
635                 {
636                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
637                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
638                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
639                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
640                 }
641         }
642         return 1;
643 }
644
645 void pointparticles_use()
646 {
647         self.state = !self.state;
648         self.SendFlags |= 2;
649 }
650
651 void pointparticles_think()
652 {
653         if(self.origin != self.oldorigin)
654         {
655                 self.SendFlags |= 4;
656                 self.oldorigin = self.origin;
657         }
658         self.nextthink = time;
659 }
660
661 void pointparticles_reset()
662 {
663         if(self.spawnflags & 1)
664                 self.state = 1;
665         else
666                 self.state = 0;
667 }
668
669 void spawnfunc_func_pointparticles()
670 {
671         if(self.model != "")
672                 setmodel(self, self.model);
673         if(self.noise != "")
674                 precache_sound (self.noise);
675         
676         if(!self.bgmscriptsustain)
677                 self.bgmscriptsustain = 1;
678         else if(self.bgmscriptsustain < 0)
679                 self.bgmscriptsustain = 0;
680
681         if(!self.atten)
682                 self.atten = ATTN_NORM;
683         else if(self.atten < 0)
684                 self.atten = 0;
685         if(!self.volume)
686                 self.volume = 1;
687         if(!self.count)
688                 self.count = 1;
689         if(!self.impulse)
690                 self.impulse = 1;
691
692         if(!self.modelindex)
693         {
694                 setorigin(self, self.origin + self.mins);
695                 setsize(self, '0 0 0', self.maxs - self.mins);
696         }
697         if(!self.cnt)
698                 self.cnt = particleeffectnum(self.mdl);
699
700         Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
701
702         IFTARGETED
703         {
704                 self.use = pointparticles_use;
705                 self.reset = pointparticles_reset;
706                 self.reset();
707         }
708         else
709                 self.state = 1;
710         self.think = pointparticles_think;
711         self.nextthink = time;
712 }
713
714 float func_clientwall_send (entity to, float sf)
715 {
716         sf = sf & 0x0F;
717         if(self.angles != '0 0 0')
718                 sf |= 0x10;
719         if(self.solid && (self.mins != '0 0 0' || self.maxs != '0 0 0'))
720                 sf |= 0x20;
721         if(self.colormap != 0)
722                 sf |= 0x40;
723         
724         WriteByte(MSG_ENTITY, ENT_CLIENT_WALL);
725         WriteByte(MSG_ENTITY, sf);
726
727         if(sf & 1)
728         {
729                 if(sf & 0x40)
730                         WriteShort(MSG_ENTITY, self.colormap);
731         }
732
733         if(sf & 2)
734         {
735                 WriteCoord(MSG_ENTITY, self.origin_x);
736                 WriteCoord(MSG_ENTITY, self.origin_y);
737                 WriteCoord(MSG_ENTITY, self.origin_z);
738         }
739
740         if(sf & 4)
741         {
742                 if(sf & 0x10)
743                 {
744                         WriteAngle(MSG_ENTITY, self.angles_x);
745                         WriteAngle(MSG_ENTITY, self.angles_y);
746                         WriteAngle(MSG_ENTITY, self.angles_z);
747                 }
748         }
749
750         if(sf & 8)
751         {
752                 WriteShort(MSG_ENTITY, self.modelindex);
753                 WriteByte(MSG_ENTITY, self.solid);
754                 WriteByte(MSG_ENTITY, floor(self.scale * 16));
755                 if(sf & 0x20)
756                 {
757                         WriteCoord(MSG_ENTITY, self.mins_x);
758                         WriteCoord(MSG_ENTITY, self.mins_y);
759                         WriteCoord(MSG_ENTITY, self.mins_z);
760                         WriteCoord(MSG_ENTITY, self.maxs_x);
761                         WriteCoord(MSG_ENTITY, self.maxs_y);
762                         WriteCoord(MSG_ENTITY, self.maxs_z);
763                 }
764                 WriteString(MSG_ENTITY, self.bgmscript);
765                 if(self.bgmscript != "")
766                 {
767                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
768                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
769                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
770                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
771                         WriteCoord(MSG_ENTITY, self.movedir_x);
772                         WriteCoord(MSG_ENTITY, self.movedir_y);
773                         WriteCoord(MSG_ENTITY, self.movedir_z);
774                         WriteByte(MSG_ENTITY, floor(self.lip * 255));
775                 }
776         }
777
778         return TRUE;
779 }
780
781 void func_clientwall_use (void)
782 {
783         if(teams_matter)
784         {
785                 if(activator.team)
786                         self.colormap = (activator.team - 1) * 0x11;
787                 else
788                         self.colormap = 0x00;
789         }
790         else
791                 self.colormap = floor(random() * 256);
792         self.colormap |= 1024; // RENDER_COLORMAPPED
793         self.SendFlags |= 1;
794 }
795
796 void spawnfunc_func_clientillusionary()
797 {
798         SetBrushEntityModelNoLOD(); // LOD can't be done the CEFC way
799         self.use = func_clientwall_use;
800
801         if(!self.bgmscriptsustain)
802                 self.bgmscriptsustain = 1;
803         else if(self.bgmscriptsustain < 0)
804                 self.bgmscriptsustain = 0;
805
806         Net_LinkEntity(self, TRUE, 0, func_clientwall_send);
807 }
808
809 void spawnfunc_func_clientwall()
810 {
811         SetBrushEntityModelNoLOD(); // LOD can't be done the CEFC way
812         if(!self.solid)
813                 self.solid = SOLID_BSP;
814         self.use = func_clientwall_use;
815
816         if(!self.bgmscriptsustain)
817                 self.bgmscriptsustain = 1;
818         else if(self.bgmscriptsustain < 0)
819                 self.bgmscriptsustain = 0;
820
821         Net_LinkEntity(self, TRUE, 0, func_clientwall_send);
822 }
823
824 void spawnfunc_func_sparks()
825 {
826         // self.cnt is the amount of sparks that one burst will spawn
827         if(self.cnt < 1) {
828                 self.cnt = 25.0; // nice default value
829         }
830
831         // self.wait is the probability that a sparkthink will spawn a spark shower
832         // range: 0 - 1, but 0 makes little sense, so...
833         if(self.wait < 0.05) {
834                 self.wait = 0.25; // nice default value
835         }
836
837         self.count = self.cnt;
838         self.mins = '0 0 0';
839         self.maxs = '0 0 0';
840         self.velocity = '0 0 -1';
841         self.mdl = "TE_SPARK";
842         self.impulse = 10 * self.wait; // by default 2.5/sec
843         self.wait = 0;
844         self.cnt = 0; // use mdl
845
846         spawnfunc_func_pointparticles();
847 }
848
849 float rainsnow_SendEntity(entity to, float sf)
850 {
851         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
852         WriteByte(MSG_ENTITY, self.state);
853         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
854         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
855         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
856         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
857         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
858         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
859         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
860         WriteShort(MSG_ENTITY, self.count);
861         WriteByte(MSG_ENTITY, self.cnt);
862         return 1;
863 };
864
865 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
866 This is an invisible area like a trigger, which rain falls inside of.
867
868 Keys:
869 "velocity"
870  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
871 "cnt"
872  sets color of rain (default 12 - white)
873 "count"
874  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
875 */
876 void spawnfunc_func_rain()
877 {
878         self.dest = self.velocity;
879         self.velocity = '0 0 0';
880         if (!self.dest)
881                 self.dest = '0 0 -700';
882         self.angles = '0 0 0';
883         self.movetype = MOVETYPE_NONE;
884         self.solid = SOLID_NOT;
885         SetBrushEntityModel();
886         if (!self.cnt)
887                 self.cnt = 12;
888         if (!self.count)
889                 self.count = 2000;
890         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
891         if (self.count < 1)
892                 self.count = 1;
893         if(self.count > 65535)
894                 self.count = 65535;
895
896         self.state = 1; // 1 is rain, 0 is snow
897         self.Version = 1;
898
899         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
900 };
901
902
903 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
904 This is an invisible area like a trigger, which snow falls inside of.
905
906 Keys:
907 "velocity"
908  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
909 "cnt"
910  sets color of rain (default 12 - white)
911 "count"
912  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
913 */
914 void spawnfunc_func_snow()
915 {
916         self.dest = self.velocity;
917         self.velocity = '0 0 0';
918         if (!self.dest)
919                 self.dest = '0 0 -300';
920         self.angles = '0 0 0';
921         self.movetype = MOVETYPE_NONE;
922         self.solid = SOLID_NOT;
923         SetBrushEntityModel();
924         if (!self.cnt)
925                 self.cnt = 12;
926         if (!self.count)
927                 self.count = 2000;
928         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
929         if (self.count < 1)
930                 self.count = 1;
931         if(self.count > 65535)
932                 self.count = 65535;
933
934         self.state = 0; // 1 is rain, 0 is snow
935         self.Version = 1;
936
937         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
938 };
939
940
941 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
942
943 .float modelscale;
944 void misc_laser_aim()
945 {
946         vector a;
947         if(self.enemy)
948         {
949                 if(self.spawnflags & 2)
950                 {
951                         if(self.enemy.origin != self.mangle)
952                         {
953                                 self.mangle = self.enemy.origin;
954                                 self.SendFlags |= 2;
955                         }
956                 }
957                 else
958                 {
959                         a = vectoangles(self.enemy.origin - self.origin);
960                         a_x = -a_x;
961                         if(a != self.mangle)
962                         {
963                                 self.mangle = a;
964                                 self.SendFlags |= 2;
965                         }
966                 }
967         }
968         else
969         {
970                 if(self.angles != self.mangle)
971                 {
972                         self.mangle = self.angles;
973                         self.SendFlags |= 2;
974                 }
975         }
976         if(self.origin != self.oldorigin)
977         {
978                 self.SendFlags |= 1;
979                 self.oldorigin = self.origin;
980         }
981 }
982
983 void misc_laser_init()
984 {
985         if(self.target != "")
986                 self.enemy = find(world, targetname, self.target);
987 }
988
989 .entity pusher;
990 void misc_laser_think()
991 {
992         vector o;
993         entity oldself;
994
995         self.nextthink = time;
996
997         if(!self.state)
998                 return;
999
1000         misc_laser_aim();
1001
1002         if(self.enemy)
1003         {
1004                 o = self.enemy.origin;
1005                 if not(self.spawnflags & 2)
1006                         o = self.origin + normalize(o - self.origin) * 32768;
1007         }
1008         else
1009         {
1010                 makevectors(self.mangle);
1011                 o = self.origin + v_forward * 32768;
1012         }
1013
1014         if(self.dmg)
1015         {
1016                 if(self.dmg < 0)
1017                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
1018                 else
1019                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
1020         }
1021
1022         if(self.enemy.target != "") // DETECTOR laser
1023         {
1024                 traceline(self.origin, o, MOVE_NORMAL, self);
1025                 if(trace_ent.iscreature)
1026                 {
1027                         self.pusher = trace_ent;
1028                         if(!self.count)
1029                         {
1030                                 self.count = 1;
1031
1032                                 oldself = self;
1033                                 self = self.enemy;
1034                                 activator = self.pusher;
1035                                 SUB_UseTargets();
1036                                 self = oldself;
1037                         }
1038                 }
1039                 else
1040                 {
1041                         if(self.count)
1042                         {
1043                                 self.count = 0;
1044
1045                                 oldself = self;
1046                                 self = self.enemy;
1047                                 activator = self.pusher;
1048                                 SUB_UseTargets();
1049                                 self = oldself;
1050                         }
1051                 }
1052         }
1053 }
1054
1055 float laser_SendEntity(entity to, float fl)
1056 {
1057         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1058         fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
1059         if(self.spawnflags & 2)
1060                 fl |= 0x80;
1061         if(self.alpha)
1062                 fl |= 0x40;
1063         if(self.scale != 1 || self.modelscale != 1)
1064                 fl |= 0x20;
1065         WriteByte(MSG_ENTITY, fl);
1066         if(fl & 1)
1067         {
1068                 WriteCoord(MSG_ENTITY, self.origin_x);
1069                 WriteCoord(MSG_ENTITY, self.origin_y);
1070                 WriteCoord(MSG_ENTITY, self.origin_z);
1071         }
1072         if(fl & 8)
1073         {
1074                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1075                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1076                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1077                 if(fl & 0x40)
1078                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
1079                 if(fl & 0x20)
1080                 {
1081                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1082                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1083                 }
1084                 WriteShort(MSG_ENTITY, self.cnt + 1);
1085         }
1086         if(fl & 2)
1087         {
1088                 if(fl & 0x80)
1089                 {
1090                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1091                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1092                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1093                 }
1094                 else
1095                 {
1096                         WriteAngle(MSG_ENTITY, self.mangle_x);
1097                         WriteAngle(MSG_ENTITY, self.mangle_y);
1098                 }
1099         }
1100         if(fl & 4)
1101                 WriteByte(MSG_ENTITY, self.state);
1102         return 1;
1103 }
1104
1105 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1106 Any object touching the beam will be hurt
1107 Keys:
1108 "target"
1109  spawnfunc_target_position where the laser ends
1110 "mdl"
1111  name of beam end effect to use
1112 "colormod"
1113  color of the beam (default: red)
1114 "dmg"
1115  damage per second (-1 for a laser that kills immediately)
1116 */
1117 void laser_use()
1118 {
1119         self.state = !self.state;
1120         self.SendFlags |= 4;
1121         misc_laser_aim();
1122 }
1123
1124 void laser_reset()
1125 {
1126         if(self.spawnflags & 1)
1127                 self.state = 1;
1128         else
1129                 self.state = 0;
1130 }
1131
1132 void spawnfunc_misc_laser()
1133 {
1134         if(self.mdl)
1135         {
1136                 if(self.mdl == "none")
1137                         self.cnt = -1;
1138                 else
1139                 {
1140                         self.cnt = particleeffectnum(self.mdl);
1141                         if(self.cnt < 0)
1142                                 if(self.dmg)
1143                                         self.cnt = particleeffectnum("laser_deadly");
1144                 }
1145         }
1146         else if(!self.cnt)
1147         {
1148                 if(self.dmg)
1149                         self.cnt = particleeffectnum("laser_deadly");
1150                 else
1151                         self.cnt = -1;
1152         }
1153         if(self.cnt < 0)
1154                 self.cnt = -1;
1155
1156         if(self.colormod == '0 0 0')
1157                 if(!self.alpha)
1158                         self.colormod = '1 0 0';
1159         if(!self.message)
1160                 self.message = "saw the light";
1161         if (!self.message2)
1162                 self.message2 = "was pushed into a laser by";
1163         if(!self.scale)
1164                 self.scale = 1;
1165         if(!self.modelscale)
1166                 self.modelscale = 1;
1167         self.think = misc_laser_think;
1168         self.nextthink = time;
1169         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1170
1171         self.mangle = self.angles;
1172
1173         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1174
1175         IFTARGETED
1176         {
1177                 self.reset = laser_reset;
1178                 laser_reset();
1179                 self.use = laser_use;
1180         }
1181         else
1182                 self.state = 1;
1183 }
1184
1185 // tZorks trigger impulse / gravity
1186 .float radius;
1187 .float falloff;
1188 .float strength;
1189 .float lastpushtime;
1190
1191 // targeted (directional) mode
1192 void trigger_impulse_touch1()
1193 {
1194         entity targ;
1195     float pushdeltatime;
1196     float str;
1197
1198         // FIXME: Better checking for what to push and not.
1199         if not(other.iscreature)
1200         if (other.classname != "corpse")
1201         if (other.classname != "body")
1202         if (other.classname != "gib")
1203         if (other.classname != "missile")
1204         if (other.classname != "rocket")
1205         if (other.classname != "casing")
1206         if (other.classname != "grenade")
1207         if (other.classname != "plasma")
1208         if (other.classname != "plasma_prim")
1209         if (other.classname != "plasma_chain")
1210         if (other.classname != "droppedweapon")
1211                 return;
1212
1213         if (other.deadflag && other.iscreature)
1214                 return;
1215
1216         EXACTTRIGGER_TOUCH;
1217
1218     targ = find(world, targetname, self.target);
1219     if(!targ)
1220     {
1221         objerror("trigger_force without a (valid) .target!\n");
1222         remove(self);
1223         return;
1224     }
1225
1226     if(self.falloff == 1)
1227         str = (str / self.radius) * self.strength;
1228     else if(self.falloff == 2)
1229         str = (1 - (str / self.radius)) * self.strength;
1230     else
1231         str = self.strength;
1232
1233     pushdeltatime = time - other.lastpushtime;
1234     if (pushdeltatime > 0.15) pushdeltatime = 0;
1235     other.lastpushtime = time;
1236     if(!pushdeltatime) return;
1237
1238     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1239         other.flags &~= FL_ONGROUND;
1240 }
1241
1242 // Directionless (accelerator/decelerator) mode
1243 void trigger_impulse_touch2()
1244 {
1245     float pushdeltatime;
1246
1247         // FIXME: Better checking for what to push and not.
1248         if not(other.iscreature)
1249         if (other.classname != "corpse")
1250         if (other.classname != "body")
1251         if (other.classname != "gib")
1252         if (other.classname != "missile")
1253         if (other.classname != "rocket")
1254         if (other.classname != "casing")
1255         if (other.classname != "grenade")
1256         if (other.classname != "plasma")
1257         if (other.classname != "plasma_prim")
1258         if (other.classname != "plasma_chain")
1259         if (other.classname != "droppedweapon")
1260                 return;
1261
1262         if (other.deadflag && other.iscreature)
1263                 return;
1264
1265         EXACTTRIGGER_TOUCH;
1266
1267     pushdeltatime = time - other.lastpushtime;
1268     if (pushdeltatime > 0.15) pushdeltatime = 0;
1269     other.lastpushtime = time;
1270     if(!pushdeltatime) return;
1271
1272     //if(self.strength > 1)
1273         other.velocity = other.velocity * (self.strength * pushdeltatime);
1274     //else
1275     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1276 }
1277
1278 // Spherical (gravity/repulsor) mode
1279 void trigger_impulse_touch3()
1280 {
1281     float pushdeltatime;
1282     float str;
1283
1284         // FIXME: Better checking for what to push and not.
1285         if not(other.iscreature)
1286         if (other.classname != "corpse")
1287         if (other.classname != "body")
1288         if (other.classname != "gib")
1289         if (other.classname != "missile")
1290         if (other.classname != "rocket")
1291         if (other.classname != "casing")
1292         if (other.classname != "grenade")
1293         if (other.classname != "plasma")
1294         if (other.classname != "plasma_prim")
1295         if (other.classname != "plasma_chain")
1296         if (other.classname != "droppedweapon")
1297                 return;
1298
1299         if (other.deadflag && other.iscreature)
1300                 return;
1301
1302         EXACTTRIGGER_TOUCH;
1303
1304     pushdeltatime = time - other.lastpushtime;
1305     if (pushdeltatime > 0.15) pushdeltatime = 0;
1306     other.lastpushtime = time;
1307     if(!pushdeltatime) return;
1308
1309     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1310
1311         str = min(self.radius, vlen(self.origin - other.origin));
1312
1313     if(self.falloff == 1)
1314         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1315     else if(self.falloff == 2)
1316         str = (str / self.radius) * self.strength; // 0 in the inside
1317     else
1318         str = self.strength;
1319
1320     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1321 }
1322
1323 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1324 -------- KEYS --------
1325 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1326          If not, this trigger acts like a damper/accelerator field.
1327
1328 strength : This is how mutch force to add in the direction of .target each second
1329            when .target is set. If not, this is hoe mutch to slow down/accelerate
1330            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1331
1332 radius   : If set, act as a spherical device rather then a liniar one.
1333
1334 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1335
1336 -------- NOTES --------
1337 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1338 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1339 */
1340
1341 void spawnfunc_trigger_impulse()
1342 {
1343         EXACTTRIGGER_INIT;
1344     if(self.radius)
1345     {
1346         if(!self.strength) self.strength = 2000;
1347         setorigin(self, self.origin);
1348         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1349         self.touch = trigger_impulse_touch3;
1350     }
1351     else
1352     {
1353         if(self.target)
1354         {
1355             if(!self.strength) self.strength = 950;
1356             self.touch = trigger_impulse_touch1;
1357         }
1358         else
1359         {
1360             if(!self.strength) self.strength = 0.9;
1361             self.touch = trigger_impulse_touch2;
1362         }
1363     }
1364 }
1365
1366 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1367 "Flip-flop" trigger gate... lets only every second trigger event through
1368 */
1369 void flipflop_use()
1370 {
1371         self.state = !self.state;
1372         if(self.state)
1373                 SUB_UseTargets();
1374 }
1375
1376 void spawnfunc_trigger_flipflop()
1377 {
1378         if(self.spawnflags & 1)
1379                 self.state = 1;
1380         self.use = flipflop_use;
1381         self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1382 }
1383
1384 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1385 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1386 */
1387 void monoflop_use()
1388 {
1389         self.nextthink = time + self.wait;
1390         self.enemy = activator;
1391         if(self.state)
1392                 return;
1393         self.state = 1;
1394         SUB_UseTargets();
1395 }
1396 void monoflop_fixed_use()
1397 {
1398         if(self.state)
1399                 return;
1400         self.nextthink = time + self.wait;
1401         self.state = 1;
1402         self.enemy = activator;
1403         SUB_UseTargets();
1404 }
1405
1406 void monoflop_think()
1407 {
1408         self.state = 0;
1409         activator = self.enemy;
1410         SUB_UseTargets();
1411 }
1412
1413 void monoflop_reset()
1414 {
1415         self.state = 0;
1416         self.nextthink = 0;
1417 }
1418
1419 void spawnfunc_trigger_monoflop()
1420 {
1421         if(!self.wait)
1422                 self.wait = 1;
1423         if(self.spawnflags & 1)
1424                 self.use = monoflop_fixed_use;
1425         else
1426                 self.use = monoflop_use;
1427         self.think = monoflop_think;
1428         self.state = 0;
1429         self.reset = monoflop_reset;
1430 }
1431
1432 void multivibrator_send()
1433 {
1434         float newstate;
1435         float cyclestart;
1436
1437         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1438
1439         newstate = (time < cyclestart + self.wait);
1440
1441         activator = self;
1442         if(self.state != newstate)
1443                 SUB_UseTargets();
1444         self.state = newstate;
1445
1446         if(self.state)
1447                 self.nextthink = cyclestart + self.wait + 0.01;
1448         else
1449                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1450 }
1451
1452 void multivibrator_toggle()
1453 {
1454         if(self.nextthink == 0)
1455         {
1456                 multivibrator_send();
1457         }
1458         else
1459         {
1460                 if(self.state)
1461                 {
1462                         SUB_UseTargets();
1463                         self.state = 0;
1464                 }
1465                 self.nextthink = 0;
1466         }
1467 }
1468
1469 void multivibrator_reset()
1470 {
1471         if(!(self.spawnflags & 1))
1472                 self.nextthink = 0; // wait for a trigger event
1473         else
1474                 self.nextthink = max(1, time);
1475 }
1476
1477 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1478 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1479 -------- KEYS --------
1480 target: trigger all entities with this targetname when it goes off
1481 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1482 phase: offset of the timing
1483 wait: "on" cycle time (default: 1)
1484 respawntime: "off" cycle time (default: same as wait)
1485 -------- SPAWNFLAGS --------
1486 START_ON: assume it is already turned on (when targeted)
1487 */
1488 void spawnfunc_trigger_multivibrator()
1489 {
1490         if(!self.wait)
1491                 self.wait = 1;
1492         if(!self.respawntime)
1493                 self.respawntime = self.wait;
1494
1495         self.state = 0;
1496         self.use = multivibrator_toggle;
1497         self.think = multivibrator_send;
1498         self.nextthink = time;
1499
1500         IFTARGETED
1501                 multivibrator_reset();
1502 }
1503
1504
1505 void follow_init()
1506 {
1507         entity src, dst;
1508         src = find(world, targetname, self.killtarget);
1509         dst = find(world, targetname, self.target);
1510
1511         if(!src || !dst)
1512         {
1513                 objerror("follow: could not find target/killtarget");
1514                 return;
1515         }
1516
1517         if(self.spawnflags & 1)
1518         {
1519                 // attach
1520                 if(self.spawnflags & 2)
1521                 {
1522                         setattachment(dst, src, self.message);
1523                 }
1524                 else
1525                 {
1526                         attach_sameorigin(dst, src, self.message);
1527                 }
1528         }
1529         else
1530         {
1531                 if(self.spawnflags & 2)
1532                 {
1533                         dst.movetype = MOVETYPE_FOLLOW;
1534                         dst.aiment = src;
1535                         // dst.punchangle = '0 0 0'; // keep unchanged
1536                         dst.view_ofs = dst.origin;
1537                         dst.v_angle = dst.angles;
1538                 }
1539                 else
1540                 {
1541                         follow_sameorigin(dst, src);
1542                 }
1543         }
1544
1545         remove(self);
1546 }
1547
1548 void spawnfunc_misc_follow()
1549 {
1550         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1551 }
1552
1553
1554
1555 void gamestart_use() {
1556         activator = self;
1557         SUB_UseTargets();
1558         remove(self);
1559 }
1560
1561 void spawnfunc_trigger_gamestart() {
1562         self.use = gamestart_use;
1563         self.reset2 = spawnfunc_trigger_gamestart;
1564
1565         if(self.wait)
1566         {
1567                 self.think = self.use;
1568                 self.nextthink = game_starttime + self.wait;
1569         }
1570         else
1571                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1572 }
1573
1574
1575
1576
1577 .entity voicescript; // attached voice script
1578 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1579 .float voicescript_nextthink; // time to play next voice
1580 .float voicescript_voiceend; // time when this voice ends
1581
1582 void target_voicescript_clear(entity pl)
1583 {
1584         pl.voicescript = world;
1585 }
1586
1587 void target_voicescript_use()
1588 {
1589         if(activator.voicescript != self)
1590         {
1591                 activator.voicescript = self;
1592                 activator.voicescript_index = 0;
1593                 activator.voicescript_nextthink = time + self.delay;
1594         }
1595 }
1596
1597 void target_voicescript_next(entity pl)
1598 {
1599         entity vs;
1600         float i, n;
1601
1602         vs = pl.voicescript;
1603         if(!vs)
1604                 return;
1605         if(vs.message == "")
1606                 return;
1607         if(pl.classname != "player")
1608                 return;
1609         if(gameover)
1610                 return;
1611
1612         if(time >= pl.voicescript_voiceend)
1613         {
1614                 if(time >= pl.voicescript_nextthink)
1615                 {
1616                         // get the next voice...
1617                         n = tokenize_sane(vs.message);
1618
1619                         if(pl.voicescript_index < vs.cnt)
1620                                 i = pl.voicescript_index * 2;
1621                         else if(n > vs.cnt * 2)
1622                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1623                         else
1624                                 i = -1;
1625
1626                         if(i >= 0)
1627                         {
1628                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1629                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1630                         }
1631                         else
1632                                 pl.voicescript = world;
1633
1634                         pl.voicescript_index += 1;
1635                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1636                 }
1637         }
1638 }
1639
1640 void spawnfunc_target_voicescript()
1641 {
1642         // netname: directory of the sound files
1643         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1644         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1645         // wait: average time between messages
1646         // delay: initial delay before the first message
1647         
1648         float i, n;
1649         self.use = target_voicescript_use;
1650
1651         n = tokenize_sane(self.message);
1652         self.cnt = n / 2;
1653         for(i = 0; i+1 < n; i += 2)
1654         {
1655                 if(argv(i) == "*")
1656                 {
1657                         self.cnt = i / 2;
1658                         ++i;
1659                 }
1660                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1661         }
1662 }
1663
1664
1665
1666 void trigger_relay_teamcheck_use()
1667 {
1668         if(activator.team)
1669         {
1670                 if(self.spawnflags & 2)
1671                 {
1672                         if(activator.team != self.team)
1673                                 SUB_UseTargets();
1674                 }
1675                 else
1676                 {
1677                         if(activator.team == self.team)
1678                                 SUB_UseTargets();
1679                 }
1680         }
1681         else
1682         {
1683                 if(self.spawnflags & 1)
1684                         SUB_UseTargets();
1685         }
1686 }
1687
1688 void trigger_relay_teamcheck_reset()
1689 {
1690         self.team = self.team_saved;
1691 }
1692
1693 void spawnfunc_trigger_relay_teamcheck()
1694 {
1695         self.team_saved = self.team;
1696         self.use = trigger_relay_teamcheck_use;
1697         self.reset = trigger_relay_teamcheck_reset;
1698 }
1699
1700
1701
1702 void trigger_disablerelay_use()
1703 {
1704         entity e;
1705
1706         float a, b;
1707         a = b = 0;
1708
1709         for(e = world; (e = find(e, targetname, self.target)); )
1710         {
1711                 if(e.use == SUB_UseTargets)
1712                 {
1713                         e.use = SUB_DontUseTargets;
1714                         ++a;
1715                 }
1716                 else if(e.use == SUB_DontUseTargets)
1717                 {
1718                         e.use = SUB_UseTargets;
1719                         ++b;
1720                 }
1721         }
1722
1723         if(!a == !b)
1724                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1725 }
1726
1727 void spawnfunc_trigger_disablerelay()
1728 {
1729         self.use = trigger_disablerelay_use;
1730 }