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