]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapobjects/func/door.qc
Transifex autosync
[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_use(entity this, entity actor, entity trigger)
217 {
218         //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
219
220         if (!this.owner)
221                 return;
222         this = this.owner;
223
224         if (this.spawnflags & DOOR_TOGGLE)
225         {
226                 if (this.state == STATE_UP || this.state == STATE_TOP)
227                 {
228                         entity e = this;
229                         do {
230                                 if (e.classname == "door") {
231                                         door_go_down(e);
232                                 } else {
233                                         door_rotating_go_down(e);
234                                 }
235                                 e = e.enemy;
236                         } while ((e != this) && (e != NULL));
237                         return;
238                 }
239         }
240
241 // trigger all paired doors
242         entity e = this;
243         do {
244                 if (e.classname == "door") {
245                         door_go_up(e, actor, trigger);
246                 } else {
247                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
248                         if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
249                                 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
250                                 e.pos2 = '0 0 0' - e.pos2;
251                         }
252                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
253                         if (!((e.spawnflags & DOOR_ROTATING_BIDIR) &&  (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN
254                                 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
255                         {
256                                 door_rotating_go_up(e, trigger);
257                         }
258                 }
259                 e = e.enemy;
260         } while ((e != this) && (e != NULL));
261 }
262
263 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
264 {
265         if(this.spawnflags & NOSPLASH)
266                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
267                         return;
268         TakeResource(this, RES_HEALTH, damage);
269
270         if (this.itemkeys)
271         {
272                 // don't allow opening doors through damage if keys are required
273                 return;
274         }
275
276         if (GetResource(this, RES_HEALTH) <= 0)
277         {
278                 SetResourceExplicit(this.owner, RES_HEALTH, this.owner.max_health);
279                 this.owner.takedamage = DAMAGE_NO;      // will be reset upon return
280                 door_use(this.owner, attacker, NULL);
281         }
282 }
283
284 .float door_finished;
285
286 /*
287 ================
288 door_touch
289
290 Prints messages
291 ================
292 */
293
294 void door_touch(entity this, entity toucher)
295 {
296         if (!IS_PLAYER(toucher))
297                 return;
298         if (this.owner.door_finished > time)
299                 return;
300
301         this.owner.door_finished = time + 2;
302
303 #ifdef SVQC
304         if (!(this.owner.dmg) && (this.owner.message != ""))
305         {
306                 if (IS_CLIENT(toucher))
307                         centerprint(toucher, this.owner.message);
308                 play2(toucher, this.owner.noise);
309         }
310 #endif
311 }
312
313 void door_generic_plat_blocked(entity this, entity blocker)
314 {
315         if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
316 #ifdef SVQC
317                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
318 #endif
319         }
320         else
321         {
322
323 #ifdef SVQC
324                 if((this.dmg) && (blocker.takedamage == DAMAGE_YES))    // Shall we bite?
325                         Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
326 #endif
327
328                  //Dont chamge direction for dead or dying stuff
329                 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
330                 {
331                         if (this.wait >= 0)
332                         {
333                                 if (this.state == STATE_DOWN)
334                                         door_rotating_go_up (this, blocker);
335                                 else
336                                         door_rotating_go_down (this);
337                         }
338                 }
339 #ifdef SVQC
340                 else
341                 {
342                         //gib dying stuff just to make sure
343                         if((this.dmg) && (blocker.takedamage != DAMAGE_NO))    // Shall we bite?
344                                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
345                 }
346 #endif
347         }
348 }
349
350 /*
351 =========================================
352 door trigger
353
354 Spawned if a door lacks a real activator
355 =========================================
356 */
357
358 void door_trigger_touch(entity this, entity toucher)
359 {
360         if (GetResource(toucher, RES_HEALTH) < 1)
361 #ifdef SVQC
362                 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
363 #elif defined(CSQC)
364                 if(!((IS_CLIENT(toucher) || toucher.classname == "ENT_CLIENT_PROJECTILE") && !IS_DEAD(toucher)))
365 #endif
366                         return;
367
368         if (this.owner.state == STATE_UP)
369                 return;
370
371         // check if door is locked
372         if (!door_check_keys(this, toucher))
373                 return;
374
375         if (this.owner.state == STATE_TOP)
376         {
377                 if (this.owner.nextthink < this.owner.ltime + this.owner.wait)
378                 {
379                         entity e = this.owner;
380                         do {
381                                 e.nextthink = e.ltime + e.wait;
382                                 e = e.enemy;
383                         } while (e != this.owner);
384                 }
385                 return;
386         }
387
388         door_use(this.owner, toucher, NULL);
389 }
390
391 void door_spawnfield(entity this, vector fmins, vector fmaxs)
392 {
393         entity  trigger;
394         vector  t1 = fmins, t2 = fmaxs;
395
396         trigger = new(doortriggerfield);
397         set_movetype(trigger, MOVETYPE_NONE);
398         trigger.solid = SOLID_TRIGGER;
399         trigger.owner = this;
400 #ifdef SVQC
401         settouch(trigger, door_trigger_touch);
402 #endif
403
404         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
405 }
406
407
408 /*
409 =============
410 LinkDoors
411
412
413 =============
414 */
415
416 entity LinkDoors_nextent(entity cur, entity near, entity pass)
417 {
418         while ((cur = find(cur, classname, pass.classname))
419         && ((!Q3COMPAT_COMMON && (cur.spawnflags & DOOR_DONT_LINK)) || cur.enemy))
420         {
421         }
422         return cur;
423 }
424
425 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
426 {
427         if(Q3COMPAT_COMMON)
428                 return e1.team == e2.team;
429
430         float DELTA = 4;
431         if((e1.absmin_x > e2.absmax_x + DELTA)
432         || (e1.absmin_y > e2.absmax_y + DELTA)
433         || (e1.absmin_z > e2.absmax_z + DELTA)
434         || (e2.absmin_x > e1.absmax_x + DELTA)
435         || (e2.absmin_y > e1.absmax_y + DELTA)
436         || (e2.absmin_z > e1.absmax_z + DELTA)
437         ) { return false; }
438         return true;
439 }
440
441 #ifdef SVQC
442 void door_link();
443 #endif
444 void LinkDoors(entity this)
445 {
446         entity  t;
447         vector  cmins, cmaxs;
448
449 #ifdef SVQC
450         door_link();
451 #endif
452
453         if (this.enemy)
454                 return;         // already linked by another door
455
456         // Q3 door linking is done for teamed doors only and is not affected by spawnflags or bmodel proximity
457         if ((!Q3COMPAT_COMMON && (this.spawnflags & DOOR_DONT_LINK)) || (Q3COMPAT_COMMON && !this.team))
458         {
459                 this.owner = this.enemy = this;
460
461                 if (GetResource(this, RES_HEALTH))
462                         return;
463                 if(this.targetname && this.targetname != "")
464                         return;
465                 if (this.items)
466                         return;
467
468                 door_spawnfield(this, this.absmin, this.absmax);
469
470                 return;         // don't want to link this door
471         }
472
473         FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
474
475         // set owner, and make a loop of the chain
476         LOG_TRACE("LinkDoors: linking doors:");
477         for(t = this; ; t = t.enemy)
478         {
479                 LOG_TRACE(" ", etos(t));
480                 t.owner = this;
481                 if(t.enemy == NULL)
482                 {
483                         t.enemy = this;
484                         break;
485                 }
486         }
487         LOG_TRACE("");
488
489         // collect health, targetname, message, size
490         cmins = this.absmin;
491         cmaxs = this.absmax;
492         for(t = this; ; t = t.enemy)
493         {
494                 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
495                         SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
496                 if((t.targetname != "") && (this.targetname == ""))
497                         this.targetname = t.targetname;
498                 if((t.message != "") && (this.message == ""))
499                         this.message = t.message;
500                 if (t.absmin_x < cmins_x)
501                         cmins_x = t.absmin_x;
502                 if (t.absmin_y < cmins_y)
503                         cmins_y = t.absmin_y;
504                 if (t.absmin_z < cmins_z)
505                         cmins_z = t.absmin_z;
506                 if (t.absmax_x > cmaxs_x)
507                         cmaxs_x = t.absmax_x;
508                 if (t.absmax_y > cmaxs_y)
509                         cmaxs_y = t.absmax_y;
510                 if (t.absmax_z > cmaxs_z)
511                         cmaxs_z = t.absmax_z;
512                 if(t.enemy == this)
513                         break;
514         }
515
516         // distribute health, targetname, message
517         for(t = this; t; t = t.enemy)
518         {
519                 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
520                 t.targetname = this.targetname;
521                 t.message = this.message;
522                 if(t.enemy == this)
523                         break;
524         }
525
526         // shootable, or triggered doors just needed the owner/enemy links,
527         // they don't spawn a field
528
529         if (GetResource(this, RES_HEALTH))
530                 return;
531         if(this.targetname && this.targetname != "")
532                 return;
533         if (this.items)
534                 return;
535
536         door_spawnfield(this, cmins, cmaxs);
537 }
538
539 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
540
541 #ifdef SVQC
542 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
543 if two doors touch, they are assumed to be connected and operate as a unit.
544
545 TOGGLE causes the door to wait in both the start and end states for a trigger event.
546
547 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).
548
549 GOLD_KEY causes the door to open only if the activator holds a gold key.
550
551 SILVER_KEY causes the door to open only if the activator holds a silver key.
552
553 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
554 "angle"         determines the opening direction
555 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
556 "health"        if set, door must be shot open
557 "speed"         movement speed (100 default)
558 "wait"          wait before returning (3 default, -1 = never return)
559 "lip"           lip remaining at end of move (8 default)
560 "dmg"           damage to inflict when blocked (0 default)
561 "sounds"
562 0)      no sound
563 1)      stone
564 2)      base
565 3)      stone chain
566 4)      screechy metal
567 FIXME: only one sound set available at the time being
568
569 */
570
571 float door_send(entity this, entity to, float sf)
572 {
573         WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
574         WriteByte(MSG_ENTITY, sf);
575
576         if(sf & SF_TRIGGER_INIT)
577         {
578                 WriteString(MSG_ENTITY, this.classname);
579                 WriteByte(MSG_ENTITY, this.spawnflags);
580
581                 WriteString(MSG_ENTITY, this.model);
582
583                 trigger_common_write(this, true);
584
585                 WriteVector(MSG_ENTITY, this.pos1);
586                 WriteVector(MSG_ENTITY, this.pos2);
587
588                 WriteVector(MSG_ENTITY, this.size);
589
590                 WriteShort(MSG_ENTITY, this.wait);
591                 WriteShort(MSG_ENTITY, this.speed);
592                 WriteByte(MSG_ENTITY, this.lip);
593                 WriteByte(MSG_ENTITY, this.state);
594                 WriteCoord(MSG_ENTITY, this.ltime);
595         }
596
597         if(sf & SF_TRIGGER_RESET)
598         {
599                 // client makes use of this, we do not
600         }
601
602         if(sf & SF_TRIGGER_UPDATE)
603         {
604                 WriteVector(MSG_ENTITY, this.origin);
605
606                 WriteVector(MSG_ENTITY, this.pos1);
607                 WriteVector(MSG_ENTITY, this.pos2);
608         }
609
610         return true;
611 }
612
613 void door_link()
614 {
615         //Net_LinkEntity(this, false, 0, door_send);
616 }
617
618 void door_init_keys(entity this)
619 {
620         // Quake 1 and QL keys compatibility
621         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
622                 this.itemkeys |= BIT(0);
623         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
624                 this.itemkeys |= BIT(1);
625 }
626 #endif
627
628 void door_init_startopen(entity this)
629 {
630         setorigin(this, this.pos2);
631         this.pos2 = this.pos1;
632         this.pos1 = this.origin;
633
634 #ifdef SVQC
635         this.SendFlags |= SF_TRIGGER_UPDATE;
636 #endif
637 }
638
639 void door_reset(entity this)
640 {
641         setorigin(this, this.pos1);
642         this.velocity = '0 0 0';
643         this.state = STATE_BOTTOM;
644         setthink(this, func_null);
645         this.nextthink = 0;
646
647 #ifdef SVQC
648         this.SendFlags |= SF_TRIGGER_RESET;
649         door_init_keys(this);
650 #endif
651 }
652
653 #ifdef SVQC
654
655 // common code for func_door and func_door_rotating spawnfuncs
656 void door_init_shared(entity this)
657 {
658         this.max_health = GetResource(this, RES_HEALTH);
659
660         // unlock sound
661         if(this.noise == "")
662         {
663                 this.noise = "misc/talk.wav";
664         }
665         // door still locked sound
666         if(this.noise3 == "")
667         {
668                 this.noise3 = "misc/talk.wav";
669         }
670         precache_sound(this.noise);
671         precache_sound(this.noise3);
672
673         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
674         {
675                 this.message = "was squished";
676         }
677         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
678         {
679                 this.message2 = "was squished by";
680         }
681
682         // TODO: other soundpacks
683         if (this.sounds > 0 || q3compat)
684         {
685                 // Doors in Q3 always have sounds (they're hard coded)
686                 this.noise2 = "plats/medplat1.wav";
687                 this.noise1 = "plats/medplat2.wav";
688         }
689
690         if (q3compat)
691         {
692                 // CPMA adds these fields for overriding the Q3 default sounds
693                 string s = GetField_fullspawndata(this, "sound_start", true);
694                 string e = GetField_fullspawndata(this, "sound_end", true);
695
696                 // Quake Live adds these ones, because of course it had to be different from CPMA
697                 if (!s) s = GetField_fullspawndata(this, "startsound", true);
698                 if (!e) e = GetField_fullspawndata(this, "endsound", true);
699
700                 if (s)
701                         this.noise2 = strzone(s);
702                 else
703                 {
704                         // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
705                         s = "sound/movers/doors/dr1_strt.wav";
706                         if (FindFileInMapPack(s))
707                                 this.noise2 = s;
708                 }
709
710                 if (e)
711                         this.noise1 = strzone(e);
712                 else
713                 {
714                         e = "sound/movers/doors/dr1_end.wav";
715                         if (FindFileInMapPack(e))
716                                 this.noise1 = e;
717                 }
718         }
719
720         // sound when door stops moving
721         if(this.noise1 && this.noise1 != "")
722         {
723                 precache_sound(this.noise1);
724         }
725         // sound when door is moving
726         if(this.noise2 && this.noise2 != "")
727         {
728                 precache_sound(this.noise2);
729         }
730
731         if(autocvar_sv_doors_always_open)
732         {
733                 this.wait = -1;
734         }
735         else if (!this.wait)
736         {
737                 this.wait = q3compat ? 2 : 3;
738         }
739
740         if (!this.lip)
741         {
742                 this.lip = 8;
743         }
744
745         this.state = STATE_BOTTOM;
746
747         if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
748         {
749                 //this.canteamdamage = true; // TODO
750                 this.takedamage = DAMAGE_YES;
751                 this.event_damage = door_damage;
752         }
753
754         if (this.items)
755         {
756                 this.wait = -1;
757         }
758 }
759
760 // spawnflags require key (for now only func_door)
761 spawnfunc(func_door)
762 {
763         door_init_keys(this);
764
765         SetMovedir(this);
766
767         if (!InitMovingBrushTrigger(this))
768                 return;
769         this.effects |= EF_LOWPRECISION;
770         this.classname = "door";
771
772         setblocked(this, door_blocked);
773         this.use = door_use;
774
775         if(this.spawnflags & DOOR_NONSOLID)
776                 this.solid = SOLID_NOT;
777
778         // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
779         // but spawn in the open position
780         // the tuba door on xoylent requires the delayed init
781         if (this.spawnflags & DOOR_START_OPEN)
782                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
783
784         door_init_shared(this);
785
786         this.pos1 = this.origin;
787         vector absmovedir;
788         absmovedir.x = fabs(this.movedir.x);
789         absmovedir.y = fabs(this.movedir.y);
790         absmovedir.z = fabs(this.movedir.z);
791         this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
792
793         if(autocvar_sv_doors_always_open)
794         {
795                 this.speed = max(750, this.speed);
796         }
797         else if (!this.speed)
798         {
799                 if (q3compat)
800                         this.speed = 400;
801                 else
802                         this.speed = 100;
803         }
804
805         if (q3compat)
806         {
807                 if (!this.dmg)
808                         this.dmg = 2;
809
810                 if (!this.team)
811                 {
812                         string t = GetField_fullspawndata(this, "team");
813                         // bones_was_here: same hack as used to support teamed items on Q3 maps
814                         if (t) this.team = crc16(false, t);
815                 }
816         }
817
818         settouch(this, door_touch);
819
820         // LinkDoors can't be done until all of the doors have been spawned, so
821         // the sizes can be detected properly.
822         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
823
824         this.reset = door_reset;
825 }
826
827 #elif defined(CSQC)
828
829 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
830 {
831         int sf = ReadByte();
832
833         if(sf & SF_TRIGGER_INIT)
834         {
835                 this.classname = strzone(ReadString());
836                 this.spawnflags = ReadByte();
837
838                 this.mdl = strzone(ReadString());
839                 _setmodel(this, this.mdl);
840
841                 trigger_common_read(this, true);
842
843                 this.pos1 = ReadVector();
844                 this.pos2 = ReadVector();
845
846                 this.size = ReadVector();
847
848                 this.wait = ReadShort();
849                 this.speed = ReadShort();
850                 this.lip = ReadByte();
851                 this.state = ReadByte();
852                 this.ltime = ReadCoord();
853
854                 this.solid = SOLID_BSP;
855                 set_movetype(this, MOVETYPE_PUSH);
856                 this.use = door_use;
857
858                 LinkDoors(this);
859
860                 if(this.spawnflags & DOOR_START_OPEN)
861                         door_init_startopen(this);
862
863                 this.move_time = time;
864                 set_movetype(this, MOVETYPE_PUSH);
865         }
866
867         if(sf & SF_TRIGGER_RESET)
868         {
869                 door_reset(this);
870         }
871
872         if(sf & SF_TRIGGER_UPDATE)
873         {
874                 this.origin = ReadVector();
875                 setorigin(this, this.origin);
876
877                 this.pos1 = ReadVector();
878                 this.pos2 = ReadVector();
879         }
880         return true;
881 }
882
883 #endif