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