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