6eaf2547b9f77627682e02db932349fc80da2544
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / func / door.qc
1 /*
2
3 Doors are similar to buttons, but can spawn a fat trigger field around them
4 to open without a touch, and they link together to form simultanious
5 double/quad doors.
6
7 Door.owner is the master door.  If there is only one door, it points to itself.
8 If multiple doors, all will point to a single one.
9
10 Door.enemy chains from the master door through all doors linked in the chain.
11
12 */
13
14
15 /*
16 =============================================================================
17
18 THINK FUNCTIONS
19
20 =============================================================================
21 */
22
23 void() door_go_down;
24 void() door_go_up;
25 void() door_rotating_go_down;
26 void() door_rotating_go_up;
27
28 void door_blocked()
29 {SELFPARAM();
30         if((self.spawnflags & 8)
31 #ifdef SVQC
32                 && (other.takedamage != DAMAGE_NO)
33 #elif defined(CSQC)
34                 && !PHYS_DEAD(other)
35 #endif
36         )
37         { // KIll Kill Kill!!
38 #ifdef SVQC
39                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
40 #endif
41         }
42         else
43         {
44 #ifdef SVQC
45                 if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
46                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
47 #endif
48
49                  // don't change direction for dead or dying stuff
50                 if(PHYS_DEAD(other)
51 #ifdef SVQC
52                         && (other.takedamage == DAMAGE_NO)
53 #endif
54                 )
55                 {
56                         if (self.wait >= 0)
57                         {
58                                 if (self.state == STATE_DOWN)
59                         if (self.classname == "door")
60                         {
61                                 door_go_up ();
62                         } else
63                         {
64                                 door_rotating_go_up ();
65                         }
66                                 else
67                         if (self.classname == "door")
68                         {
69                                 door_go_down ();
70                         } else
71                         {
72                                 door_rotating_go_down ();
73                         }
74                         }
75                 }
76 #ifdef SVQC
77                 else
78                 {
79                         //gib dying stuff just to make sure
80                         if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
81                                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
82                 }
83 #endif
84         }
85 }
86
87 void door_hit_top()
88 {SELFPARAM();
89         if (self.noise1 != "")
90                 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
91         self.state = STATE_TOP;
92         if (self.spawnflags & DOOR_TOGGLE)
93                 return;         // don't come down automatically
94         if (self.classname == "door")
95         {
96                 self.SUB_THINK = door_go_down;
97         } else
98         {
99                 self.SUB_THINK = door_rotating_go_down;
100         }
101         self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
102 }
103
104 void door_hit_bottom()
105 {SELFPARAM();
106         if (self.noise1 != "")
107                 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
108         self.state = STATE_BOTTOM;
109 }
110
111 void door_go_down()
112 {SELFPARAM();
113         if (self.noise2 != "")
114                 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
115         if (self.max_health)
116         {
117                 self.takedamage = DAMAGE_YES;
118                 self.health = self.max_health;
119         }
120
121         self.state = STATE_DOWN;
122         SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
123 }
124
125 void door_go_up()
126 {SELFPARAM();
127         if (self.state == STATE_UP)
128                 return;         // already going up
129
130         if (self.state == STATE_TOP)
131         {       // reset top wait time
132                 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
133                 return;
134         }
135
136         if (self.noise2 != "")
137                 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
138         self.state = STATE_UP;
139         SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
140
141         string oldmessage;
142         oldmessage = self.message;
143         self.message = "";
144         SUB_UseTargets();
145         self.message = oldmessage;
146 }
147
148
149 /*
150 =============================================================================
151
152 ACTIVATION FUNCTIONS
153
154 =============================================================================
155 */
156
157 bool door_check_keys(entity door, entity player)
158 {
159         if(door.owner)
160                 door = door.owner;
161
162         // no key needed
163         if(!door.itemkeys)
164                 return true;
165
166         // this door require a key
167         // only a player can have a key
168         if(!IS_PLAYER(player))
169                 return false;
170
171         int valid = (door.itemkeys & player.itemkeys);
172         door.itemkeys &= ~valid; // only some of the needed keys were given
173
174         if(!door.itemkeys)
175         {
176 #ifdef SVQC
177                 play2(player, SND(TALK));
178                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
179 #endif
180                 return true;
181         }
182
183         if(!valid)
184         {
185 #ifdef SVQC
186                 if(player.key_door_messagetime <= time)
187                 {
188                         play2(player, door.noise3);
189                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
190                         player.key_door_messagetime = time + 2;
191                 }
192 #endif
193                 return false;
194         }
195
196         // door needs keys the player doesn't have
197 #ifdef SVQC
198         if(player.key_door_messagetime <= time)
199         {
200                 play2(player, door.noise3);
201                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
202                 player.key_door_messagetime = time + 2;
203         }
204 #endif
205
206         return false;
207 }
208
209 void door_fire()
210 {SELFPARAM();
211         entity  starte;
212
213         if (self.owner != self)
214                 objerror ("door_fire: self.owner != self");
215
216         if (self.spawnflags & DOOR_TOGGLE)
217         {
218                 if (self.state == STATE_UP || self.state == STATE_TOP)
219                 {
220                         starte = self;
221                         do
222                         {
223                                 if (self.classname == "door")
224                                 {
225                                         door_go_down ();
226                                 }
227                                 else
228                                 {
229                                         door_rotating_go_down ();
230                                 }
231                                 setself(self.enemy);
232                         } while ( (self != starte) && (self != world) );
233                         setself(this);
234                         return;
235                 }
236         }
237
238 // trigger all paired doors
239         starte = self;
240         do
241         {
242                 if (self.classname == "door")
243                 {
244                         door_go_up ();
245                 } else
246                 {
247                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
248                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
249                         {
250                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
251                                 self.pos2 = '0 0 0' - self.pos2;
252                         }
253                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
254                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
255                                 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
256                         {
257                                 door_rotating_go_up ();
258                         }
259                 }
260                 setself(self.enemy);
261         } while ( (self != starte) && (self != world) );
262         setself(this);
263 }
264
265 void door_use()
266 {SELFPARAM();
267         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
268
269         if (self.owner)
270         {
271                 WITH(entity, self, self.owner, door_fire());
272         }
273 }
274
275 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
276 {SELFPARAM();
277         if(self.spawnflags & DOOR_NOSPLASH)
278                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
279                         return;
280         self.health = self.health - damage;
281
282         if (self.itemkeys)
283         {
284                 // don't allow opening doors through damage if keys are required
285                 return;
286         }
287
288         if (self.health <= 0)
289         {
290                 self.owner.health = self.owner.max_health;
291                 self.owner.takedamage = DAMAGE_NO;      // wil be reset upon return
292                 WITH(entity, self, self.owner, door_use());
293         }
294 }
295
296 .float door_finished;
297
298 /*
299 ================
300 door_touch
301
302 Prints messages
303 ================
304 */
305
306 void door_touch()
307 {SELFPARAM();
308         if (!IS_PLAYER(other))
309                 return;
310         if (self.owner.door_finished > time)
311                 return;
312
313         self.owner.door_finished = time + 2;
314
315 #ifdef SVQC
316         if (!(self.owner.dmg) && (self.owner.message != ""))
317         {
318                 if (IS_CLIENT(other))
319                         centerprint(other, self.owner.message);
320                 play2(other, self.owner.noise);
321         }
322 #endif
323 }
324
325 void door_generic_plat_blocked()
326 {SELFPARAM();
327
328         if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
329 #ifdef SVQC
330                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
331 #endif
332         }
333         else
334         {
335
336 #ifdef SVQC
337                 if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
338                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
339 #endif
340
341                  //Dont chamge direction for dead or dying stuff
342                 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
343                 {
344                         if (self.wait >= 0)
345                         {
346                                 if (self.state == STATE_DOWN)
347                                         door_rotating_go_up ();
348                                 else
349                                         door_rotating_go_down ();
350                         }
351                 }
352 #ifdef SVQC
353                 else
354                 {
355                         //gib dying stuff just to make sure
356                         if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
357                                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
358                 }
359 #endif
360         }
361 }
362
363 void door_rotating_hit_top()
364 {SELFPARAM();
365         if (self.noise1 != "")
366                 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
367         self.state = STATE_TOP;
368         if (self.spawnflags & DOOR_TOGGLE)
369                 return;         // don't come down automatically
370         self.SUB_THINK = door_rotating_go_down;
371         self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
372 }
373
374 void door_rotating_hit_bottom()
375 {SELFPARAM();
376         if (self.noise1 != "")
377                 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
378         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
379         {
380                 self.pos2 = '0 0 0' - self.pos2;
381                 self.lip = 0;
382         }
383         self.state = STATE_BOTTOM;
384 }
385
386 void door_rotating_go_down()
387 {SELFPARAM();
388         if (self.noise2 != "")
389                 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
390         if (self.max_health)
391         {
392                 self.takedamage = DAMAGE_YES;
393                 self.health = self.max_health;
394         }
395
396         self.state = STATE_DOWN;
397         SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
398 }
399
400 void door_rotating_go_up()
401 {SELFPARAM();
402         if (self.state == STATE_UP)
403                 return;         // already going up
404
405         if (self.state == STATE_TOP)
406         {       // reset top wait time
407                 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
408                 return;
409         }
410         if (self.noise2 != "")
411                 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
412         self.state = STATE_UP;
413         SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
414
415         string oldmessage;
416         oldmessage = self.message;
417         self.message = "";
418         SUB_UseTargets();
419         self.message = oldmessage;
420 }
421
422
423 /*
424 =========================================
425 door trigger
426
427 Spawned if a door lacks a real activator
428 =========================================
429 */
430
431 void door_trigger_touch()
432 {SELFPARAM();
433         if (other.health < 1)
434 #ifdef SVQC
435                 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
436 #elif defined(CSQC)
437                 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
438 #endif
439                         return;
440
441         if (time < self.door_finished)
442                 return;
443
444         // check if door is locked
445         if (!door_check_keys(self, other))
446                 return;
447
448         self.door_finished = time + 1;
449
450         activator = other;
451
452         setself(self.owner);
453         door_use ();
454 }
455
456 void door_spawnfield(vector fmins, vector fmaxs)
457 {SELFPARAM();
458         entity  trigger;
459         vector  t1 = fmins, t2 = fmaxs;
460
461         trigger = new(doortriggerfield);
462         trigger.movetype = MOVETYPE_NONE;
463         trigger.solid = SOLID_TRIGGER;
464         trigger.owner = self;
465 #ifdef SVQC
466         trigger.touch = door_trigger_touch;
467 #elif defined(CSQC)
468         trigger.trigger_touch = door_trigger_touch;
469         trigger.draw = trigger_draw_generic;
470         trigger.drawmask = MASK_NORMAL;
471 #endif
472
473         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
474 }
475
476
477 /*
478 =============
479 LinkDoors
480
481
482 =============
483 */
484
485 entity LinkDoors_nextent(entity cur, entity near, entity pass)
486 {SELFPARAM();
487         while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
488         {
489         }
490         return cur;
491 }
492
493 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
494 {
495         float DELTA = 4;
496         if((e1.absmin_x > e2.absmax_x + DELTA)
497         || (e1.absmin_y > e2.absmax_y + DELTA)
498         || (e1.absmin_z > e2.absmax_z + DELTA)
499         || (e2.absmin_x > e1.absmax_x + DELTA)
500         || (e2.absmin_y > e1.absmax_y + DELTA)
501         || (e2.absmin_z > e1.absmax_z + DELTA)
502         ) { return false; }
503         return true;
504 }
505
506 #ifdef SVQC
507 void door_link();
508 #endif
509 void LinkDoors()
510 {SELFPARAM();
511         entity  t;
512         vector  cmins, cmaxs;
513
514 #ifdef SVQC
515         door_link();
516 #endif
517
518         if (self.enemy)
519                 return;         // already linked by another door
520         if (self.spawnflags & 4)
521         {
522                 self.owner = self.enemy = self;
523
524                 if (self.health)
525                         return;
526                 IFTARGETED
527                         return;
528                 if (self.items)
529                         return;
530
531                 door_spawnfield(self.absmin, self.absmax);
532
533                 return;         // don't want to link this door
534         }
535
536         FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
537
538         // set owner, and make a loop of the chain
539         LOG_TRACE("LinkDoors: linking doors:");
540         for(t = self; ; t = t.enemy)
541         {
542                 LOG_TRACE(" ", etos(t));
543                 t.owner = self;
544                 if(t.enemy == world)
545                 {
546                         t.enemy = self;
547                         break;
548                 }
549         }
550         LOG_TRACE("\n");
551
552         // collect health, targetname, message, size
553         cmins = self.absmin;
554         cmaxs = self.absmax;
555         for(t = self; ; t = t.enemy)
556         {
557                 if(t.health && !self.health)
558                         self.health = t.health;
559                 if((t.targetname != "") && (self.targetname == ""))
560                         self.targetname = t.targetname;
561                 if((t.message != "") && (self.message == ""))
562                         self.message = t.message;
563                 if (t.absmin_x < cmins_x)
564                         cmins_x = t.absmin_x;
565                 if (t.absmin_y < cmins_y)
566                         cmins_y = t.absmin_y;
567                 if (t.absmin_z < cmins_z)
568                         cmins_z = t.absmin_z;
569                 if (t.absmax_x > cmaxs_x)
570                         cmaxs_x = t.absmax_x;
571                 if (t.absmax_y > cmaxs_y)
572                         cmaxs_y = t.absmax_y;
573                 if (t.absmax_z > cmaxs_z)
574                         cmaxs_z = t.absmax_z;
575                 if(t.enemy == self)
576                         break;
577         }
578
579         // distribute health, targetname, message
580         for(t = self; t; t = t.enemy)
581         {
582                 t.health = self.health;
583                 t.targetname = self.targetname;
584                 t.message = self.message;
585                 if(t.enemy == self)
586                         break;
587         }
588
589         // shootable, or triggered doors just needed the owner/enemy links,
590         // they don't spawn a field
591
592         if (self.health)
593                 return;
594         IFTARGETED
595                 return;
596         if (self.items)
597                 return;
598
599         door_spawnfield(cmins, cmaxs);
600 }
601
602 #ifdef SVQC
603 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
604 if two doors touch, they are assumed to be connected and operate as a unit.
605
606 TOGGLE causes the door to wait in both the start and end states for a trigger event.
607
608 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).
609
610 GOLD_KEY causes the door to open only if the activator holds a gold key.
611
612 SILVER_KEY causes the door to open only if the activator holds a silver key.
613
614 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
615 "angle"         determines the opening direction
616 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
617 "health"        if set, door must be shot open
618 "speed"         movement speed (100 default)
619 "wait"          wait before returning (3 default, -1 = never return)
620 "lip"           lip remaining at end of move (8 default)
621 "dmg"           damage to inflict when blocked (2 default)
622 "sounds"
623 0)      no sound
624 1)      stone
625 2)      base
626 3)      stone chain
627 4)      screechy metal
628 FIXME: only one sound set available at the time being
629
630 */
631
632 float door_send(entity to, float sf)
633 {SELFPARAM();
634         WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
635         WriteByte(MSG_ENTITY, sf);
636
637         if(sf & SF_TRIGGER_INIT)
638         {
639                 WriteString(MSG_ENTITY, self.classname);
640                 WriteByte(MSG_ENTITY, self.spawnflags);
641
642                 WriteString(MSG_ENTITY, self.model);
643
644                 trigger_common_write(true);
645
646                 WriteCoord(MSG_ENTITY, self.pos1_x);
647                 WriteCoord(MSG_ENTITY, self.pos1_y);
648                 WriteCoord(MSG_ENTITY, self.pos1_z);
649                 WriteCoord(MSG_ENTITY, self.pos2_x);
650                 WriteCoord(MSG_ENTITY, self.pos2_y);
651                 WriteCoord(MSG_ENTITY, self.pos2_z);
652
653                 WriteCoord(MSG_ENTITY, self.size_x);
654                 WriteCoord(MSG_ENTITY, self.size_y);
655                 WriteCoord(MSG_ENTITY, self.size_z);
656
657                 WriteShort(MSG_ENTITY, self.wait);
658                 WriteShort(MSG_ENTITY, self.speed);
659                 WriteByte(MSG_ENTITY, self.lip);
660                 WriteByte(MSG_ENTITY, self.state);
661                 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
662         }
663
664         if(sf & SF_TRIGGER_RESET)
665         {
666                 // client makes use of this, we do not
667         }
668
669         if(sf & SF_TRIGGER_UPDATE)
670         {
671                 WriteCoord(MSG_ENTITY, self.origin_x);
672                 WriteCoord(MSG_ENTITY, self.origin_y);
673                 WriteCoord(MSG_ENTITY, self.origin_z);
674
675                 WriteCoord(MSG_ENTITY, self.pos1_x);
676                 WriteCoord(MSG_ENTITY, self.pos1_y);
677                 WriteCoord(MSG_ENTITY, self.pos1_z);
678                 WriteCoord(MSG_ENTITY, self.pos2_x);
679                 WriteCoord(MSG_ENTITY, self.pos2_y);
680                 WriteCoord(MSG_ENTITY, self.pos2_z);
681         }
682
683         return true;
684 }
685
686 void door_link()
687 {
688         // set size now, as everything is loaded
689         //FixSize(self);
690         //Net_LinkEntity(self, false, 0, door_send);
691 }
692 #endif
693
694 void door_init_startopen()
695 {SELFPARAM();
696         SUB_SETORIGIN(self, self.pos2);
697         self.pos2 = self.pos1;
698         self.pos1 = self.origin;
699
700 #ifdef SVQC
701         self.SendFlags |= SF_TRIGGER_UPDATE;
702 #endif
703 }
704
705 void door_reset()
706 {SELFPARAM();
707         SUB_SETORIGIN(self, self.pos1);
708         self.SUB_VELOCITY = '0 0 0';
709         self.state = STATE_BOTTOM;
710         self.SUB_THINK = func_null;
711         self.SUB_NEXTTHINK = 0;
712
713 #ifdef SVQC
714         self.SendFlags |= SF_TRIGGER_RESET;
715 #endif
716 }
717
718 #ifdef SVQC
719
720 // spawnflags require key (for now only func_door)
721 spawnfunc(func_door)
722 {
723         // Quake 1 keys compatibility
724         if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
725                 self.itemkeys |= ITEM_KEY_BIT(0);
726         if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
727                 self.itemkeys |= ITEM_KEY_BIT(1);
728
729         SetMovedir(self);
730
731         self.max_health = self.health;
732         if (!InitMovingBrushTrigger())
733                 return;
734         self.effects |= EF_LOWPRECISION;
735         self.classname = "door";
736
737         if(self.noise == "")
738                 self.noise = "misc/talk.wav";
739         if(self.noise3 == "")
740                 self.noise3 = "misc/talk.wav";
741         precache_sound(self.noise);
742         precache_sound(self.noise3);
743
744         self.blocked = door_blocked;
745         self.use = door_use;
746
747         if(self.dmg && (self.message == ""))
748                 self.message = "was squished";
749         if(self.dmg && (self.message2 == ""))
750                 self.message2 = "was squished by";
751
752         if (self.sounds > 0)
753         {
754                 precache_sound ("plats/medplat1.wav");
755                 precache_sound ("plats/medplat2.wav");
756                 self.noise2 = "plats/medplat1.wav";
757                 self.noise1 = "plats/medplat2.wav";
758         }
759
760         if (!self.speed)
761                 self.speed = 100;
762         if (!self.wait)
763                 self.wait = 3;
764         if (!self.lip)
765                 self.lip = 8;
766
767         self.pos1 = self.SUB_ORIGIN;
768         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
769
770         if(self.spawnflags & DOOR_NONSOLID)
771                 self.solid = SOLID_NOT;
772
773 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
774 // but spawn in the open position
775         if (self.spawnflags & DOOR_START_OPEN)
776                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
777
778         self.state = STATE_BOTTOM;
779
780         if (self.health)
781         {
782                 self.takedamage = DAMAGE_YES;
783                 self.event_damage = door_damage;
784         }
785
786         if (self.items)
787                 self.wait = -1;
788
789         self.touch = door_touch;
790
791 // LinkDoors can't be done until all of the doors have been spawned, so
792 // the sizes can be detected properly.
793         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
794
795         self.reset = door_reset;
796 }
797
798 #elif defined(CSQC)
799
800 void door_draw(entity this)
801 {
802         Movetype_Physics_NoMatchServer();
803
804         trigger_draw_generic(this);
805 }
806
807 void ent_door()
808 {SELFPARAM();
809         float sf = ReadByte();
810
811         if(sf & SF_TRIGGER_INIT)
812         {
813                 self.classname = strzone(ReadString());
814                 self.spawnflags = ReadByte();
815
816                 self.mdl = strzone(ReadString());
817                 _setmodel(self, self.mdl);
818
819                 trigger_common_read(true);
820
821                 self.pos1_x = ReadCoord();
822                 self.pos1_y = ReadCoord();
823                 self.pos1_z = ReadCoord();
824                 self.pos2_x = ReadCoord();
825                 self.pos2_y = ReadCoord();
826                 self.pos2_z = ReadCoord();
827
828                 self.size_x = ReadCoord();
829                 self.size_y = ReadCoord();
830                 self.size_z = ReadCoord();
831
832                 self.wait = ReadShort();
833                 self.speed = ReadShort();
834                 self.lip = ReadByte();
835                 self.state = ReadByte();
836                 self.SUB_LTIME = ReadCoord();
837
838                 self.solid = SOLID_BSP;
839                 self.movetype = MOVETYPE_PUSH;
840                 self.trigger_touch = door_touch;
841                 self.draw = door_draw;
842                 self.drawmask = MASK_NORMAL;
843                 self.use = door_use;
844
845                 LinkDoors();
846
847                 if(self.spawnflags & DOOR_START_OPEN)
848                         door_init_startopen();
849
850                 self.move_time = time;
851                 self.move_origin = self.origin;
852                 self.move_movetype = MOVETYPE_PUSH;
853                 self.move_angles = self.angles;
854                 self.move_blocked = door_blocked;
855         }
856
857         if(sf & SF_TRIGGER_RESET)
858         {
859                 door_reset();
860         }
861
862         if(sf & SF_TRIGGER_UPDATE)
863         {
864                 self.origin_x = ReadCoord();
865                 self.origin_y = ReadCoord();
866                 self.origin_z = ReadCoord();
867                 setorigin(self, self.origin);
868                 self.move_origin = self.origin;
869
870                 self.pos1_x = ReadCoord();
871                 self.pos1_y = ReadCoord();
872                 self.pos1_z = ReadCoord();
873                 self.pos2_x = ReadCoord();
874                 self.pos2_y = ReadCoord();
875                 self.pos2_z = ReadCoord();
876         }
877 }
878
879 #endif