Fix a major and very rare issue, that would cause killtarget-ing a func_bobbing to...
[voretournament/voretournament.git] / data / qcsrc / server / t_plats.qc
1 .float dmgtime2;\r
2 void generic_plat_blocked()\r
3 {\r
4     if(self.dmg && other.takedamage != DAMAGE_NO) {\r
5         if(self.dmgtime2 < time) {\r
6             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
7             self.dmgtime2 = time + self.dmgtime;\r
8         }\r
9 \r
10         // Gib dead/dying stuff\r
11         if(other.deadflag != DEAD_NO)\r
12             Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
13     }\r
14 }\r
15 \r
16 \r
17 float   STATE_TOP               = 0;\r
18 float   STATE_BOTTOM    = 1;\r
19 float   STATE_UP                = 2;\r
20 float   STATE_DOWN              = 3;\r
21 \r
22 .entity trigger_field;\r
23 \r
24 void() plat_center_touch;\r
25 void() plat_outside_touch;\r
26 void() plat_trigger_use;\r
27 void() plat_go_up;\r
28 void() plat_go_down;\r
29 void() plat_crush;\r
30 float PLAT_LOW_TRIGGER = 1;\r
31 \r
32 void plat_spawn_inside_trigger()\r
33 {\r
34         local entity trigger;\r
35         local vector tmin, tmax;\r
36 \r
37         trigger = spawn();\r
38         trigger.touch = plat_center_touch;\r
39         trigger.movetype = MOVETYPE_NONE;\r
40         trigger.solid = SOLID_TRIGGER;\r
41         trigger.enemy = self;\r
42 \r
43         tmin = self.absmin + '25 25 0';\r
44         tmax = self.absmax - '25 25 -8';\r
45         tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);\r
46         if (self.spawnflags & PLAT_LOW_TRIGGER)\r
47                 tmax_z = tmin_z + 8;\r
48 \r
49         if (self.size_x <= 50)\r
50         {\r
51                 tmin_x = (self.mins_x + self.maxs_x) / 2;\r
52                 tmax_x = tmin_x + 1;\r
53         }\r
54         if (self.size_y <= 50)\r
55         {\r
56                 tmin_y = (self.mins_y + self.maxs_y) / 2;\r
57                 tmax_y = tmin_y + 1;\r
58         }\r
59 \r
60         setsize (trigger, tmin, tmax);\r
61 };\r
62 \r
63 void plat_hit_top()\r
64 {\r
65         sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);\r
66         self.state = 1;\r
67         self.think = plat_go_down;\r
68         self.nextthink = self.ltime + 3;\r
69 };\r
70 \r
71 void plat_hit_bottom()\r
72 {\r
73         sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);\r
74         self.state = 2;\r
75 };\r
76 \r
77 void plat_go_down()\r
78 {\r
79         sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);\r
80         self.state = 3;\r
81         SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);\r
82 };\r
83 \r
84 void plat_go_up()\r
85 {\r
86         sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);\r
87         self.state = 4;\r
88         SUB_CalcMove (self.pos1, self.speed, plat_hit_top);\r
89 };\r
90 \r
91 void plat_center_touch()\r
92 {\r
93         if not(other.iscreature)\r
94                 return;\r
95 \r
96         if (other.health <= 0)\r
97                 return;\r
98 \r
99         self = self.enemy;\r
100         if (self.state == 2)\r
101                 plat_go_up ();\r
102         else if (self.state == 1)\r
103                 self.nextthink = self.ltime + 1;        // delay going down\r
104 };\r
105 \r
106 void plat_outside_touch()\r
107 {\r
108         if not(other.iscreature)\r
109                 return;\r
110 \r
111         if (other.health <= 0)\r
112                 return;\r
113 \r
114         self = self.enemy;\r
115         if (self.state == 1)\r
116                 plat_go_down ();\r
117 };\r
118 \r
119 void plat_trigger_use()\r
120 {\r
121         if (self.think)\r
122                 return;         // already activated\r
123         plat_go_down();\r
124 };\r
125 \r
126 \r
127 void plat_crush()\r
128 {\r
129     if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!\r
130         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
131     } else {\r
132         if((self.dmg) && (other.takedamage != DAMAGE_NO)) {   // Shall we bite?\r
133             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
134             // Gib dead/dying stuff\r
135             if(other.deadflag != DEAD_NO)\r
136                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
137         }\r
138 \r
139         if (self.state == 4)\r
140             plat_go_down ();\r
141         else if (self.state == 3)\r
142             plat_go_up ();\r
143         else\r
144             objerror ("plat_crush: bad self.state\n");\r
145     }\r
146 };\r
147 \r
148 void plat_use()\r
149 {\r
150         self.use = SUB_Null;\r
151         if (self.state != 4)\r
152                 objerror ("plat_use: not in up state");\r
153         plat_go_down();\r
154 };\r
155 \r
156 .string sound1, sound2;\r
157 \r
158 void plat_reset()\r
159 {\r
160         IFTARGETED\r
161         {\r
162                 setorigin (self, self.pos1);\r
163                 self.state = 4;\r
164                 self.use = plat_use;\r
165         }\r
166         else\r
167         {\r
168                 setorigin (self, self.pos2);\r
169                 self.state = 2;\r
170                 self.use = plat_trigger_use;\r
171         }\r
172 }\r
173 \r
174 void spawnfunc_path_corner() { };\r
175 void spawnfunc_func_plat()\r
176 {\r
177         if (!self.t_length)\r
178                 self.t_length = 80;\r
179         if (!self.t_width)\r
180                 self.t_width = 10;\r
181 \r
182         if (self.sounds == 0)\r
183                 self.sounds = 2;\r
184 \r
185     if(self.spawnflags & 4)\r
186         self.dmg = 10000;\r
187 \r
188     if(self.dmg && (!self.message))\r
189                 self.message = "was squished";\r
190     if(self.dmg && (!self.message2))\r
191                 self.message2 = "was squished by";\r
192 \r
193         if (self.sounds == 1)\r
194         {\r
195                 precache_sound ("plats/plat1.wav");\r
196                 precache_sound ("plats/plat2.wav");\r
197                 self.noise = "plats/plat1.wav";\r
198                 self.noise1 = "plats/plat2.wav";\r
199         }\r
200 \r
201         if (self.sounds == 2)\r
202         {\r
203                 precache_sound ("plats/medplat1.wav");\r
204                 precache_sound ("plats/medplat2.wav");\r
205                 self.noise = "plats/medplat1.wav";\r
206                 self.noise1 = "plats/medplat2.wav";\r
207         }\r
208 \r
209         if (self.sound1)\r
210         {\r
211                 precache_sound (self.sound1);\r
212                 self.noise = self.sound1;\r
213         }\r
214         if (self.sound2)\r
215         {\r
216                 precache_sound (self.sound2);\r
217                 self.noise1 = self.sound2;\r
218         }\r
219 \r
220         self.mangle = self.angles;\r
221         self.angles = '0 0 0';\r
222 \r
223         self.classname = "plat";\r
224         if not(InitMovingBrushTrigger())\r
225                 return;\r
226         self.effects |= EF_LOWPRECISION;\r
227         setsize (self, self.mins , self.maxs);\r
228 \r
229         self.blocked = plat_crush;\r
230 \r
231         if (!self.speed)\r
232                 self.speed = 150;\r
233 \r
234         self.pos1 = self.origin;\r
235         self.pos2 = self.origin;\r
236         self.pos2_z = self.origin_z - self.size_z + 8;\r
237 \r
238         plat_spawn_inside_trigger ();   // the "start moving" trigger\r
239 \r
240         self.reset = plat_reset;\r
241         plat_reset();\r
242 };\r
243 \r
244 \r
245 void() train_next;\r
246 void train_wait()\r
247 {\r
248         self.think = train_next;\r
249         self.nextthink = self.ltime + self.wait;\r
250 \r
251         if(self.noise != "")\r
252                 stopsoundto(MSG_BROADCAST, self, CHAN_TRIGGER); // send this as unreliable only, as the train will resume operation shortly anyway\r
253 };\r
254 \r
255 void train_next()\r
256 {\r
257         local entity targ;\r
258         targ = find(world, targetname, self.target);\r
259         self.target = targ.target;\r
260         if (!self.target)\r
261                 objerror("train_next: no next target");\r
262         self.wait = targ.wait;\r
263         if (!self.wait)\r
264                 self.wait = 0.1;\r
265         if(self.wait < 0)\r
266         {\r
267                 if (targ.speed)\r
268                         SUB_CalcMove(targ.origin - self.mins, targ.speed, train_next);\r
269                 else\r
270                         SUB_CalcMove(targ.origin - self.mins, self.speed, train_next);\r
271         }\r
272         else\r
273         {\r
274                 if (targ.speed)\r
275                         SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);\r
276                 else\r
277                         SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);\r
278         }\r
279 \r
280         if(self.noise != "")\r
281                 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);\r
282 };\r
283 \r
284 void func_train_find()\r
285 {\r
286         local entity targ;\r
287         targ = find(world, targetname, self.target);\r
288         self.target = targ.target;\r
289         if (!self.target)\r
290                 objerror("func_train_find: no next target");\r
291         setorigin(self, targ.origin - self.mins);\r
292         self.nextthink = self.ltime + 1;\r
293         self.think = train_next;\r
294 };\r
295 \r
296 /*QUAKED spawnfunc_func_train (0 .5 .8) ?\r
297 Ridable platform, targets spawnfunc_path_corner path to follow.\r
298 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)\r
299 target : targetname of first spawnfunc_path_corner (starts here)\r
300 */\r
301 void spawnfunc_func_train()\r
302 {\r
303         if (self.noise != "")\r
304                 precache_sound(self.noise);\r
305 \r
306         if (!self.target)\r
307                 objerror("func_train without a target");\r
308         if (!self.speed)\r
309                 self.speed = 100;\r
310 \r
311         if not(InitMovingBrushTrigger())\r
312                 return;\r
313         self.effects |= EF_LOWPRECISION;\r
314 \r
315         // wait for targets to spawn\r
316         InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);\r
317 \r
318         self.blocked = generic_plat_blocked;\r
319         if(self.dmg & (!self.message))\r
320                 self.message = " was squished";\r
321     if(self.dmg && (!self.message2))\r
322                 self.message2 = "was squished by";\r
323         if(self.dmg && (!self.dmgtime))\r
324                 self.dmgtime = 0.25;\r
325         self.dmgtime2 = time;\r
326 \r
327         // TODO make a reset function for this one\r
328 };\r
329 \r
330 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS\r
331 Brush model that spins in place on one axis (default Z).\r
332 speed   : speed to rotate (in degrees per second)\r
333 noise   : path/name of looping .wav file to play.\r
334 dmg     : Do this mutch dmg every .dmgtime intervall when blocked\r
335 dmgtime : See above.\r
336 */\r
337 \r
338 void spawnfunc_func_rotating()\r
339 {\r
340         if (self.noise != "")\r
341         {\r
342                 precache_sound(self.noise);\r
343                 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);\r
344         }\r
345         if (!self.speed)\r
346                 self.speed = 100;\r
347         // FIXME: test if this turns the right way, then remove this comment (negate as needed)\r
348         if (self.spawnflags & 4) // X (untested)\r
349                 self.avelocity = '0 0 1' * self.speed;\r
350         // FIXME: test if this turns the right way, then remove this comment (negate as needed)\r
351         else if (self.spawnflags & 8) // Y (untested)\r
352                 self.avelocity = '1 0 0' * self.speed;\r
353         // FIXME: test if this turns the right way, then remove this comment (negate as needed)\r
354         else // Z\r
355                 self.avelocity = '0 1 0' * self.speed;\r
356 \r
357     if(self.dmg & (!self.message))\r
358         self.message = " was squished";\r
359     if(self.dmg && (!self.message2))\r
360                 self.message2 = "was squished by";\r
361 \r
362 \r
363     if(self.dmg && (!self.dmgtime))\r
364         self.dmgtime = 0.25;\r
365 \r
366     self.dmgtime2 = time;\r
367 \r
368         if not(InitMovingBrushTrigger())\r
369                 return;\r
370         // no EF_LOWPRECISION here, as rounding angles is bad\r
371 \r
372     self.blocked = generic_plat_blocked;\r
373 \r
374         // wait for targets to spawn\r
375         self.nextthink = self.ltime + 999999999;\r
376         self.think = SUB_Null;\r
377 \r
378         // TODO make a reset function for this one\r
379 };\r
380 \r
381 .float height;\r
382 void func_bobbing_controller_think()\r
383 {\r
384         local vector v;\r
385         self.nextthink = time + 0.1;\r
386         // calculate sinewave using makevectors\r
387         makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');\r
388         v = self.owner.destvec + self.owner.movedir * v_forward_y;\r
389         // * 10 so it will arrive in 0.1 sec\r
390         if(self.owner.classname == "func_bobbing")\r
391                 self.owner.velocity = (v - self.owner.origin) * 10;\r
392 };\r
393 \r
394 void bobbing_blocked()\r
395 {\r
396         // no need to duplicate code\r
397         generic_plat_blocked();\r
398 }\r
399 \r
400 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS\r
401 Brush model that moves back and forth on one axis (default Z).\r
402 speed : how long one cycle takes in seconds (default 4)\r
403 height : how far the cycle moves (default 32)\r
404 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)\r
405 noise : path/name of looping .wav file to play.\r
406 dmg : Do this mutch dmg every .dmgtime intervall when blocked\r
407 dmgtime : See above.\r
408 */\r
409 void spawnfunc_func_bobbing()\r
410 {\r
411         local entity controller;\r
412         if (self.noise != "")\r
413         {\r
414                 precache_sound(self.noise);\r
415                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);\r
416         }\r
417         if (!self.speed)\r
418                 self.speed = 4;\r
419         if (!self.height)\r
420                 self.height = 32;\r
421         // center of bobbing motion\r
422         self.destvec = self.origin;\r
423         // time scale to get degrees\r
424         self.cnt = 360 / self.speed;\r
425 \r
426         // damage when blocked\r
427         self.blocked = bobbing_blocked;\r
428         if(self.dmg & (!self.message))\r
429                 self.message = " was squished";\r
430     if(self.dmg && (!self.message2))\r
431                 self.message2 = "was squished by";\r
432         if(self.dmg && (!self.dmgtime))\r
433                 self.dmgtime = 0.25;\r
434         self.dmgtime2 = time;\r
435 \r
436         // how far to bob\r
437         if (self.spawnflags & 1) // X\r
438                 self.movedir = '1 0 0' * self.height;\r
439         else if (self.spawnflags & 2) // Y\r
440                 self.movedir = '0 1 0' * self.height;\r
441         else // Z\r
442                 self.movedir = '0 0 1' * self.height;\r
443 \r
444         if not(InitMovingBrushTrigger())\r
445                 return;\r
446 \r
447         // wait for targets to spawn\r
448         controller = spawn();\r
449         controller.classname = "func_bobbing_controller";\r
450         controller.owner = self;\r
451         controller.nextthink = time + 1;\r
452         controller.think = func_bobbing_controller_think;\r
453         self.nextthink = self.ltime + 999999999;\r
454         self.think = SUB_Null;\r
455 \r
456         // Savage: Reduce bandwith, critical on e.g. nexdm02\r
457         self.effects |= EF_LOWPRECISION;\r
458 \r
459         // TODO make a reset function for this one\r
460 };\r
461 \r
462 // button and multiple button\r
463 \r
464 void() button_wait;\r
465 void() button_return;\r
466 \r
467 void button_wait()\r
468 {\r
469         self.state = STATE_TOP;\r
470         self.nextthink = self.ltime + self.wait;\r
471         self.think = button_return;\r
472         activator = self.enemy;\r
473         SUB_UseTargets();\r
474         self.frame = 1;                 // use alternate textures\r
475 };\r
476 \r
477 void button_done()\r
478 {\r
479         self.state = STATE_BOTTOM;\r
480 };\r
481 \r
482 void button_return()\r
483 {\r
484         self.state = STATE_DOWN;\r
485         SUB_CalcMove (self.pos1, self.speed, button_done);\r
486         self.frame = 0;                 // use normal textures\r
487         if (self.health)\r
488                 self.takedamage = DAMAGE_YES;   // can be shot again\r
489 };\r
490 \r
491 \r
492 void button_blocked()\r
493 {\r
494         // do nothing, just don't come all the way back out\r
495 };\r
496 \r
497 \r
498 void button_fire()\r
499 {\r
500         self.health = self.max_health;\r
501         self.takedamage = DAMAGE_NO;    // will be reset upon return\r
502 \r
503         if (self.state == STATE_UP || self.state == STATE_TOP)\r
504                 return;\r
505 \r
506         if (self.noise != "")\r
507                 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);\r
508 \r
509         self.state = STATE_UP;\r
510         SUB_CalcMove (self.pos2, self.speed, button_wait);\r
511 };\r
512 \r
513 void button_reset()\r
514 {\r
515         self.health = self.max_health;\r
516         setorigin(self, self.pos1);\r
517         self.frame = 0;                 // use normal textures\r
518         self.state = STATE_BOTTOM;\r
519         if (self.health)\r
520                 self.takedamage = DAMAGE_YES;   // can be shot again\r
521 }\r
522 \r
523 void button_use()\r
524 {\r
525 //      if (activator.classname != "player")\r
526 //      {\r
527 //              dprint(activator.classname);\r
528 //              dprint(" triggered a button\n");\r
529 //      }\r
530         self.enemy = activator;\r
531         button_fire ();\r
532 };\r
533 \r
534 void button_touch()\r
535 {\r
536 //      if (activator.classname != "player")\r
537 //      {\r
538 //              dprint(activator.classname);\r
539 //              dprint(" touched a button\n");\r
540 //      }\r
541         if (!other)\r
542                 return;\r
543         if not(other.iscreature)\r
544                 return;\r
545         if(other.velocity * self.movedir < 0)\r
546                 return;\r
547         self.enemy = other;\r
548         if (other.owner)\r
549                 self.enemy = other.owner;\r
550         button_fire ();\r
551 };\r
552 \r
553 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)\r
554 {\r
555         if(self.spawnflags & DOOR_NOSPLASH)\r
556                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))\r
557                         return;\r
558         self.health = self.health - damage;\r
559         if (self.health <= 0)\r
560         {\r
561         //      if (activator.classname != "player")\r
562         //      {\r
563         //              dprint(activator.classname);\r
564         //              dprint(" killed a button\n");\r
565         //      }\r
566                 self.enemy = damage_attacker;\r
567                 button_fire ();\r
568         }\r
569 };\r
570 \r
571 \r
572 /*QUAKED spawnfunc_func_button (0 .5 .8) ?\r
573 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.\r
574 \r
575 "angle"         determines the opening direction\r
576 "target"        all entities with a matching targetname will be used\r
577 "speed"         override the default 40 speed\r
578 "wait"          override the default 1 second wait (-1 = never return)\r
579 "lip"           override the default 4 pixel lip remaining at end of move\r
580 "health"        if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the MinstaGib laser\r
581 "sounds"\r
582 0) steam metal\r
583 1) wooden clunk\r
584 2) metallic click\r
585 3) in-out\r
586 */\r
587 void spawnfunc_func_button()\r
588 {\r
589         SetMovedir ();\r
590 \r
591         if not(InitMovingBrushTrigger())\r
592                 return;\r
593         self.effects |= EF_LOWPRECISION;\r
594 \r
595         self.blocked = button_blocked;\r
596         self.use = button_use;\r
597 \r
598 //      if (self.health == 0) // all buttons are now shootable\r
599 //              self.health = 10;\r
600         if (self.health)\r
601         {\r
602                 self.max_health = self.health;\r
603                 self.event_damage = button_damage;\r
604                 self.takedamage = DAMAGE_YES;\r
605         }\r
606         else\r
607                 self.touch = button_touch;\r
608 \r
609         if (!self.speed)\r
610                 self.speed = 40;\r
611         if (!self.wait)\r
612                 self.wait = 1;\r
613         if (!self.lip)\r
614                 self.lip = 4;\r
615 \r
616     if(self.noise != "")\r
617         precache_sound(self.noise);\r
618 \r
619         self.pos1 = self.origin;\r
620         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);\r
621     self.flags |= FL_NOTARGET;\r
622 \r
623         button_reset();\r
624 };\r
625 \r
626 \r
627 float DOOR_START_OPEN = 1;\r
628 float DOOR_DONT_LINK = 4;\r
629 float DOOR_TOGGLE = 32;\r
630 \r
631 /*\r
632 \r
633 Doors are similar to buttons, but can spawn a fat trigger field around them\r
634 to open without a touch, and they link together to form simultanious\r
635 double/quad doors.\r
636 \r
637 Door.owner is the master door.  If there is only one door, it points to itself.\r
638 If multiple doors, all will point to a single one.\r
639 \r
640 Door.enemy chains from the master door through all doors linked in the chain.\r
641 \r
642 */\r
643 \r
644 /*\r
645 =============================================================================\r
646 \r
647 THINK FUNCTIONS\r
648 \r
649 =============================================================================\r
650 */\r
651 \r
652 void() door_go_down;\r
653 void() door_go_up;\r
654 void() door_rotating_go_down;\r
655 void() door_rotating_go_up;\r
656 \r
657 void door_blocked()\r
658 {\r
659 \r
660     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!\r
661         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
662     } else {\r
663 \r
664         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?\r
665             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
666 \r
667          //Dont chamge direction for dead or dying stuff\r
668         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {\r
669             if (self.wait >= 0)\r
670             {\r
671                 if (self.state == STATE_DOWN)\r
672                         if (self.classname == "door")\r
673                         {\r
674                                 door_go_up ();\r
675                         } else\r
676                         {\r
677                                 door_rotating_go_up ();\r
678                         }\r
679                 else\r
680                         if (self.classname == "door")\r
681                         {\r
682                                 door_go_down ();\r
683                         } else\r
684                         {\r
685                                 door_rotating_go_down ();\r
686                         }\r
687             }\r
688         } else {\r
689             //gib dying stuff just to make sure\r
690             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?\r
691                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
692         }\r
693     }\r
694 \r
695         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);\r
696 // if a door has a negative wait, it would never come back if blocked,\r
697 // so let it just squash the object to death real fast\r
698 /*      if (self.wait >= 0)\r
699         {\r
700                 if (self.state == STATE_DOWN)\r
701                         door_go_up ();\r
702                 else\r
703                         door_go_down ();\r
704         }\r
705 */\r
706 };\r
707 \r
708 \r
709 void door_hit_top()\r
710 {\r
711         if (self.noise1 != "")\r
712                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);\r
713         self.state = STATE_TOP;\r
714         if (self.spawnflags & DOOR_TOGGLE)\r
715                 return;         // don't come down automatically\r
716         if (self.classname == "door")\r
717         {\r
718                 self.think = door_go_down;\r
719         } else\r
720         {\r
721                 self.think = door_rotating_go_down;\r
722         }\r
723         self.nextthink = self.ltime + self.wait;\r
724 };\r
725 \r
726 void door_hit_bottom()\r
727 {\r
728         if (self.noise1 != "")\r
729                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);\r
730         self.state = STATE_BOTTOM;\r
731 };\r
732 \r
733 void door_go_down()\r
734 {\r
735         if (self.noise2 != "")\r
736                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
737         if (self.max_health)\r
738         {\r
739                 self.takedamage = DAMAGE_YES;\r
740                 self.health = self.max_health;\r
741         }\r
742 \r
743         self.state = STATE_DOWN;\r
744         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);\r
745 };\r
746 \r
747 void door_go_up()\r
748 {\r
749         if (self.state == STATE_UP)\r
750                 return;         // already going up\r
751 \r
752         if (self.state == STATE_TOP)\r
753         {       // reset top wait time\r
754                 self.nextthink = self.ltime + self.wait;\r
755                 return;\r
756         }\r
757 \r
758         if (self.noise2 != "")\r
759                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
760         self.state = STATE_UP;\r
761         SUB_CalcMove (self.pos2, self.speed, door_hit_top);\r
762 \r
763         string oldmessage;\r
764         oldmessage = self.message;\r
765         self.message = "";\r
766         SUB_UseTargets();\r
767         self.message = oldmessage;\r
768 };\r
769 \r
770 \r
771 /*\r
772 =============================================================================\r
773 \r
774 ACTIVATION FUNCTIONS\r
775 \r
776 =============================================================================\r
777 */\r
778 \r
779 void door_fire()\r
780 {\r
781         local entity    oself;\r
782         local entity    starte;\r
783 \r
784         if (self.owner != self)\r
785                 objerror ("door_fire: self.owner != self");\r
786 \r
787         oself = self;\r
788 \r
789         if (self.spawnflags & DOOR_TOGGLE)\r
790         {\r
791                 if (self.state == STATE_UP || self.state == STATE_TOP)\r
792                 {\r
793                         starte = self;\r
794                         do\r
795                         {\r
796                                 if (self.classname == "door")\r
797                                 {\r
798                                         door_go_down ();\r
799                                 }\r
800                                 else\r
801                                 {\r
802                                         door_rotating_go_down ();\r
803                                 }\r
804                                 self = self.enemy;\r
805                         } while ( (self != starte) && (self != world) );\r
806                         self = oself;\r
807                         return;\r
808                 }\r
809         }\r
810 \r
811 // trigger all paired doors\r
812         starte = self;\r
813         do\r
814         {\r
815                 if (self.classname == "door")\r
816                 {\r
817                         door_go_up ();\r
818                 } else\r
819                 {\r
820                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction\r
821                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)\r
822                         {\r
823                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating\r
824                                 self.pos2 = '0 0 0' - self.pos2;\r
825                         }\r
826                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side\r
827                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN\r
828                             && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))\r
829                         {\r
830                                 door_rotating_go_up ();\r
831                         }\r
832                 }\r
833                 self = self.enemy;\r
834         } while ( (self != starte) && (self != world) );\r
835         self = oself;\r
836 };\r
837 \r
838 \r
839 void door_use()\r
840 {\r
841         local entity oself;\r
842 \r
843         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");\r
844         if (self.owner)\r
845         {\r
846                 oself = self;\r
847                 self = self.owner;\r
848                 door_fire ();\r
849                 self = oself;\r
850         }\r
851 };\r
852 \r
853 \r
854 void door_trigger_touch()\r
855 {\r
856         if (other.health < 1)\r
857         if not(other.iscreature && other.deadflag == DEAD_NO)\r
858                 return;\r
859 \r
860         if (time < self.attack_finished_single)\r
861                 return;\r
862         self.attack_finished_single = time + 1;\r
863 \r
864         activator = other;\r
865 \r
866         self = self.owner;\r
867         door_use ();\r
868 };\r
869 \r
870 \r
871 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)\r
872 {\r
873         local entity oself;\r
874         if(self.spawnflags & DOOR_NOSPLASH)\r
875                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))\r
876                         return;\r
877         self.health = self.health - damage;\r
878         if (self.health <= 0)\r
879         {\r
880                 oself = self;\r
881                 self = self.owner;\r
882                 self.health = self.max_health;\r
883                 self.takedamage = DAMAGE_NO;    // wil be reset upon return\r
884                 door_use ();\r
885                 self = oself;\r
886         }\r
887 };\r
888 \r
889 \r
890 /*\r
891 ================\r
892 door_touch\r
893 \r
894 Prints messages\r
895 ================\r
896 */\r
897 void door_touch()\r
898 {\r
899         if(other.classname != "player")\r
900                 return;\r
901         if (self.owner.attack_finished_single > time)\r
902                 return;\r
903 \r
904         self.owner.attack_finished_single = time + 2;\r
905 \r
906         if (!(self.owner.dmg) && (self.owner.message != ""))\r
907         {\r
908                 if (other.flags & FL_CLIENT)\r
909                         centerprint (other, self.owner.message);\r
910                 play2(other, "misc/talk.wav");\r
911         }\r
912 };\r
913 \r
914 \r
915 void door_generic_plat_blocked()\r
916 {\r
917 \r
918     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!\r
919         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
920     } else {\r
921 \r
922         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?\r
923             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
924 \r
925          //Dont chamge direction for dead or dying stuff\r
926         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {\r
927             if (self.wait >= 0)\r
928             {\r
929                 if (self.state == STATE_DOWN)\r
930                     door_rotating_go_up ();\r
931                 else\r
932                     door_rotating_go_down ();\r
933             }\r
934         } else {\r
935             //gib dying stuff just to make sure\r
936             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?\r
937                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');\r
938         }\r
939     }\r
940 \r
941         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);\r
942 // if a door has a negative wait, it would never come back if blocked,\r
943 // so let it just squash the object to death real fast\r
944 /*      if (self.wait >= 0)\r
945         {\r
946                 if (self.state == STATE_DOWN)\r
947                         door_rotating_go_up ();\r
948                 else\r
949                         door_rotating_go_down ();\r
950         }\r
951 */\r
952 };\r
953 \r
954 \r
955 void door_rotating_hit_top()\r
956 {\r
957         if (self.noise1 != "")\r
958                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);\r
959         self.state = STATE_TOP;\r
960         if (self.spawnflags & DOOR_TOGGLE)\r
961                 return;         // don't come down automatically\r
962         self.think = door_rotating_go_down;\r
963         self.nextthink = self.ltime + self.wait;\r
964 };\r
965 \r
966 void door_rotating_hit_bottom()\r
967 {\r
968         if (self.noise1 != "")\r
969                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);\r
970         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating\r
971         {\r
972                 self.pos2 = '0 0 0' - self.pos2;\r
973                 self.lip = 0;\r
974         }\r
975         self.state = STATE_BOTTOM;\r
976 };\r
977 \r
978 void door_rotating_go_down()\r
979 {\r
980         if (self.noise2 != "")\r
981                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
982         if (self.max_health)\r
983         {\r
984                 self.takedamage = DAMAGE_YES;\r
985                 self.health = self.max_health;\r
986         }\r
987 \r
988         self.state = STATE_DOWN;\r
989         SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);\r
990 };\r
991 \r
992 void door_rotating_go_up()\r
993 {\r
994         if (self.state == STATE_UP)\r
995                 return;         // already going up\r
996 \r
997         if (self.state == STATE_TOP)\r
998         {       // reset top wait time\r
999                 self.nextthink = self.ltime + self.wait;\r
1000                 return;\r
1001         }\r
1002         if (self.noise2 != "")\r
1003                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
1004         self.state = STATE_UP;\r
1005         SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);\r
1006 \r
1007         string oldmessage;\r
1008         oldmessage = self.message;\r
1009         self.message = "";\r
1010         SUB_UseTargets();\r
1011         self.message = oldmessage;\r
1012 };\r
1013 \r
1014 \r
1015 \r
1016 \r
1017 /*\r
1018 =============================================================================\r
1019 \r
1020 SPAWNING FUNCTIONS\r
1021 \r
1022 =============================================================================\r
1023 */\r
1024 \r
1025 \r
1026 entity spawn_field(vector fmins, vector fmaxs)\r
1027 {\r
1028         local entity    trigger;\r
1029         local   vector  t1, t2;\r
1030 \r
1031         trigger = spawn();\r
1032         trigger.classname = "doortriggerfield";\r
1033         trigger.movetype = MOVETYPE_NONE;\r
1034         trigger.solid = SOLID_TRIGGER;\r
1035         trigger.owner = self;\r
1036         trigger.touch = door_trigger_touch;\r
1037 \r
1038         t1 = fmins;\r
1039         t2 = fmaxs;\r
1040         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');\r
1041         return (trigger);\r
1042 };\r
1043 \r
1044 \r
1045 float EntitiesTouching(entity e1, entity e2)\r
1046 {\r
1047         if (e1.absmin_x > e2.absmax_x)\r
1048                 return FALSE;\r
1049         if (e1.absmin_y > e2.absmax_y)\r
1050                 return FALSE;\r
1051         if (e1.absmin_z > e2.absmax_z)\r
1052                 return FALSE;\r
1053         if (e1.absmax_x < e2.absmin_x)\r
1054                 return FALSE;\r
1055         if (e1.absmax_y < e2.absmin_y)\r
1056                 return FALSE;\r
1057         if (e1.absmax_z < e2.absmin_z)\r
1058                 return FALSE;\r
1059         return TRUE;\r
1060 };\r
1061 \r
1062 \r
1063 /*\r
1064 =============\r
1065 LinkDoors\r
1066 \r
1067 \r
1068 =============\r
1069 */\r
1070 void LinkDoors()\r
1071 {\r
1072         local entity    t, starte;\r
1073         local vector    cmins, cmaxs;\r
1074 \r
1075         if (self.enemy)\r
1076                 return;         // already linked by another door\r
1077         if (self.spawnflags & 4)\r
1078         {\r
1079                 self.owner = self.enemy = self;\r
1080 \r
1081                 if (self.health)\r
1082                         return;\r
1083                 IFTARGETED\r
1084                         return;\r
1085                 if (self.items)\r
1086                         return;\r
1087                 self.trigger_field = spawn_field(self.absmin, self.absmax);\r
1088 \r
1089                 return;         // don't want to link this door\r
1090         }\r
1091 \r
1092         cmins = self.absmin;\r
1093         cmaxs = self.absmax;\r
1094 \r
1095         starte = self;\r
1096         t = self;\r
1097 \r
1098         do\r
1099         {\r
1100                 self.owner = starte;                    // master door\r
1101 \r
1102                 if (self.health)\r
1103                         starte.health = self.health;\r
1104                 IFTARGETED\r
1105                         starte.targetname = self.targetname;\r
1106                 if (self.message != "")\r
1107                         starte.message = self.message;\r
1108 \r
1109                 t = find(t, classname, self.classname);\r
1110                 if (!t)\r
1111                 {\r
1112                         self.enemy = starte;            // make the chain a loop\r
1113 \r
1114                 // shootable, or triggered doors just needed the owner/enemy links,\r
1115                 // they don't spawn a field\r
1116 \r
1117                         self = self.owner;\r
1118 \r
1119                         if (self.health)\r
1120                                 return;\r
1121                         IFTARGETED\r
1122                                 return;\r
1123                         if (self.items)\r
1124                                 return;\r
1125 \r
1126                         self.owner.trigger_field = spawn_field(cmins, cmaxs);\r
1127 \r
1128                         return;\r
1129                 }\r
1130 \r
1131                 if (EntitiesTouching(self,t))\r
1132                 {\r
1133                         if (t.enemy)\r
1134                                 objerror ("cross connected doors");\r
1135 \r
1136                         self.enemy = t;\r
1137                         self = t;\r
1138 \r
1139                         if (t.absmin_x < cmins_x)\r
1140                                 cmins_x = t.absmin_x;\r
1141                         if (t.absmin_y < cmins_y)\r
1142                                 cmins_y = t.absmin_y;\r
1143                         if (t.absmin_z < cmins_z)\r
1144                                 cmins_z = t.absmin_z;\r
1145                         if (t.absmax_x > cmaxs_x)\r
1146                                 cmaxs_x = t.absmax_x;\r
1147                         if (t.absmax_y > cmaxs_y)\r
1148                                 cmaxs_y = t.absmax_y;\r
1149                         if (t.absmax_z > cmaxs_z)\r
1150                                 cmaxs_z = t.absmax_z;\r
1151                 }\r
1152         } while (1 );\r
1153 \r
1154 };\r
1155 \r
1156 \r
1157 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE\r
1158 if two doors touch, they are assumed to be connected and operate as a unit.\r
1159 \r
1160 TOGGLE causes the door to wait in both the start and end states for a trigger event.\r
1161 \r
1162 START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).\r
1163 \r
1164 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet\r
1165 "angle"         determines the opening direction\r
1166 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.\r
1167 "health"        if set, door must be shot open\r
1168 "speed"         movement speed (100 default)\r
1169 "wait"          wait before returning (3 default, -1 = never return)\r
1170 "lip"           lip remaining at end of move (8 default)\r
1171 "dmg"           damage to inflict when blocked (2 default)\r
1172 "sounds"\r
1173 0)      no sound\r
1174 1)      stone\r
1175 2)      base\r
1176 3)      stone chain\r
1177 4)      screechy metal\r
1178 FIXME: only one sound set available at the time being\r
1179 \r
1180 */\r
1181 \r
1182 void door_init_startopen()\r
1183 {\r
1184         setorigin (self, self.pos2);\r
1185         self.pos2 = self.pos1;\r
1186         self.pos1 = self.origin;\r
1187 }\r
1188 \r
1189 void door_reset()\r
1190 {\r
1191         setorigin(self, self.pos1);\r
1192         self.velocity = '0 0 0';\r
1193         self.state = STATE_BOTTOM;\r
1194         self.think = SUB_Null;\r
1195 }\r
1196 \r
1197 void spawnfunc_func_door()\r
1198 {\r
1199         //if (!self.deathtype) // map makers can override this\r
1200         //      self.deathtype = " got in the way";\r
1201         SetMovedir ();\r
1202 \r
1203         self.max_health = self.health;\r
1204         if not(InitMovingBrushTrigger())\r
1205                 return;\r
1206         self.effects |= EF_LOWPRECISION;\r
1207         self.classname = "door";\r
1208 \r
1209         self.blocked = door_blocked;\r
1210         self.use = door_use;\r
1211 \r
1212     if(self.spawnflags & 8)\r
1213         self.dmg = 10000;\r
1214 \r
1215     if(self.dmg && (!self.message))\r
1216                 self.message = "was squished";\r
1217     if(self.dmg && (!self.message2))\r
1218                 self.message2 = "was squished by";\r
1219 \r
1220         if (self.sounds > 0)\r
1221         {\r
1222                 precache_sound ("plats/medplat1.wav");\r
1223                 precache_sound ("plats/medplat2.wav");\r
1224                 self.noise2 = "plats/medplat1.wav";\r
1225                 self.noise1 = "plats/medplat2.wav";\r
1226         }\r
1227 \r
1228         if (!self.speed)\r
1229                 self.speed = 100;\r
1230         if (!self.wait)\r
1231                 self.wait = 3;\r
1232         if (!self.lip)\r
1233                 self.lip = 8;\r
1234 \r
1235         self.pos1 = self.origin;\r
1236         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);\r
1237 \r
1238 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position\r
1239 // but spawn in the open position\r
1240         if (self.spawnflags & DOOR_START_OPEN)\r
1241                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);\r
1242 \r
1243         self.state = STATE_BOTTOM;\r
1244 \r
1245         if (self.health)\r
1246         {\r
1247                 self.takedamage = DAMAGE_YES;\r
1248                 self.event_damage = door_damage;\r
1249         }\r
1250 \r
1251         if (self.items)\r
1252                 self.wait = -1;\r
1253 \r
1254         self.touch = door_touch;\r
1255 \r
1256 // LinkDoors can't be done until all of the doors have been spawned, so\r
1257 // the sizes can be detected properly.\r
1258         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);\r
1259 \r
1260         self.reset = door_reset;\r
1261 };\r
1262 \r
1263 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS\r
1264 if two doors touch, they are assumed to be connected and operate as a unit.\r
1265 \r
1266 TOGGLE causes the door to wait in both the start and end states for a trigger event.\r
1267 \r
1268 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.\r
1269 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction\r
1270 must have set trigger_reverse to 1.\r
1271 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.\r
1272 \r
1273 START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).\r
1274 \r
1275 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet\r
1276 "angle"         determines the destination angle for opening. negative values reverse the direction.\r
1277 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.\r
1278 "health"        if set, door must be shot open\r
1279 "speed"         movement speed (100 default)\r
1280 "wait"          wait before returning (3 default, -1 = never return)\r
1281 "dmg"           damage to inflict when blocked (2 default)\r
1282 "sounds"\r
1283 0)      no sound\r
1284 1)      stone\r
1285 2)      base\r
1286 3)      stone chain\r
1287 4)      screechy metal\r
1288 FIXME: only one sound set available at the time being\r
1289 */\r
1290 \r
1291 void door_rotating_reset()\r
1292 {\r
1293         self.angles = self.pos1;\r
1294         self.avelocity = '0 0 0';\r
1295         self.state = STATE_BOTTOM;\r
1296         self.think = SUB_Null;\r
1297 }\r
1298 \r
1299 void door_rotating_init_startopen()\r
1300 {\r
1301         self.angles = self.movedir;\r
1302         self.pos2 = '0 0 0';\r
1303         self.pos1 = self.movedir;\r
1304 }\r
1305 \r
1306 \r
1307 void spawnfunc_func_door_rotating()\r
1308 {\r
1309 \r
1310         //if (!self.deathtype) // map makers can override this\r
1311         //      self.deathtype = " got in the way";\r
1312 \r
1313         // I abuse "movedir" for denoting the axis for now\r
1314         if (self.spawnflags & 64) // X (untested)\r
1315                 self.movedir = '0 0 1';\r
1316         else if (self.spawnflags & 128) // Y (untested)\r
1317                 self.movedir = '1 0 0';\r
1318         else // Z\r
1319                 self.movedir = '0 1 0';\r
1320 \r
1321         if (self.angles_y==0) self.angles_y = 90;\r
1322 \r
1323         self.movedir = self.movedir * self.angles_y;\r
1324         self.angles = '0 0 0';\r
1325 \r
1326         self.max_health = self.health;\r
1327         if not(InitMovingBrushTrigger())\r
1328                 return;\r
1329         //self.effects |= EF_LOWPRECISION;\r
1330         self.classname = "door_rotating";\r
1331 \r
1332         self.blocked = door_blocked;\r
1333         self.use = door_use;\r
1334 \r
1335     if(self.spawnflags & 8)\r
1336         self.dmg = 10000;\r
1337 \r
1338     if(self.dmg && (!self.message))\r
1339                 self.message = "was squished";\r
1340     if(self.dmg && (!self.message2))\r
1341                 self.message2 = "was squished by";\r
1342 \r
1343     if (self.sounds > 0)\r
1344         {\r
1345                 precache_sound ("plats/medplat1.wav");\r
1346                 precache_sound ("plats/medplat2.wav");\r
1347                 self.noise2 = "plats/medplat1.wav";\r
1348                 self.noise1 = "plats/medplat2.wav";\r
1349         }\r
1350 \r
1351         if (!self.speed)\r
1352                 self.speed = 50;\r
1353         if (!self.wait)\r
1354                 self.wait = 1;\r
1355         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating\r
1356 \r
1357         self.pos1 = '0 0 0';\r
1358         self.pos2 = self.movedir;\r
1359 \r
1360 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position\r
1361 // but spawn in the open position\r
1362         if (self.spawnflags & DOOR_START_OPEN)\r
1363                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);\r
1364 \r
1365         self.state = STATE_BOTTOM;\r
1366 \r
1367         if (self.health)\r
1368         {\r
1369                 self.takedamage = DAMAGE_YES;\r
1370                 self.event_damage = door_damage;\r
1371         }\r
1372 \r
1373         if (self.items)\r
1374                 self.wait = -1;\r
1375 \r
1376         self.touch = door_touch;\r
1377 \r
1378 // LinkDoors can't be done until all of the doors have been spawned, so\r
1379 // the sizes can be detected properly.\r
1380         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);\r
1381 \r
1382         self.reset = door_rotating_reset;\r
1383 };\r
1384 \r
1385 /*\r
1386 =============================================================================\r
1387 \r
1388 SECRET DOORS\r
1389 \r
1390 =============================================================================\r
1391 */\r
1392 \r
1393 void() fd_secret_move1;\r
1394 void() fd_secret_move2;\r
1395 void() fd_secret_move3;\r
1396 void() fd_secret_move4;\r
1397 void() fd_secret_move5;\r
1398 void() fd_secret_move6;\r
1399 void() fd_secret_done;\r
1400 \r
1401 float SECRET_OPEN_ONCE = 1;             // stays open\r
1402 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow\r
1403 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow\r
1404 float SECRET_NO_SHOOT = 8;              // only opened by trigger\r
1405 float SECRET_YES_SHOOT = 16;    // shootable even if targeted\r
1406 \r
1407 \r
1408 void fd_secret_use()\r
1409 {\r
1410         local float temp;\r
1411         string message_save;\r
1412 \r
1413         self.health = 10000;\r
1414         self.bot_attack = TRUE;\r
1415 \r
1416         // exit if still moving around...\r
1417         if (self.origin != self.oldorigin)\r
1418                 return;\r
1419 \r
1420         message_save = self.message;\r
1421         self.message = ""; // no more message\r
1422         SUB_UseTargets();                               // fire all targets / killtargets\r
1423         self.message = message_save;\r
1424 \r
1425         self.velocity = '0 0 0';\r
1426 \r
1427         // Make a sound, wait a little...\r
1428 \r
1429         if (self.noise1 != "")\r
1430                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);\r
1431         self.nextthink = self.ltime + 0.1;\r
1432 \r
1433         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1\r
1434         makevectors(self.mangle);\r
1435 \r
1436         if (!self.t_width)\r
1437         {\r
1438                 if (self.spawnflags & SECRET_1ST_DOWN)\r
1439                         self.t_width = fabs(v_up * self.size);\r
1440                 else\r
1441                         self.t_width = fabs(v_right * self.size);\r
1442         }\r
1443 \r
1444         if (!self.t_length)\r
1445                 self.t_length = fabs(v_forward * self.size);\r
1446 \r
1447         if (self.spawnflags & SECRET_1ST_DOWN)\r
1448                 self.dest1 = self.origin - v_up * self.t_width;\r
1449         else\r
1450                 self.dest1 = self.origin + v_right * (self.t_width * temp);\r
1451 \r
1452         self.dest2 = self.dest1 + v_forward * self.t_length;\r
1453         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);\r
1454         if (self.noise2 != "")\r
1455                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
1456 };\r
1457 \r
1458 // Wait after first movement...\r
1459 void fd_secret_move1()\r
1460 {\r
1461         self.nextthink = self.ltime + 1.0;\r
1462         self.think = fd_secret_move2;\r
1463         if (self.noise3 != "")\r
1464                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);\r
1465 };\r
1466 \r
1467 // Start moving sideways w/sound...\r
1468 void fd_secret_move2()\r
1469 {\r
1470         if (self.noise2 != "")\r
1471                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
1472         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);\r
1473 };\r
1474 \r
1475 // Wait here until time to go back...\r
1476 void fd_secret_move3()\r
1477 {\r
1478         if (self.noise3 != "")\r
1479                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);\r
1480         if (!(self.spawnflags & SECRET_OPEN_ONCE))\r
1481         {\r
1482                 self.nextthink = self.ltime + self.wait;\r
1483                 self.think = fd_secret_move4;\r
1484         }\r
1485 };\r
1486 \r
1487 // Move backward...\r
1488 void fd_secret_move4()\r
1489 {\r
1490         if (self.noise2 != "")\r
1491                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
1492         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);\r
1493 };\r
1494 \r
1495 // Wait 1 second...\r
1496 void fd_secret_move5()\r
1497 {\r
1498         self.nextthink = self.ltime + 1.0;\r
1499         self.think = fd_secret_move6;\r
1500         if (self.noise3 != "")\r
1501                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);\r
1502 };\r
1503 \r
1504 void fd_secret_move6()\r
1505 {\r
1506         if (self.noise2 != "")\r
1507                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);\r
1508         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);\r
1509 };\r
1510 \r
1511 void fd_secret_done()\r
1512 {\r
1513         if (self.spawnflags&SECRET_YES_SHOOT)\r
1514         {\r
1515                 self.health = 10000;\r
1516                 self.takedamage = DAMAGE_YES;\r
1517                 //self.th_pain = fd_secret_use;\r
1518         }\r
1519         if (self.noise3 != "")\r
1520                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);\r
1521 };\r
1522 \r
1523 void secret_blocked()\r
1524 {\r
1525         if (time < self.attack_finished_single)\r
1526                 return;\r
1527         self.attack_finished_single = time + 0.5;\r
1528         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);\r
1529 };\r
1530 \r
1531 /*\r
1532 ==============\r
1533 secret_touch\r
1534 \r
1535 Prints messages\r
1536 ================\r
1537 */\r
1538 void secret_touch()\r
1539 {\r
1540         if not(other.iscreature)\r
1541                 return;\r
1542         if (self.attack_finished_single > time)\r
1543                 return;\r
1544 \r
1545         self.attack_finished_single = time + 2;\r
1546 \r
1547         if (self.message)\r
1548         {\r
1549                 if (other.flags & FL_CLIENT)\r
1550                         centerprint (other, self.message);\r
1551                 play2(other, "misc/talk.wav");\r
1552         }\r
1553 };\r
1554 \r
1555 void secret_reset()\r
1556 {\r
1557         if (self.spawnflags&SECRET_YES_SHOOT)\r
1558         {\r
1559                 self.health = 10000;\r
1560                 self.takedamage = DAMAGE_YES;\r
1561         }\r
1562         setorigin(self, self.oldorigin);\r
1563         self.think = SUB_Null;\r
1564 }\r
1565 \r
1566 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot\r
1567 Basic secret door. Slides back, then to the side. Angle determines direction.\r
1568 wait  = # of seconds before coming back\r
1569 1st_left = 1st move is left of arrow\r
1570 1st_down = 1st move is down from arrow\r
1571 always_shoot = even if targeted, keep shootable\r
1572 t_width = override WIDTH to move back (or height if going down)\r
1573 t_length = override LENGTH to move sideways\r
1574 "dmg"           damage to inflict when blocked (2 default)\r
1575 \r
1576 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.\r
1577 "sounds"\r
1578 1) medieval\r
1579 2) metal\r
1580 3) base\r
1581 */\r
1582 \r
1583 void spawnfunc_func_door_secret()\r
1584 {\r
1585         /*if (!self.deathtype) // map makers can override this\r
1586                 self.deathtype = " got in the way";*/\r
1587 \r
1588         if (!self.dmg)\r
1589                 self.dmg = 2;\r
1590 \r
1591         // Magic formula...\r
1592         self.mangle = self.angles;\r
1593         self.angles = '0 0 0';\r
1594         self.classname = "door";\r
1595         if not(InitMovingBrushTrigger())\r
1596                 return;\r
1597         self.effects |= EF_LOWPRECISION;\r
1598 \r
1599         self.touch = secret_touch;\r
1600         self.blocked = secret_blocked;\r
1601         self.speed = 50;\r
1602         self.use = fd_secret_use;\r
1603         IFTARGETED\r
1604         {\r
1605         }\r
1606         else\r
1607                 self.spawnflags |= SECRET_YES_SHOOT;\r
1608 \r
1609         if(self.spawnflags&SECRET_YES_SHOOT)\r
1610         {\r
1611                 self.health = 10000;\r
1612                 self.takedamage = DAMAGE_YES;\r
1613                 self.event_damage = fd_secret_use;\r
1614         }\r
1615         self.oldorigin = self.origin;\r
1616         if (!self.wait)\r
1617                 self.wait = 5;          // 5 seconds before closing\r
1618 \r
1619         self.reset = secret_reset;\r
1620         secret_reset();\r
1621 };\r
1622 \r
1623 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?\r
1624 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.\r
1625 netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults\r
1626 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)\r
1627 height: amplitude modifier (default 32)\r
1628 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)\r
1629 noise: path/name of looping .wav file to play.\r
1630 dmg: Do this mutch dmg every .dmgtime intervall when blocked\r
1631 dmgtime: See above.\r
1632 */\r
1633 \r
1634 void func_fourier_controller_think()\r
1635 {\r
1636         local vector v;\r
1637         float n, i, t;\r
1638 \r
1639         self.nextthink = time + 0.1;\r
1640 \r
1641         n = floor((tokenize_console(self.owner.netname)) / 5);\r
1642         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;\r
1643 \r
1644         v = self.owner.destvec;\r
1645 \r
1646         for(i = 0; i < n; ++i)\r
1647         {\r
1648                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');\r
1649                 v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward_y;\r
1650         }\r
1651 \r
1652         // * 10 so it will arrive in 0.1 sec\r
1653         self.owner.velocity = (v - self.owner.origin) * 10;\r
1654 };\r
1655 \r
1656 void spawnfunc_func_fourier()\r
1657 {\r
1658         local entity controller;\r
1659         if (self.noise != "")\r
1660         {\r
1661                 precache_sound(self.noise);\r
1662                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);\r
1663         }\r
1664 \r
1665         if (!self.speed)\r
1666                 self.speed = 4;\r
1667         if (!self.height)\r
1668                 self.height = 32;\r
1669         self.destvec = self.origin;\r
1670         self.cnt = 360 / self.speed;\r
1671 \r
1672         self.blocked = generic_plat_blocked;\r
1673         if(self.dmg & (!self.message))\r
1674                 self.message = " was squished";\r
1675     if(self.dmg && (!self.message2))\r
1676                 self.message2 = "was squished by";\r
1677         if(self.dmg && (!self.dmgtime))\r
1678                 self.dmgtime = 0.25;\r
1679         self.dmgtime2 = time;\r
1680 \r
1681         if(self.netname == "")\r
1682                 self.netname = "1 0 0 0 1";\r
1683 \r
1684         if not(InitMovingBrushTrigger())\r
1685                 return;\r
1686 \r
1687         // wait for targets to spawn\r
1688         controller = spawn();\r
1689         controller.classname = "func_fourier_controller";\r
1690         controller.owner = self;\r
1691         controller.nextthink = time + 1;\r
1692         controller.think = func_fourier_controller_think;\r
1693         self.nextthink = self.ltime + 999999999;\r
1694         self.think = SUB_Null;\r
1695 \r
1696         // Savage: Reduce bandwith, critical on e.g. nexdm02\r
1697         self.effects |= EF_LOWPRECISION;\r
1698 \r
1699         // TODO make a reset function for this one\r
1700 };\r
1701 \r
1702 // reusing some fields havocbots declared\r
1703 .entity wp00, wp01, wp02, wp03;\r
1704 \r
1705 .float targetfactor, target2factor, target3factor, target4factor;\r
1706 .vector targetnormal, target2normal, target3normal, target4normal;\r
1707 \r
1708 vector func_vectormamamam_origin(entity o, float t)\r
1709 {\r
1710         vector v, p;\r
1711         float f;\r
1712         entity e;\r
1713 \r
1714         f = o.spawnflags;\r
1715         v = '0 0 0';\r
1716 \r
1717         e = o.wp00;\r
1718         if(e)\r
1719         {\r
1720                 p = e.origin + t * e.velocity;\r
1721                 if(f & 1)\r
1722                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;\r
1723                 else\r
1724                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;\r
1725         }\r
1726 \r
1727         e = o.wp01;\r
1728         if(e)\r
1729         {\r
1730                 p = e.origin + t * e.velocity;\r
1731                 if(f & 2)\r
1732                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;\r
1733                 else\r
1734                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;\r
1735         }\r
1736 \r
1737         e = o.wp02;\r
1738         if(e)\r
1739         {\r
1740                 p = e.origin + t * e.velocity;\r
1741                 if(f & 4)\r
1742                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;\r
1743                 else\r
1744                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;\r
1745         }\r
1746 \r
1747         e = o.wp03;\r
1748         if(e)\r
1749         {\r
1750                 p = e.origin + t * e.velocity;\r
1751                 if(f & 8)\r
1752                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;\r
1753                 else\r
1754                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;\r
1755         }\r
1756 \r
1757         return v;\r
1758 }\r
1759 \r
1760 void func_vectormamamam_controller_think()\r
1761 {\r
1762         self.nextthink = time + 0.1;\r
1763         self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;\r
1764 }\r
1765 \r
1766 void func_vectormamamam_findtarget()\r
1767 {\r
1768         if(self.target != "")\r
1769                 self.wp00 = find(world, targetname, self.target);\r
1770 \r
1771         if(self.target2 != "")\r
1772                 self.wp01 = find(world, targetname, self.target2);\r
1773 \r
1774         if(self.target3 != "")\r
1775                 self.wp02 = find(world, targetname, self.target3);\r
1776 \r
1777         if(self.target4 != "")\r
1778                 self.wp03 = find(world, targetname, self.target4);\r
1779 \r
1780         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)\r
1781                 objerror("No reference entity found, so there is nothing to move. Aborting.");\r
1782 \r
1783         self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);\r
1784 \r
1785         local entity controller;\r
1786         controller = spawn();\r
1787         controller.classname = "func_vectormamamam_controller";\r
1788         controller.owner = self;\r
1789         controller.nextthink = time + 1;\r
1790         controller.think = func_vectormamamam_controller_think;\r
1791 }\r
1792 \r
1793 void spawnfunc_func_vectormamamam()\r
1794 {\r
1795         if (self.noise != "")\r
1796         {\r
1797                 precache_sound(self.noise);\r
1798                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);\r
1799         }\r
1800 \r
1801         if(!self.targetfactor)\r
1802                 self.targetfactor = 1;\r
1803 \r
1804         if(!self.target2factor)\r
1805                 self.target2factor = 1;\r
1806 \r
1807         if(!self.target3factor)\r
1808                 self.target3factor = 1;\r
1809 \r
1810         if(!self.target4factor)\r
1811                 self.target4factor = 1;\r
1812 \r
1813         if(vlen(self.targetnormal))\r
1814                 self.targetnormal = normalize(self.targetnormal);\r
1815 \r
1816         if(vlen(self.target2normal))\r
1817                 self.target2normal = normalize(self.target2normal);\r
1818 \r
1819         if(vlen(self.target3normal))\r
1820                 self.target3normal = normalize(self.target3normal);\r
1821 \r
1822         if(vlen(self.target4normal))\r
1823                 self.target4normal = normalize(self.target4normal);\r
1824 \r
1825         self.blocked = generic_plat_blocked;\r
1826         if(self.dmg & (!self.message))\r
1827                 self.message = " was squished";\r
1828     if(self.dmg && (!self.message2))\r
1829                 self.message2 = "was squished by";\r
1830         if(self.dmg && (!self.dmgtime))\r
1831                 self.dmgtime = 0.25;\r
1832         self.dmgtime2 = time;\r
1833 \r
1834         if(self.netname == "")\r
1835                 self.netname = "1 0 0 0 1";\r
1836 \r
1837         if not(InitMovingBrushTrigger())\r
1838                 return;\r
1839 \r
1840         // wait for targets to spawn\r
1841         self.nextthink = self.ltime + 999999999;\r
1842         self.think = SUB_Null;\r
1843 \r
1844         // Savage: Reduce bandwith, critical on e.g. nexdm02\r
1845         self.effects |= EF_LOWPRECISION;\r
1846 \r
1847         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);\r
1848 }\r