]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/triggers/func/door.qc
Clean up door key handling
[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, SND(TALK));
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, SND(TALK));
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
297 /*
298 ================
299 door_touch
300
301 Prints messages
302 ================
303 */
304
305 void door_touch()
306 {SELFPARAM();
307         if (!IS_PLAYER(other))
308                 return;
309         if (self.owner.attack_finished_single > time)
310                 return;
311
312         self.owner.attack_finished_single = time + 2;
313
314 #ifdef SVQC
315         if (!(self.owner.dmg) && (self.owner.message != ""))
316         {
317                 if (IS_CLIENT(other))
318                         centerprint(other, self.owner.message);
319                 play2(other, SND(TALK));
320         }
321 #endif
322 }
323
324 void door_generic_plat_blocked()
325 {SELFPARAM();
326
327         if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
328 #ifdef SVQC
329                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
330 #endif
331         }
332         else
333         {
334
335 #ifdef SVQC
336                 if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
337                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
338 #endif
339
340                  //Dont chamge direction for dead or dying stuff
341                 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
342                 {
343                         if (self.wait >= 0)
344                         {
345                                 if (self.state == STATE_DOWN)
346                                         door_rotating_go_up ();
347                                 else
348                                         door_rotating_go_down ();
349                         }
350                 }
351 #ifdef SVQC
352                 else
353                 {
354                         //gib dying stuff just to make sure
355                         if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
356                                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
357                 }
358 #endif
359         }
360 }
361
362 void door_rotating_hit_top()
363 {SELFPARAM();
364         if (self.noise1 != "")
365                 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
366         self.state = STATE_TOP;
367         if (self.spawnflags & DOOR_TOGGLE)
368                 return;         // don't come down automatically
369         self.SUB_THINK = door_rotating_go_down;
370         self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
371 }
372
373 void door_rotating_hit_bottom()
374 {SELFPARAM();
375         if (self.noise1 != "")
376                 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
377         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
378         {
379                 self.pos2 = '0 0 0' - self.pos2;
380                 self.lip = 0;
381         }
382         self.state = STATE_BOTTOM;
383 }
384
385 void door_rotating_go_down()
386 {SELFPARAM();
387         if (self.noise2 != "")
388                 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
389         if (self.max_health)
390         {
391                 self.takedamage = DAMAGE_YES;
392                 self.health = self.max_health;
393         }
394
395         self.state = STATE_DOWN;
396         SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
397 }
398
399 void door_rotating_go_up()
400 {SELFPARAM();
401         if (self.state == STATE_UP)
402                 return;         // already going up
403
404         if (self.state == STATE_TOP)
405         {       // reset top wait time
406                 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
407                 return;
408         }
409         if (self.noise2 != "")
410                 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
411         self.state = STATE_UP;
412         SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
413
414         string oldmessage;
415         oldmessage = self.message;
416         self.message = "";
417         SUB_UseTargets();
418         self.message = oldmessage;
419 }
420
421
422 /*
423 =========================================
424 door trigger
425
426 Spawned if a door lacks a real activator
427 =========================================
428 */
429
430 void door_trigger_touch()
431 {SELFPARAM();
432         if (other.health < 1)
433 #ifdef SVQC
434                 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
435 #elif defined(CSQC)
436                 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
437 #endif
438                         return;
439
440         if (time < self.attack_finished_single)
441                 return;
442
443         // check if door is locked
444         if (!door_check_keys(self, other))
445                 return;
446
447         self.attack_finished_single = time + 1;
448
449         activator = other;
450
451         setself(self.owner);
452         door_use ();
453 }
454
455 void door_spawnfield(vector fmins, vector fmaxs)
456 {SELFPARAM();
457         entity  trigger;
458         vector  t1 = fmins, t2 = fmaxs;
459
460         trigger = spawn();
461         trigger.classname = "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         WriteByte(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 ();
730
731         self.max_health = self.health;
732         if (!InitMovingBrushTrigger())
733                 return;
734         self.effects |= EF_LOWPRECISION;
735         self.classname = "door";
736
737         self.blocked = door_blocked;
738         self.use = door_use;
739
740         if(self.dmg && (self.message == ""))
741                 self.message = "was squished";
742         if(self.dmg && (self.message2 == ""))
743                 self.message2 = "was squished by";
744
745         if (self.sounds > 0)
746         {
747                 precache_sound ("plats/medplat1.wav");
748                 precache_sound ("plats/medplat2.wav");
749                 self.noise2 = "plats/medplat1.wav";
750                 self.noise1 = "plats/medplat2.wav";
751         }
752
753         if (!self.speed)
754                 self.speed = 100;
755         if (!self.wait)
756                 self.wait = 3;
757         if (!self.lip)
758                 self.lip = 8;
759
760         self.pos1 = self.SUB_ORIGIN;
761         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
762
763         if(self.spawnflags & DOOR_NONSOLID)
764                 self.solid = SOLID_NOT;
765
766 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
767 // but spawn in the open position
768         if (self.spawnflags & DOOR_START_OPEN)
769                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
770
771         self.state = STATE_BOTTOM;
772
773         if (self.health)
774         {
775                 self.takedamage = DAMAGE_YES;
776                 self.event_damage = door_damage;
777         }
778
779         if (self.items)
780                 self.wait = -1;
781
782         self.touch = door_touch;
783
784 // LinkDoors can't be done until all of the doors have been spawned, so
785 // the sizes can be detected properly.
786         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
787
788         self.reset = door_reset;
789 }
790
791 #elif defined(CSQC)
792
793 void door_draw(entity this)
794 {
795         Movetype_Physics_NoMatchServer();
796
797         trigger_draw_generic(this);
798 }
799
800 void ent_door()
801 {SELFPARAM();
802         float sf = ReadByte();
803
804         if(sf & SF_TRIGGER_INIT)
805         {
806                 self.classname = strzone(ReadString());
807                 self.spawnflags = ReadByte();
808
809                 self.mdl = strzone(ReadString());
810                 _setmodel(self, self.mdl);
811
812                 trigger_common_read(true);
813
814                 self.pos1_x = ReadCoord();
815                 self.pos1_y = ReadCoord();
816                 self.pos1_z = ReadCoord();
817                 self.pos2_x = ReadCoord();
818                 self.pos2_y = ReadCoord();
819                 self.pos2_z = ReadCoord();
820
821                 self.size_x = ReadCoord();
822                 self.size_y = ReadCoord();
823                 self.size_z = ReadCoord();
824
825                 self.wait = ReadShort();
826                 self.speed = ReadShort();
827                 self.lip = ReadByte();
828                 self.state = ReadByte();
829                 self.SUB_LTIME = ReadCoord();
830
831                 self.solid = SOLID_BSP;
832                 self.movetype = MOVETYPE_PUSH;
833                 self.trigger_touch = door_touch;
834                 self.draw = door_draw;
835                 self.drawmask = MASK_NORMAL;
836                 self.use = door_use;
837
838                 LinkDoors();
839
840                 if(self.spawnflags & DOOR_START_OPEN)
841                         door_init_startopen();
842
843                 self.move_time = time;
844                 self.move_origin = self.origin;
845                 self.move_movetype = MOVETYPE_PUSH;
846                 self.move_angles = self.angles;
847                 self.move_blocked = door_blocked;
848         }
849
850         if(sf & SF_TRIGGER_RESET)
851         {
852                 door_reset();
853         }
854
855         if(sf & SF_TRIGGER_UPDATE)
856         {
857                 self.origin_x = ReadCoord();
858                 self.origin_y = ReadCoord();
859                 self.origin_z = ReadCoord();
860                 setorigin(self, self.origin);
861                 self.move_origin = self.origin;
862
863                 self.pos1_x = ReadCoord();
864                 self.pos1_y = ReadCoord();
865                 self.pos1_z = ReadCoord();
866                 self.pos2_x = ReadCoord();
867                 self.pos2_y = ReadCoord();
868                 self.pos2_z = ReadCoord();
869         }
870 }
871
872 #endif