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