]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapobjects/func/door.qc
func_door: implement Q3 team-based door linking using fullspawndata
[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))
422         && ((!Q3COMPAT_COMMON && (cur.spawnflags & DOOR_DONT_LINK)) || cur.enemy))
423         {
424         }
425         return cur;
426 }
427
428 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
429 {
430         if(Q3COMPAT_COMMON)
431                 return e1.team == e2.team;
432
433         float DELTA = 4;
434         if((e1.absmin_x > e2.absmax_x + DELTA)
435         || (e1.absmin_y > e2.absmax_y + DELTA)
436         || (e1.absmin_z > e2.absmax_z + DELTA)
437         || (e2.absmin_x > e1.absmax_x + DELTA)
438         || (e2.absmin_y > e1.absmax_y + DELTA)
439         || (e2.absmin_z > e1.absmax_z + DELTA)
440         ) { return false; }
441         return true;
442 }
443
444 #ifdef SVQC
445 void door_link();
446 #endif
447 void LinkDoors(entity this)
448 {
449         entity  t;
450         vector  cmins, cmaxs;
451
452 #ifdef SVQC
453         door_link();
454 #endif
455
456         if (this.enemy)
457                 return;         // already linked by another door
458
459         // Q3 door linking is done for teamed doors only and is not affected by spawnflags or bmodel proximity
460         if ((!Q3COMPAT_COMMON && (this.spawnflags & DOOR_DONT_LINK)) || (Q3COMPAT_COMMON && !this.team))
461         {
462                 this.owner = this.enemy = this;
463
464                 if (GetResource(this, RES_HEALTH))
465                         return;
466                 if(this.targetname && this.targetname != "")
467                         return;
468                 if (this.items)
469                         return;
470
471                 door_spawnfield(this, this.absmin, this.absmax);
472
473                 return;         // don't want to link this door
474         }
475
476         FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
477
478         // set owner, and make a loop of the chain
479         LOG_TRACE("LinkDoors: linking doors:");
480         for(t = this; ; t = t.enemy)
481         {
482                 LOG_TRACE(" ", etos(t));
483                 t.owner = this;
484                 if(t.enemy == NULL)
485                 {
486                         t.enemy = this;
487                         break;
488                 }
489         }
490         LOG_TRACE("");
491
492         // collect health, targetname, message, size
493         cmins = this.absmin;
494         cmaxs = this.absmax;
495         for(t = this; ; t = t.enemy)
496         {
497                 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
498                         SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
499                 if((t.targetname != "") && (this.targetname == ""))
500                         this.targetname = t.targetname;
501                 if((t.message != "") && (this.message == ""))
502                         this.message = t.message;
503                 if (t.absmin_x < cmins_x)
504                         cmins_x = t.absmin_x;
505                 if (t.absmin_y < cmins_y)
506                         cmins_y = t.absmin_y;
507                 if (t.absmin_z < cmins_z)
508                         cmins_z = t.absmin_z;
509                 if (t.absmax_x > cmaxs_x)
510                         cmaxs_x = t.absmax_x;
511                 if (t.absmax_y > cmaxs_y)
512                         cmaxs_y = t.absmax_y;
513                 if (t.absmax_z > cmaxs_z)
514                         cmaxs_z = t.absmax_z;
515                 if(t.enemy == this)
516                         break;
517         }
518
519         // distribute health, targetname, message
520         for(t = this; t; t = t.enemy)
521         {
522                 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
523                 t.targetname = this.targetname;
524                 t.message = this.message;
525                 if(t.enemy == this)
526                         break;
527         }
528
529         // shootable, or triggered doors just needed the owner/enemy links,
530         // they don't spawn a field
531
532         if (GetResource(this, RES_HEALTH))
533                 return;
534         if(this.targetname && this.targetname != "")
535                 return;
536         if (this.items)
537                 return;
538
539         door_spawnfield(this, cmins, cmaxs);
540 }
541
542 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
543
544 #ifdef SVQC
545 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
546 if two doors touch, they are assumed to be connected and operate as a unit.
547
548 TOGGLE causes the door to wait in both the start and end states for a trigger event.
549
550 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).
551
552 GOLD_KEY causes the door to open only if the activator holds a gold key.
553
554 SILVER_KEY causes the door to open only if the activator holds a silver key.
555
556 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
557 "angle"         determines the opening direction
558 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
559 "health"        if set, door must be shot open
560 "speed"         movement speed (100 default)
561 "wait"          wait before returning (3 default, -1 = never return)
562 "lip"           lip remaining at end of move (8 default)
563 "dmg"           damage to inflict when blocked (0 default)
564 "sounds"
565 0)      no sound
566 1)      stone
567 2)      base
568 3)      stone chain
569 4)      screechy metal
570 FIXME: only one sound set available at the time being
571
572 */
573
574 float door_send(entity this, entity to, float sf)
575 {
576         WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
577         WriteByte(MSG_ENTITY, sf);
578
579         if(sf & SF_TRIGGER_INIT)
580         {
581                 WriteString(MSG_ENTITY, this.classname);
582                 WriteByte(MSG_ENTITY, this.spawnflags);
583
584                 WriteString(MSG_ENTITY, this.model);
585
586                 trigger_common_write(this, true);
587
588                 WriteVector(MSG_ENTITY, this.pos1);
589                 WriteVector(MSG_ENTITY, this.pos2);
590
591                 WriteVector(MSG_ENTITY, this.size);
592
593                 WriteShort(MSG_ENTITY, this.wait);
594                 WriteShort(MSG_ENTITY, this.speed);
595                 WriteByte(MSG_ENTITY, this.lip);
596                 WriteByte(MSG_ENTITY, this.state);
597                 WriteCoord(MSG_ENTITY, this.ltime);
598         }
599
600         if(sf & SF_TRIGGER_RESET)
601         {
602                 // client makes use of this, we do not
603         }
604
605         if(sf & SF_TRIGGER_UPDATE)
606         {
607                 WriteVector(MSG_ENTITY, this.origin);
608
609                 WriteVector(MSG_ENTITY, this.pos1);
610                 WriteVector(MSG_ENTITY, this.pos2);
611         }
612
613         return true;
614 }
615
616 void door_link()
617 {
618         //Net_LinkEntity(this, false, 0, door_send);
619 }
620 #endif
621
622 void door_init_startopen(entity this)
623 {
624         setorigin(this, this.pos2);
625         this.pos2 = this.pos1;
626         this.pos1 = this.origin;
627
628 #ifdef SVQC
629         this.SendFlags |= SF_TRIGGER_UPDATE;
630 #endif
631 }
632
633 void door_reset(entity this)
634 {
635         setorigin(this, this.pos1);
636         this.velocity = '0 0 0';
637         this.state = STATE_BOTTOM;
638         setthink(this, func_null);
639         this.nextthink = 0;
640
641 #ifdef SVQC
642         this.SendFlags |= SF_TRIGGER_RESET;
643 #endif
644 }
645
646 #ifdef SVQC
647
648 // common code for func_door and func_door_rotating spawnfuncs
649 void door_init_shared(entity this)
650 {
651         this.max_health = GetResource(this, RES_HEALTH);
652
653         // unlock sound
654         if(this.noise == "")
655         {
656                 this.noise = "misc/talk.wav";
657         }
658         // door still locked sound
659         if(this.noise3 == "")
660         {
661                 this.noise3 = "misc/talk.wav";
662         }
663         precache_sound(this.noise);
664         precache_sound(this.noise3);
665
666         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
667         {
668                 this.message = "was squished";
669         }
670         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
671         {
672                 this.message2 = "was squished by";
673         }
674
675         // TODO: other soundpacks
676         if (this.sounds > 0 || q3compat)
677         {
678                 // Doors in Q3 always have sounds (they're hard coded)
679                 this.noise2 = "plats/medplat1.wav";
680                 this.noise1 = "plats/medplat2.wav";
681         }
682
683         if (q3compat)
684         {
685                 // CPMA adds these fields for overriding the engine sounds
686                 string s = GetField_fullspawndata(this, "sound_start", true);
687                 string e = GetField_fullspawndata(this, "sound_end", true);
688
689                 if (s)
690                         this.noise2 = strzone(s);
691                 if (e)
692                         this.noise1 = strzone(e);
693         }
694
695         // sound when door stops moving
696         if(this.noise1 && this.noise1 != "")
697         {
698                 precache_sound(this.noise1);
699         }
700         // sound when door is moving
701         if(this.noise2 && this.noise2 != "")
702         {
703                 precache_sound(this.noise2);
704         }
705
706         if(autocvar_sv_doors_always_open)
707         {
708                 this.wait = -1;
709         }
710         else if (!this.wait)
711         {
712                 this.wait = q3compat ? 2 : 3;
713         }
714
715         if (!this.lip)
716         {
717                 this.lip = 8;
718         }
719
720         this.state = STATE_BOTTOM;
721
722         if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
723         {
724                 //this.canteamdamage = true; // TODO
725                 this.takedamage = DAMAGE_YES;
726                 this.event_damage = door_damage;
727         }
728
729         if (this.items)
730         {
731                 this.wait = -1;
732         }
733 }
734
735 // spawnflags require key (for now only func_door)
736 spawnfunc(func_door)
737 {
738         // Quake 1 keys compatibility
739         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
740                 this.itemkeys |= ITEM_KEY_BIT(0);
741         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
742                 this.itemkeys |= ITEM_KEY_BIT(1);
743
744         SetMovedir(this);
745
746         if (!InitMovingBrushTrigger(this))
747                 return;
748         this.effects |= EF_LOWPRECISION;
749         this.classname = "door";
750
751         setblocked(this, door_blocked);
752         this.use = door_use;
753
754         if(this.spawnflags & DOOR_NONSOLID)
755                 this.solid = SOLID_NOT;
756
757         // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
758         // but spawn in the open position
759         // the tuba door on xoylent requires the delayed init
760         if (this.spawnflags & DOOR_START_OPEN)
761                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
762
763         door_init_shared(this);
764
765         this.pos1 = this.origin;
766         vector absmovedir;
767         absmovedir.x = fabs(this.movedir.x);
768         absmovedir.y = fabs(this.movedir.y);
769         absmovedir.z = fabs(this.movedir.z);
770         this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
771
772         if(autocvar_sv_doors_always_open)
773         {
774                 this.speed = max(750, this.speed);
775         }
776         else if (!this.speed)
777         {
778                 if (q3compat)
779                         this.speed = 400;
780                 else
781                         this.speed = 100;
782         }
783
784         if (q3compat)
785         {
786                 if (!this.dmg)
787                         this.dmg = 2;
788
789                 if (!this.team)
790                 {
791                         string t = GetField_fullspawndata(this, "team");
792                         // bones_was_here: same hack as used to support teamed items on Q3 maps
793                         if (t) this.team = crc16(false, t);
794                 }
795         }
796
797         settouch(this, door_touch);
798
799         // LinkDoors can't be done until all of the doors have been spawned, so
800         // the sizes can be detected properly.
801         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
802
803         this.reset = door_reset;
804 }
805
806 #elif defined(CSQC)
807
808 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
809 {
810         int sf = ReadByte();
811
812         if(sf & SF_TRIGGER_INIT)
813         {
814                 this.classname = strzone(ReadString());
815                 this.spawnflags = ReadByte();
816
817                 this.mdl = strzone(ReadString());
818                 _setmodel(this, this.mdl);
819
820                 trigger_common_read(this, true);
821
822                 this.pos1 = ReadVector();
823                 this.pos2 = ReadVector();
824
825                 this.size = ReadVector();
826
827                 this.wait = ReadShort();
828                 this.speed = ReadShort();
829                 this.lip = ReadByte();
830                 this.state = ReadByte();
831                 this.ltime = ReadCoord();
832
833                 this.solid = SOLID_BSP;
834                 set_movetype(this, MOVETYPE_PUSH);
835                 this.use = door_use;
836
837                 LinkDoors(this);
838
839                 if(this.spawnflags & DOOR_START_OPEN)
840                         door_init_startopen(this);
841
842                 this.move_time = time;
843                 set_movetype(this, MOVETYPE_PUSH);
844         }
845
846         if(sf & SF_TRIGGER_RESET)
847         {
848                 door_reset(this);
849         }
850
851         if(sf & SF_TRIGGER_UPDATE)
852         {
853                 this.origin = ReadVector();
854                 setorigin(this, this.origin);
855
856                 this.pos1 = ReadVector();
857                 this.pos2 = ReadVector();
858         }
859         return true;
860 }
861
862 #endif