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