]> de.git.xonotic.org Git - voretournament/voretournament.git/blob - data/qcsrc/server/g_triggers.qc
Remove player model leaning from the code. It's too buggy, problematic, and not done...
[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         // apply size-based weight\r
1140         str *= (cvar("g_healthsize") && other.classname == "player") ? pow(other.scale, cvar("g_healthsize_weight") * 0.5) : 1;\r
1141 \r
1142     other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;\r
1143         other.flags &~= FL_ONGROUND;\r
1144 }\r
1145 \r
1146 // Directionless (accelerator/decelerator) mode\r
1147 void trigger_impulse_touch2()\r
1148 {\r
1149     float pushdeltatime;\r
1150         float str;\r
1151 \r
1152         // FIXME: Better checking for what to push and not.\r
1153         if not(other.iscreature)\r
1154         if (other.classname != "corpse")\r
1155         if (other.classname != "body")\r
1156         if (other.classname != "gib")\r
1157         if (other.classname != "missile")\r
1158         if (other.classname != "rocket")\r
1159         if (other.classname != "casing")\r
1160         if (other.classname != "grenade")\r
1161         if (other.classname != "plasma")\r
1162         if (other.classname != "plasma_prim")\r
1163         if (other.classname != "plasma_chain")\r
1164         if (other.classname != "droppedweapon")\r
1165                 return;\r
1166 \r
1167         if (other.deadflag && other.iscreature)\r
1168                 return;\r
1169 \r
1170         EXACTTRIGGER_TOUCH;\r
1171 \r
1172     pushdeltatime = time - other.lastpushtime;\r
1173     if (pushdeltatime > 0.15) pushdeltatime = 0;\r
1174     other.lastpushtime = time;\r
1175     if(!pushdeltatime) return;\r
1176 \r
1177         str = pow(self.strength, pushdeltatime);\r
1178 \r
1179         // apply size-based weight\r
1180         str *= (cvar("g_healthsize") && other.classname == "player") ? pow(other.scale, cvar("g_healthsize_weight") * 0.5) : 1;\r
1181 \r
1182     // div0: ticrate independent, 1 = identity (not 20)\r
1183     other.velocity = other.velocity * str;\r
1184 }\r
1185 \r
1186 // Spherical (gravity/repulsor) mode\r
1187 void trigger_impulse_touch3()\r
1188 {\r
1189     float pushdeltatime;\r
1190     float str;\r
1191 \r
1192         // FIXME: Better checking for what to push and not.\r
1193         if not(other.iscreature)\r
1194         if (other.classname != "corpse")\r
1195         if (other.classname != "body")\r
1196         if (other.classname != "gib")\r
1197         if (other.classname != "missile")\r
1198         if (other.classname != "rocket")\r
1199         if (other.classname != "casing")\r
1200         if (other.classname != "grenade")\r
1201         if (other.classname != "plasma")\r
1202         if (other.classname != "plasma_prim")\r
1203         if (other.classname != "plasma_chain")\r
1204         if (other.classname != "droppedweapon")\r
1205                 return;\r
1206 \r
1207         if (other.deadflag && other.iscreature)\r
1208                 return;\r
1209 \r
1210         EXACTTRIGGER_TOUCH;\r
1211 \r
1212     pushdeltatime = time - other.lastpushtime;\r
1213     if (pushdeltatime > 0.15) pushdeltatime = 0;\r
1214     other.lastpushtime = time;\r
1215     if(!pushdeltatime) return;\r
1216 \r
1217     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);\r
1218 \r
1219         str = min(self.radius, vlen(self.origin - other.origin));\r
1220 \r
1221     if(self.falloff == 1)\r
1222         str = (1 - str / self.radius) * self.strength; // 1 in the inside\r
1223     else if(self.falloff == 2)\r
1224         str = (str / self.radius) * self.strength; // 0 in the inside\r
1225     else\r
1226         str = self.strength;\r
1227 \r
1228         // apply size-based weight\r
1229         str *= (cvar("g_healthsize") && other.classname == "player") ? pow(other.scale, cvar("g_healthsize_weight") * 0.5) : 1;\r
1230 \r
1231     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;\r
1232 }\r
1233 \r
1234 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?\r
1235 -------- KEYS --------\r
1236 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.\r
1237          If not, this trigger acts like a damper/accelerator field.\r
1238 \r
1239 strength : This is how mutch force to add in the direction of .target each second\r
1240            when .target is set. If not, this is hoe mutch to slow down/accelerate\r
1241            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)\r
1242 \r
1243 radius   : If set, act as a spherical device rather then a liniar one.\r
1244 \r
1245 falloff : 0 = none, 1 = liniar, 2 = inverted liniar\r
1246 \r
1247 -------- NOTES --------\r
1248 Use a brush textured with common/origin in the trigger entity to determine the origin of the force\r
1249 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).\r
1250 */\r
1251 \r
1252 void spawnfunc_trigger_impulse()\r
1253 {\r
1254         EXACTTRIGGER_INIT;\r
1255     if(self.radius)\r
1256     {\r
1257         if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");\r
1258         setorigin(self, self.origin);\r
1259         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);\r
1260         self.touch = trigger_impulse_touch3;\r
1261     }\r
1262     else\r
1263     {\r
1264         if(self.target)\r
1265         {\r
1266             if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");\r
1267             self.touch = trigger_impulse_touch1;\r
1268         }\r
1269         else\r
1270         {\r
1271             if(!self.strength) self.strength = 0.9;\r
1272                         self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");\r
1273             self.touch = trigger_impulse_touch2;\r
1274         }\r
1275     }\r
1276 }\r
1277 \r
1278 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED\r
1279 "Flip-flop" trigger gate... lets only every second trigger event through\r
1280 */\r
1281 void flipflop_use()\r
1282 {\r
1283         self.state = !self.state;\r
1284         if(self.state)\r
1285                 SUB_UseTargets();\r
1286 }\r
1287 \r
1288 void spawnfunc_trigger_flipflop()\r
1289 {\r
1290         if(self.spawnflags & 1)\r
1291                 self.state = 1;\r
1292         self.use = flipflop_use;\r
1293         self.reset = spawnfunc_trigger_flipflop; // perfect resetter\r
1294 }\r
1295 \r
1296 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)\r
1297 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"\r
1298 */\r
1299 void monoflop_use()\r
1300 {\r
1301         self.nextthink = time + self.wait;\r
1302         self.enemy = activator;\r
1303         if(self.state)\r
1304                 return;\r
1305         self.state = 1;\r
1306         SUB_UseTargets();\r
1307 }\r
1308 void monoflop_fixed_use()\r
1309 {\r
1310         if(self.state)\r
1311                 return;\r
1312         self.nextthink = time + self.wait;\r
1313         self.state = 1;\r
1314         self.enemy = activator;\r
1315         SUB_UseTargets();\r
1316 }\r
1317 \r
1318 void monoflop_think()\r
1319 {\r
1320         self.state = 0;\r
1321         activator = self.enemy;\r
1322         SUB_UseTargets();\r
1323 }\r
1324 \r
1325 void monoflop_reset()\r
1326 {\r
1327         self.state = 0;\r
1328         self.nextthink = 0;\r
1329 }\r
1330 \r
1331 void spawnfunc_trigger_monoflop()\r
1332 {\r
1333         if(!self.wait)\r
1334                 self.wait = 1;\r
1335         if(self.spawnflags & 1)\r
1336                 self.use = monoflop_fixed_use;\r
1337         else\r
1338                 self.use = monoflop_use;\r
1339         self.think = monoflop_think;\r
1340         self.state = 0;\r
1341         self.reset = monoflop_reset;\r
1342 }\r
1343 \r
1344 void multivibrator_send()\r
1345 {\r
1346         float newstate;\r
1347         float cyclestart;\r
1348 \r
1349         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;\r
1350 \r
1351         newstate = (time < cyclestart + self.wait);\r
1352 \r
1353         activator = self;\r
1354         if(self.state != newstate)\r
1355                 SUB_UseTargets();\r
1356         self.state = newstate;\r
1357 \r
1358         if(self.state)\r
1359                 self.nextthink = cyclestart + self.wait + 0.01;\r
1360         else\r
1361                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;\r
1362 }\r
1363 \r
1364 void multivibrator_toggle()\r
1365 {\r
1366         if(self.nextthink == 0)\r
1367         {\r
1368                 multivibrator_send();\r
1369         }\r
1370         else\r
1371         {\r
1372                 if(self.state)\r
1373                 {\r
1374                         SUB_UseTargets();\r
1375                         self.state = 0;\r
1376                 }\r
1377                 self.nextthink = 0;\r
1378         }\r
1379 }\r
1380 \r
1381 void multivibrator_reset()\r
1382 {\r
1383         if(!(self.spawnflags & 1))\r
1384                 self.nextthink = 0; // wait for a trigger event\r
1385         else\r
1386                 self.nextthink = max(1, time);\r
1387 }\r
1388 \r
1389 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON\r
1390 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.\r
1391 -------- KEYS --------\r
1392 target: trigger all entities with this targetname when it goes off\r
1393 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state\r
1394 phase: offset of the timing\r
1395 wait: "on" cycle time (default: 1)\r
1396 respawntime: "off" cycle time (default: same as wait)\r
1397 -------- SPAWNFLAGS --------\r
1398 START_ON: assume it is already turned on (when targeted)\r
1399 */\r
1400 void spawnfunc_trigger_multivibrator()\r
1401 {\r
1402         if(!self.wait)\r
1403                 self.wait = 1;\r
1404         if(!self.respawntime)\r
1405                 self.respawntime = self.wait;\r
1406 \r
1407         self.state = 0;\r
1408         self.use = multivibrator_toggle;\r
1409         self.think = multivibrator_send;\r
1410         self.nextthink = time;\r
1411 \r
1412         IFTARGETED\r
1413                 multivibrator_reset();\r
1414 }\r
1415 \r
1416 \r
1417 void follow_init()\r
1418 {\r
1419         entity src, dst;\r
1420         src = world;\r
1421         dst = world;\r
1422         if(self.killtarget != "")\r
1423                 src = find(world, targetname, self.killtarget);\r
1424         if(self.target != "")\r
1425                 dst = find(world, targetname, self.target);\r
1426 \r
1427         if(!src && !dst)\r
1428         {\r
1429                 objerror("follow: could not find target/killtarget");\r
1430                 return;\r
1431         }\r
1432 \r
1433         if(self.jointtype)\r
1434         {\r
1435                 // already done :P entity must stay\r
1436                 self.aiment = src;\r
1437                 self.enemy = dst;\r
1438         }\r
1439         else if(!src || !dst)\r
1440         {\r
1441                 objerror("follow: could not find target/killtarget");\r
1442                 return;\r
1443         }\r
1444         else if(self.spawnflags & 1)\r
1445         {\r
1446                 // attach\r
1447                 if(self.spawnflags & 2)\r
1448                 {\r
1449                         setattachment(dst, src, self.message);\r
1450                 }\r
1451                 else\r
1452                 {\r
1453                         attach_sameorigin(dst, src, self.message);\r
1454                 }\r
1455 \r
1456                 remove(self);\r
1457         }\r
1458         else\r
1459         {\r
1460                 if(self.spawnflags & 2)\r
1461                 {\r
1462                         dst.movetype = MOVETYPE_FOLLOW;\r
1463                         dst.aiment = src;\r
1464                         // dst.punchangle = '0 0 0'; // keep unchanged\r
1465                         dst.view_ofs = dst.origin;\r
1466                         dst.v_angle = dst.angles;\r
1467                 }\r
1468                 else\r
1469                 {\r
1470                         follow_sameorigin(dst, src);\r
1471                 }\r
1472 \r
1473                 remove(self);\r
1474         }\r
1475 }\r
1476 \r
1477 void spawnfunc_misc_follow()\r
1478 {\r
1479         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);\r
1480 }\r
1481 \r
1482 \r
1483 \r
1484 void gamestart_use() {\r
1485         activator = self;\r
1486         SUB_UseTargets();\r
1487         remove(self);\r
1488 }\r
1489 \r
1490 void spawnfunc_trigger_gamestart() {\r
1491         self.use = gamestart_use;\r
1492         self.reset2 = spawnfunc_trigger_gamestart;\r
1493 \r
1494         if(self.wait)\r
1495         {\r
1496                 self.think = self.use;\r
1497                 self.nextthink = game_starttime + self.wait;\r
1498         }\r
1499         else\r
1500                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);\r
1501 }\r
1502 \r
1503 \r
1504 \r
1505 \r
1506 .entity voicescript; // attached voice script\r
1507 .float voicescript_index; // index of next voice, or -1 to use the randomized ones\r
1508 .float voicescript_nextthink; // time to play next voice\r
1509 .float voicescript_voiceend; // time when this voice ends\r
1510 \r
1511 void target_voicescript_clear(entity pl)\r
1512 {\r
1513         pl.voicescript = world;\r
1514 }\r
1515 \r
1516 void target_voicescript_use()\r
1517 {\r
1518         if(activator.voicescript != self)\r
1519         {\r
1520                 activator.voicescript = self;\r
1521                 activator.voicescript_index = 0;\r
1522                 activator.voicescript_nextthink = time + self.delay;\r
1523         }\r
1524 }\r
1525 \r
1526 void target_voicescript_next(entity pl)\r
1527 {\r
1528         entity vs;\r
1529         float i, n, dt;\r
1530 \r
1531         vs = pl.voicescript;\r
1532         if(!vs)\r
1533                 return;\r
1534         if(vs.message == "")\r
1535                 return;\r
1536         if(pl.classname != "player")\r
1537                 return;\r
1538         if(gameover)\r
1539                 return;\r
1540 \r
1541         if(time >= pl.voicescript_voiceend)\r
1542         {\r
1543                 if(time >= pl.voicescript_nextthink)\r
1544                 {\r
1545                         // get the next voice...\r
1546                         n = tokenize_console(vs.message);\r
1547 \r
1548                         if(pl.voicescript_index < vs.cnt)\r
1549                                 i = pl.voicescript_index * 2;\r
1550                         else if(n > vs.cnt * 2)\r
1551                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;\r
1552                         else\r
1553                                 i = -1;\r
1554 \r
1555                         if(i >= 0)\r
1556                         {\r
1557                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));\r
1558                                 dt = stof(argv(i + 1));\r
1559                                 if(dt >= 0)\r
1560                                 {\r
1561                                         pl.voicescript_voiceend = time + dt;\r
1562                                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());\r
1563                                 }\r
1564                                 else\r
1565                                 {\r
1566                                         pl.voicescript_voiceend = time - dt;\r
1567                                         pl.voicescript_nextthink = pl.voicescript_voiceend;\r
1568                                 }\r
1569 \r
1570                                 pl.voicescript_index += 1;\r
1571                         }\r
1572                         else\r
1573                         {\r
1574                                 pl.voicescript = world; // stop trying then\r
1575                         }\r
1576                 }\r
1577         }\r
1578 }\r
1579 \r
1580 void spawnfunc_target_voicescript()\r
1581 {\r
1582         // netname: directory of the sound files\r
1583         // message: list of "sound file" duration "sound file" duration, a *, and again a list\r
1584         //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7\r
1585         //          Here, a - in front of the duration means that no delay is to be\r
1586         //          added after this message\r
1587         // wait: average time between messages\r
1588         // delay: initial delay before the first message\r
1589         \r
1590         float i, n;\r
1591         self.use = target_voicescript_use;\r
1592 \r
1593         n = tokenize_console(self.message);\r
1594         self.cnt = n / 2;\r
1595         for(i = 0; i+1 < n; i += 2)\r
1596         {\r
1597                 if(argv(i) == "*")\r
1598                 {\r
1599                         self.cnt = i / 2;\r
1600                         ++i;\r
1601                 }\r
1602                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));\r
1603         }\r
1604 }\r
1605 \r
1606 \r
1607 \r
1608 void trigger_relay_teamcheck_use()\r
1609 {\r
1610         if(activator.team)\r
1611         {\r
1612                 if(self.spawnflags & 2)\r
1613                 {\r
1614                         if(activator.team != self.team)\r
1615                                 SUB_UseTargets();\r
1616                 }\r
1617                 else\r
1618                 {\r
1619                         if(activator.team == self.team)\r
1620                                 SUB_UseTargets();\r
1621                 }\r
1622         }\r
1623         else\r
1624         {\r
1625                 if(self.spawnflags & 1)\r
1626                         SUB_UseTargets();\r
1627         }\r
1628 }\r
1629 \r
1630 void trigger_relay_teamcheck_reset()\r
1631 {\r
1632         self.team = self.team_saved;\r
1633 }\r
1634 \r
1635 void spawnfunc_trigger_relay_teamcheck()\r
1636 {\r
1637         self.team_saved = self.team;\r
1638         self.use = trigger_relay_teamcheck_use;\r
1639         self.reset = trigger_relay_teamcheck_reset;\r
1640 }\r
1641 \r
1642 \r
1643 \r
1644 void trigger_disablerelay_use()\r
1645 {\r
1646         entity e;\r
1647 \r
1648         float a, b;\r
1649         a = b = 0;\r
1650 \r
1651         for(e = world; (e = find(e, targetname, self.target)); )\r
1652         {\r
1653                 if(e.use == SUB_UseTargets)\r
1654                 {\r
1655                         e.use = SUB_DontUseTargets;\r
1656                         ++a;\r
1657                 }\r
1658                 else if(e.use == SUB_DontUseTargets)\r
1659                 {\r
1660                         e.use = SUB_UseTargets;\r
1661                         ++b;\r
1662                 }\r
1663         }\r
1664 \r
1665         if((!a) == (!b))\r
1666                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");\r
1667 }\r
1668 \r
1669 void spawnfunc_trigger_disablerelay()\r
1670 {\r
1671         self.use = trigger_disablerelay_use;\r
1672 }\r
1673 \r
1674 float magicear_matched;\r
1675 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)\r
1676 {\r
1677         float domatch, dotrigger, matchstart, l;\r
1678         string s, msg;\r
1679         entity oldself;\r
1680 \r
1681         magicear_matched = FALSE;\r
1682 \r
1683         dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));\r
1684         domatch = ((ear.spawnflags & 32) || dotrigger);\r
1685         if not(domatch)\r
1686                 return msgin;\r
1687 \r
1688         if(privatesay)\r
1689         {\r
1690                 if(ear.spawnflags & 4)\r
1691                         return msgin;\r
1692         }\r
1693         else\r
1694         {\r
1695                 if(!teamsay)\r
1696                         if(ear.spawnflags & 1)\r
1697                                 return msgin;\r
1698                 if(teamsay > 0)\r
1699                         if(ear.spawnflags & 2)\r
1700                                 return msgin;\r
1701                 if(teamsay < 0)\r
1702                         if(ear.spawnflags & 8)\r
1703                                 return msgin;\r
1704         }\r
1705         \r
1706         matchstart = -1;\r
1707         l = strlen(ear.message);\r
1708 \r
1709         if(self.spawnflags & 128)\r
1710                 msg = msgin;\r
1711         else\r
1712                 msg = strdecolorize(msgin);\r
1713 \r
1714         if(substring(ear.message, 0, 1) == "*")\r
1715         {\r
1716                 if(substring(ear.message, -1, 1) == "*")\r
1717                 {\r
1718                         // two wildcards\r
1719                         // as we need multi-replacement here...\r
1720                         s = substring(ear.message, 1, -2);\r
1721                         l -= 2;\r
1722                         if(strstrofs(msg, s, 0) >= 0)\r
1723                                 matchstart = -2; // we use strreplace on s\r
1724                 }\r
1725                 else\r
1726                 {\r
1727                         // match at start\r
1728                         s = substring(ear.message, 1, -1);\r
1729                         l -= 1;\r
1730                         if(substring(msg, -l, l) == s)\r
1731                                 matchstart = strlen(msg) - l;\r
1732                 }\r
1733         }\r
1734         else\r
1735         {\r
1736                 if(substring(ear.message, -1, 1) == "*")\r
1737                 {\r
1738                         // match at end\r
1739                         s = substring(ear.message, 0, -2);\r
1740                         l -= 1;\r
1741                         if(substring(msg, 0, l) == s)\r
1742                                 matchstart = 0;\r
1743                 }\r
1744                 else\r
1745                 {\r
1746                         // full match\r
1747                         s = ear.message;\r
1748                         if(msg == ear.message)\r
1749                                 matchstart = 0;\r
1750                 }\r
1751         }\r
1752 \r
1753         if(matchstart == -1) // no match\r
1754                 return msgin;\r
1755 \r
1756         magicear_matched = TRUE;\r
1757 \r
1758         if(dotrigger)\r
1759         {\r
1760                 oldself = activator = self;\r
1761                 self = ear;\r
1762                 SUB_UseTargets();\r
1763                 self = oldself;\r
1764         }\r
1765 \r
1766         if(ear.spawnflags & 16)\r
1767         {\r
1768                 return ear.netname;\r
1769         }\r
1770         else if(ear.netname != "")\r
1771         {\r
1772                 if(matchstart < 0)\r
1773                         return strreplace(s, ear.netname, msg);\r
1774                 else\r
1775                         return strcat(\r
1776                                 substring(msg, 0, matchstart),\r
1777                                 ear.netname,\r
1778                                 substring(msg, matchstart + l, -1)\r
1779                         );\r
1780         }\r
1781         else\r
1782                 return msgin;\r
1783 }\r
1784 \r
1785 entity magicears;\r
1786 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)\r
1787 {\r
1788         entity ear;\r
1789         string msgout;\r
1790         for(ear = magicears; ear; ear = ear.enemy)\r
1791         {\r
1792                 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);\r
1793                 if not(ear.spawnflags & 64)\r
1794                         if(magicear_matched)\r
1795                                 return msgout;\r
1796                 msgin = msgout;\r
1797         }\r
1798         return msgin;\r
1799 }\r
1800 \r
1801 void spawnfunc_trigger_magicear()\r
1802 {\r
1803         self.enemy = magicears;\r
1804         magicears = self;\r
1805 \r
1806         // actually handled in "say" processing\r
1807         // spawnflags:\r
1808         //   1 = ignore say\r
1809         //   2 = ignore teamsay\r
1810         //   4 = ignore tell\r
1811         //   8 = ignore tell to unknown player\r
1812         //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)\r
1813         //   32 = perform the replacement even if outside the radius or dead\r
1814         //   64 = continue replacing/triggering even if this one matched\r
1815         // message: either\r
1816         //   *pattern*\r
1817         // or\r
1818         //   *pattern\r
1819         // or\r
1820         //   pattern*\r
1821         // or\r
1822         //   pattern\r
1823         // netname:\r
1824         //   if set, replacement for the matched text\r
1825         // radius:\r
1826         //   "hearing distance"\r
1827         // target:\r
1828         //   what to trigger\r
1829 }\r
1830 \r
1831 .string chmap, gametype;\r
1832 void spawnfunc_target_changelevel_use()\r
1833 {\r
1834         if(self.gametype != "")\r
1835                 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));\r
1836 \r
1837         if (self.chmap == "")\r
1838                 localcmd("endmatch\n");\r
1839         else\r
1840                 localcmd(strcat("changelevel ", self.chmap, "\n"));\r
1841 };\r
1842 \r
1843 void spawnfunc_target_changelevel()\r
1844 {\r
1845         self.use = spawnfunc_target_changelevel_use;\r
1846 };\r