]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapobjects/func/door.qc
func_door: merge door_use() and door_fire()
[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 engine sounds
683                 string s = GetField_fullspawndata(this, "sound_start", true);
684                 string e = GetField_fullspawndata(this, "sound_end", true);
685
686                 if (s)
687                         this.noise2 = strzone(s);
688                 if (e)
689                         this.noise1 = strzone(e);
690         }
691
692         // sound when door stops moving
693         if(this.noise1 && this.noise1 != "")
694         {
695                 precache_sound(this.noise1);
696         }
697         // sound when door is moving
698         if(this.noise2 && this.noise2 != "")
699         {
700                 precache_sound(this.noise2);
701         }
702
703         if(autocvar_sv_doors_always_open)
704         {
705                 this.wait = -1;
706         }
707         else if (!this.wait)
708         {
709                 this.wait = q3compat ? 2 : 3;
710         }
711
712         if (!this.lip)
713         {
714                 this.lip = 8;
715         }
716
717         this.state = STATE_BOTTOM;
718
719         if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
720         {
721                 //this.canteamdamage = true; // TODO
722                 this.takedamage = DAMAGE_YES;
723                 this.event_damage = door_damage;
724         }
725
726         if (this.items)
727         {
728                 this.wait = -1;
729         }
730 }
731
732 // spawnflags require key (for now only func_door)
733 spawnfunc(func_door)
734 {
735         // Quake 1 keys compatibility
736         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
737                 this.itemkeys |= ITEM_KEY_BIT(0);
738         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
739                 this.itemkeys |= ITEM_KEY_BIT(1);
740
741         SetMovedir(this);
742
743         if (!InitMovingBrushTrigger(this))
744                 return;
745         this.effects |= EF_LOWPRECISION;
746         this.classname = "door";
747
748         setblocked(this, door_blocked);
749         this.use = door_use;
750
751         if(this.spawnflags & DOOR_NONSOLID)
752                 this.solid = SOLID_NOT;
753
754         // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
755         // but spawn in the open position
756         // the tuba door on xoylent requires the delayed init
757         if (this.spawnflags & DOOR_START_OPEN)
758                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
759
760         door_init_shared(this);
761
762         this.pos1 = this.origin;
763         vector absmovedir;
764         absmovedir.x = fabs(this.movedir.x);
765         absmovedir.y = fabs(this.movedir.y);
766         absmovedir.z = fabs(this.movedir.z);
767         this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
768
769         if(autocvar_sv_doors_always_open)
770         {
771                 this.speed = max(750, this.speed);
772         }
773         else if (!this.speed)
774         {
775                 if (q3compat)
776                         this.speed = 400;
777                 else
778                         this.speed = 100;
779         }
780
781         if (q3compat)
782         {
783                 if (!this.dmg)
784                         this.dmg = 2;
785
786                 if (!this.team)
787                 {
788                         string t = GetField_fullspawndata(this, "team");
789                         // bones_was_here: same hack as used to support teamed items on Q3 maps
790                         if (t) this.team = crc16(false, t);
791                 }
792         }
793
794         settouch(this, door_touch);
795
796         // LinkDoors can't be done until all of the doors have been spawned, so
797         // the sizes can be detected properly.
798         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
799
800         this.reset = door_reset;
801 }
802
803 #elif defined(CSQC)
804
805 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
806 {
807         int sf = ReadByte();
808
809         if(sf & SF_TRIGGER_INIT)
810         {
811                 this.classname = strzone(ReadString());
812                 this.spawnflags = ReadByte();
813
814                 this.mdl = strzone(ReadString());
815                 _setmodel(this, this.mdl);
816
817                 trigger_common_read(this, true);
818
819                 this.pos1 = ReadVector();
820                 this.pos2 = ReadVector();
821
822                 this.size = ReadVector();
823
824                 this.wait = ReadShort();
825                 this.speed = ReadShort();
826                 this.lip = ReadByte();
827                 this.state = ReadByte();
828                 this.ltime = ReadCoord();
829
830                 this.solid = SOLID_BSP;
831                 set_movetype(this, MOVETYPE_PUSH);
832                 this.use = door_use;
833
834                 LinkDoors(this);
835
836                 if(this.spawnflags & DOOR_START_OPEN)
837                         door_init_startopen(this);
838
839                 this.move_time = time;
840                 set_movetype(this, MOVETYPE_PUSH);
841         }
842
843         if(sf & SF_TRIGGER_RESET)
844         {
845                 door_reset(this);
846         }
847
848         if(sf & SF_TRIGGER_UPDATE)
849         {
850                 this.origin = ReadVector();
851                 setorigin(this, this.origin);
852
853                 this.pos1 = ReadVector();
854                 this.pos2 = ReadVector();
855         }
856         return true;
857 }
858
859 #endif