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