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