]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapobjects/func/door.qc
25e9ba844e75e89a5752569f2e81d2f061bd6b4f
[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 #endif
618
619 void door_init_startopen(entity this)
620 {
621         setorigin(this, this.pos2);
622         this.pos2 = this.pos1;
623         this.pos1 = this.origin;
624
625 #ifdef SVQC
626         this.SendFlags |= SF_TRIGGER_UPDATE;
627 #endif
628 }
629
630 void door_reset(entity this)
631 {
632         setorigin(this, this.pos1);
633         this.velocity = '0 0 0';
634         this.state = STATE_BOTTOM;
635         setthink(this, func_null);
636         this.nextthink = 0;
637
638 #ifdef SVQC
639         this.SendFlags |= SF_TRIGGER_RESET;
640 #endif
641 }
642
643 #ifdef SVQC
644
645 // common code for func_door and func_door_rotating spawnfuncs
646 void door_init_shared(entity this)
647 {
648         this.max_health = GetResource(this, RES_HEALTH);
649
650         // unlock sound
651         if(this.noise == "")
652         {
653                 this.noise = "misc/talk.wav";
654         }
655         // door still locked sound
656         if(this.noise3 == "")
657         {
658                 this.noise3 = "misc/talk.wav";
659         }
660         precache_sound(this.noise);
661         precache_sound(this.noise3);
662
663         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
664         {
665                 this.message = "was squished";
666         }
667         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
668         {
669                 this.message2 = "was squished by";
670         }
671
672         // TODO: other soundpacks
673         if (this.sounds > 0 || q3compat)
674         {
675                 // Doors in Q3 always have sounds (they're hard coded)
676                 this.noise2 = "plats/medplat1.wav";
677                 this.noise1 = "plats/medplat2.wav";
678         }
679
680         if (q3compat)
681         {
682                 // CPMA adds these fields for overriding the Q3 default sounds
683                 string s = GetField_fullspawndata(this, "sound_start", true);
684                 string e = GetField_fullspawndata(this, "sound_end", true);
685
686                 // Quake Live adds these ones, because of course it had to be different from CPMA
687                 if (!s) s = GetField_fullspawndata(this, "startsound", true);
688                 if (!e) e = GetField_fullspawndata(this, "endsound", true);
689
690                 if (s)
691                         this.noise2 = strzone(s);
692                 else
693                 {
694                         // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
695                         s = "sound/movers/doors/dr1_strt.wav";
696                         if (FindFileInMapPack(s))
697                                 this.noise2 = s;
698                 }
699
700                 if (e)
701                         this.noise1 = strzone(e);
702                 else
703                 {
704                         e = "sound/movers/doors/dr1_end.wav";
705                         if (FindFileInMapPack(e))
706                                 this.noise1 = e;
707                 }
708         }
709
710         // sound when door stops moving
711         if(this.noise1 && this.noise1 != "")
712         {
713                 precache_sound(this.noise1);
714         }
715         // sound when door is moving
716         if(this.noise2 && this.noise2 != "")
717         {
718                 precache_sound(this.noise2);
719         }
720
721         if(autocvar_sv_doors_always_open)
722         {
723                 this.wait = -1;
724         }
725         else if (!this.wait)
726         {
727                 this.wait = q3compat ? 2 : 3;
728         }
729
730         if (!this.lip)
731         {
732                 this.lip = 8;
733         }
734
735         this.state = STATE_BOTTOM;
736
737         if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
738         {
739                 //this.canteamdamage = true; // TODO
740                 this.takedamage = DAMAGE_YES;
741                 this.event_damage = door_damage;
742         }
743
744         if (this.items)
745         {
746                 this.wait = -1;
747         }
748 }
749
750 // spawnflags require key (for now only func_door)
751 spawnfunc(func_door)
752 {
753         // Quake 1 and QL keys compatibility
754         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
755                 this.itemkeys |= BIT(0);
756         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
757                 this.itemkeys |= BIT(1);
758
759         SetMovedir(this);
760
761         if (!InitMovingBrushTrigger(this))
762                 return;
763         this.effects |= EF_LOWPRECISION;
764         this.classname = "door";
765
766         setblocked(this, door_blocked);
767         this.use = door_use;
768
769         if(this.spawnflags & DOOR_NONSOLID)
770                 this.solid = SOLID_NOT;
771
772         // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
773         // but spawn in the open position
774         // the tuba door on xoylent requires the delayed init
775         if (this.spawnflags & DOOR_START_OPEN)
776                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
777
778         door_init_shared(this);
779
780         this.pos1 = this.origin;
781         vector absmovedir;
782         absmovedir.x = fabs(this.movedir.x);
783         absmovedir.y = fabs(this.movedir.y);
784         absmovedir.z = fabs(this.movedir.z);
785         this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
786
787         if(autocvar_sv_doors_always_open)
788         {
789                 this.speed = max(750, this.speed);
790         }
791         else if (!this.speed)
792         {
793                 if (q3compat)
794                         this.speed = 400;
795                 else
796                         this.speed = 100;
797         }
798
799         if (q3compat)
800         {
801                 if (!this.dmg)
802                         this.dmg = 2;
803
804                 if (!this.team)
805                 {
806                         string t = GetField_fullspawndata(this, "team");
807                         // bones_was_here: same hack as used to support teamed items on Q3 maps
808                         if (t) this.team = crc16(false, t);
809                 }
810         }
811
812         settouch(this, door_touch);
813
814         // LinkDoors can't be done until all of the doors have been spawned, so
815         // the sizes can be detected properly.
816         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
817
818         this.reset = door_reset;
819 }
820
821 #elif defined(CSQC)
822
823 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
824 {
825         int sf = ReadByte();
826
827         if(sf & SF_TRIGGER_INIT)
828         {
829                 this.classname = strzone(ReadString());
830                 this.spawnflags = ReadByte();
831
832                 this.mdl = strzone(ReadString());
833                 _setmodel(this, this.mdl);
834
835                 trigger_common_read(this, true);
836
837                 this.pos1 = ReadVector();
838                 this.pos2 = ReadVector();
839
840                 this.size = ReadVector();
841
842                 this.wait = ReadShort();
843                 this.speed = ReadShort();
844                 this.lip = ReadByte();
845                 this.state = ReadByte();
846                 this.ltime = ReadCoord();
847
848                 this.solid = SOLID_BSP;
849                 set_movetype(this, MOVETYPE_PUSH);
850                 this.use = door_use;
851
852                 LinkDoors(this);
853
854                 if(this.spawnflags & DOOR_START_OPEN)
855                         door_init_startopen(this);
856
857                 this.move_time = time;
858                 set_movetype(this, MOVETYPE_PUSH);
859         }
860
861         if(sf & SF_TRIGGER_RESET)
862         {
863                 door_reset(this);
864         }
865
866         if(sf & SF_TRIGGER_UPDATE)
867         {
868                 this.origin = ReadVector();
869                 setorigin(this, this.origin);
870
871                 this.pos1 = ReadVector();
872                 this.pos2 = ReadVector();
873         }
874         return true;
875 }
876
877 #endif