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