Properly support team field on trigger_multiple
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / func / door.qc
1 #include "door.qh"
2 /*
3
4 Doors are similar to buttons, but can spawn a fat trigger field around them
5 to open without a touch, and they link together to form simultanious
6 double/quad doors.
7
8 Door.owner is the master door.  If there is only one door, it points to itself.
9 If multiple doors, all will point to a single one.
10
11 Door.enemy chains from the master door through all doors linked in the chain.
12
13 */
14
15
16 /*
17 =============================================================================
18
19 THINK FUNCTIONS
20
21 =============================================================================
22 */
23
24 void door_go_down(entity this);
25 void door_go_up(entity this, entity actor, entity trigger);
26 void door_rotating_go_down(entity this);
27 void door_rotating_go_up(entity this, entity oth);
28
29 void door_blocked(entity this, entity blocker)
30 {
31         if((this.spawnflags & 8)
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, blocker.origin, '0 0 0');
41 #endif
42         }
43         else
44         {
45 #ifdef SVQC
46                 if((this.dmg) && (blocker.takedamage == DAMAGE_YES))    // Shall we bite?
47                         Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, 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                         if (this.classname == "door")
61                         {
62                                 door_go_up (this, NULL, NULL);
63                         } else
64                         {
65                                 door_rotating_go_up(this, blocker);
66                         }
67                                 else
68                         if (this.classname == "door")
69                         {
70                                 door_go_down (this);
71                         } else
72                         {
73                                 door_rotating_go_down (this);
74                         }
75                         }
76                 }
77 #ifdef SVQC
78                 else
79                 {
80                         //gib dying stuff just to make sure
81                         if((this.dmg) && (blocker.takedamage != DAMAGE_NO))    // Shall we bite?
82                                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
83                 }
84 #endif
85         }
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                 this.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, SND(TALK));
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 & 2) && 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 & 2) &&  (e.spawnflags & 8) && 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, vector hitloc, vector force)
267 {
268         if(this.spawnflags & DOOR_NOSPLASH)
269                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
270                         return;
271         this.health = this.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 (this.health <= 0)
280         {
281                 this.owner.health = this.owner.max_health;
282                 this.owner.takedamage = DAMAGE_NO;      // wil be reset upon return
283                 door_use(this.owner, NULL, 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 & 8) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
319 #ifdef SVQC
320                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, 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, 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, blocker.origin, '0 0 0');
348                 }
349 #endif
350         }
351 }
352
353 void door_rotating_hit_top(entity this)
354 {
355         if (this.noise1 != "")
356                 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
357         this.state = STATE_TOP;
358         if (this.spawnflags & DOOR_TOGGLE)
359                 return;         // don't come down automatically
360         setthink(this, door_rotating_go_down);
361         this.nextthink = this.ltime + this.wait;
362 }
363
364 void door_rotating_hit_bottom(entity this)
365 {
366         if (this.noise1 != "")
367                 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
368         if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating
369         {
370                 this.pos2 = '0 0 0' - this.pos2;
371                 this.lip = 0;
372         }
373         this.state = STATE_BOTTOM;
374 }
375
376 void door_rotating_go_down(entity this)
377 {
378         if (this.noise2 != "")
379                 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
380         if (this.max_health)
381         {
382                 this.takedamage = DAMAGE_YES;
383                 this.health = this.max_health;
384         }
385
386         this.state = STATE_DOWN;
387         SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom);
388 }
389
390 void door_rotating_go_up(entity this, entity oth)
391 {
392         if (this.state == STATE_UP)
393                 return;         // already going up
394
395         if (this.state == STATE_TOP)
396         {       // reset top wait time
397                 this.nextthink = this.ltime + this.wait;
398                 return;
399         }
400         if (this.noise2 != "")
401                 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
402         this.state = STATE_UP;
403         SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top);
404
405         string oldmessage;
406         oldmessage = this.message;
407         this.message = "";
408         SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here?
409         this.message = oldmessage;
410 }
411
412
413 /*
414 =========================================
415 door trigger
416
417 Spawned if a door lacks a real activator
418 =========================================
419 */
420
421 void door_trigger_touch(entity this, entity toucher)
422 {
423         if (toucher.health < 1)
424 #ifdef SVQC
425                 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
426 #elif defined(CSQC)
427                 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
428 #endif
429                         return;
430
431         if (time < this.door_finished)
432                 return;
433
434         // check if door is locked
435         if (!door_check_keys(this, toucher))
436                 return;
437
438         this.door_finished = time + 1;
439
440         door_use(this.owner, toucher, NULL);
441 }
442
443 void door_spawnfield(entity this, vector fmins, vector fmaxs)
444 {
445         entity  trigger;
446         vector  t1 = fmins, t2 = fmaxs;
447
448         trigger = new(doortriggerfield);
449         set_movetype(trigger, MOVETYPE_NONE);
450         trigger.solid = SOLID_TRIGGER;
451         trigger.owner = this;
452 #ifdef SVQC
453         settouch(trigger, door_trigger_touch);
454 #endif
455
456         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
457 }
458
459
460 /*
461 =============
462 LinkDoors
463
464
465 =============
466 */
467
468 entity LinkDoors_nextent(entity cur, entity near, entity pass)
469 {
470         while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & 4) || cur.enemy))
471         {
472         }
473         return cur;
474 }
475
476 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
477 {
478         float DELTA = 4;
479         if((e1.absmin_x > e2.absmax_x + DELTA)
480         || (e1.absmin_y > e2.absmax_y + DELTA)
481         || (e1.absmin_z > e2.absmax_z + DELTA)
482         || (e2.absmin_x > e1.absmax_x + DELTA)
483         || (e2.absmin_y > e1.absmax_y + DELTA)
484         || (e2.absmin_z > e1.absmax_z + DELTA)
485         ) { return false; }
486         return true;
487 }
488
489 #ifdef SVQC
490 void door_link();
491 #endif
492 void LinkDoors(entity this)
493 {
494         entity  t;
495         vector  cmins, cmaxs;
496
497 #ifdef SVQC
498         door_link();
499 #endif
500
501         if (this.enemy)
502                 return;         // already linked by another door
503         if (this.spawnflags & 4)
504         {
505                 this.owner = this.enemy = this;
506
507                 if (this.health)
508                         return;
509                 IFTARGETED
510                         return;
511                 if (this.items)
512                         return;
513
514                 door_spawnfield(this, this.absmin, this.absmax);
515
516                 return;         // don't want to link this door
517         }
518
519         FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
520
521         // set owner, and make a loop of the chain
522         LOG_TRACE("LinkDoors: linking doors:");
523         for(t = this; ; t = t.enemy)
524         {
525                 LOG_TRACE(" ", etos(t));
526                 t.owner = this;
527                 if(t.enemy == NULL)
528                 {
529                         t.enemy = this;
530                         break;
531                 }
532         }
533         LOG_TRACE("");
534
535         // collect health, targetname, message, size
536         cmins = this.absmin;
537         cmaxs = this.absmax;
538         for(t = this; ; t = t.enemy)
539         {
540                 if(t.health && !this.health)
541                         this.health = t.health;
542                 if((t.targetname != "") && (this.targetname == ""))
543                         this.targetname = t.targetname;
544                 if((t.message != "") && (this.message == ""))
545                         this.message = t.message;
546                 if (t.absmin_x < cmins_x)
547                         cmins_x = t.absmin_x;
548                 if (t.absmin_y < cmins_y)
549                         cmins_y = t.absmin_y;
550                 if (t.absmin_z < cmins_z)
551                         cmins_z = t.absmin_z;
552                 if (t.absmax_x > cmaxs_x)
553                         cmaxs_x = t.absmax_x;
554                 if (t.absmax_y > cmaxs_y)
555                         cmaxs_y = t.absmax_y;
556                 if (t.absmax_z > cmaxs_z)
557                         cmaxs_z = t.absmax_z;
558                 if(t.enemy == this)
559                         break;
560         }
561
562         // distribute health, targetname, message
563         for(t = this; t; t = t.enemy)
564         {
565                 t.health = this.health;
566                 t.targetname = this.targetname;
567                 t.message = this.message;
568                 if(t.enemy == this)
569                         break;
570         }
571
572         // shootable, or triggered doors just needed the owner/enemy links,
573         // they don't spawn a field
574
575         if (this.health)
576                 return;
577         IFTARGETED
578                 return;
579         if (this.items)
580                 return;
581
582         door_spawnfield(this, cmins, cmaxs);
583 }
584
585 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
586
587 #ifdef SVQC
588 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
589 if two doors touch, they are assumed to be connected and operate as a unit.
590
591 TOGGLE causes the door to wait in both the start and end states for a trigger event.
592
593 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).
594
595 GOLD_KEY causes the door to open only if the activator holds a gold key.
596
597 SILVER_KEY causes the door to open only if the activator holds a silver key.
598
599 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
600 "angle"         determines the opening direction
601 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
602 "health"        if set, door must be shot open
603 "speed"         movement speed (100 default)
604 "wait"          wait before returning (3 default, -1 = never return)
605 "lip"           lip remaining at end of move (8 default)
606 "dmg"           damage to inflict when blocked (2 default)
607 "sounds"
608 0)      no sound
609 1)      stone
610 2)      base
611 3)      stone chain
612 4)      screechy metal
613 FIXME: only one sound set available at the time being
614
615 */
616
617 float door_send(entity this, entity to, float sf)
618 {
619         WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
620         WriteByte(MSG_ENTITY, sf);
621
622         if(sf & SF_TRIGGER_INIT)
623         {
624                 WriteString(MSG_ENTITY, this.classname);
625                 WriteByte(MSG_ENTITY, this.spawnflags);
626
627                 WriteString(MSG_ENTITY, this.model);
628
629                 trigger_common_write(this, true);
630
631                 WriteCoord(MSG_ENTITY, this.pos1_x);
632                 WriteCoord(MSG_ENTITY, this.pos1_y);
633                 WriteCoord(MSG_ENTITY, this.pos1_z);
634                 WriteCoord(MSG_ENTITY, this.pos2_x);
635                 WriteCoord(MSG_ENTITY, this.pos2_y);
636                 WriteCoord(MSG_ENTITY, this.pos2_z);
637
638                 WriteCoord(MSG_ENTITY, this.size_x);
639                 WriteCoord(MSG_ENTITY, this.size_y);
640                 WriteCoord(MSG_ENTITY, this.size_z);
641
642                 WriteShort(MSG_ENTITY, this.wait);
643                 WriteShort(MSG_ENTITY, this.speed);
644                 WriteByte(MSG_ENTITY, this.lip);
645                 WriteByte(MSG_ENTITY, this.state);
646                 WriteCoord(MSG_ENTITY, this.ltime);
647         }
648
649         if(sf & SF_TRIGGER_RESET)
650         {
651                 // client makes use of this, we do not
652         }
653
654         if(sf & SF_TRIGGER_UPDATE)
655         {
656                 WriteCoord(MSG_ENTITY, this.origin_x);
657                 WriteCoord(MSG_ENTITY, this.origin_y);
658                 WriteCoord(MSG_ENTITY, this.origin_z);
659
660                 WriteCoord(MSG_ENTITY, this.pos1_x);
661                 WriteCoord(MSG_ENTITY, this.pos1_y);
662                 WriteCoord(MSG_ENTITY, this.pos1_z);
663                 WriteCoord(MSG_ENTITY, this.pos2_x);
664                 WriteCoord(MSG_ENTITY, this.pos2_y);
665                 WriteCoord(MSG_ENTITY, this.pos2_z);
666         }
667
668         return true;
669 }
670
671 void door_link()
672 {
673         // set size now, as everything is loaded
674         //FixSize(this);
675         //Net_LinkEntity(this, false, 0, door_send);
676 }
677 #endif
678
679 void door_init_startopen(entity this)
680 {
681         setorigin(this, this.pos2);
682         this.pos2 = this.pos1;
683         this.pos1 = this.origin;
684
685 #ifdef SVQC
686         this.SendFlags |= SF_TRIGGER_UPDATE;
687 #endif
688 }
689
690 void door_reset(entity this)
691 {
692         setorigin(this, this.pos1);
693         this.velocity = '0 0 0';
694         this.state = STATE_BOTTOM;
695         setthink(this, func_null);
696         this.nextthink = 0;
697
698 #ifdef SVQC
699         this.SendFlags |= SF_TRIGGER_RESET;
700 #endif
701 }
702
703 #ifdef SVQC
704
705 // spawnflags require key (for now only func_door)
706 spawnfunc(func_door)
707 {
708         // Quake 1 keys compatibility
709         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
710                 this.itemkeys |= ITEM_KEY_BIT(0);
711         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
712                 this.itemkeys |= ITEM_KEY_BIT(1);
713
714         SetMovedir(this);
715
716         this.max_health = this.health;
717         if (!InitMovingBrushTrigger(this))
718                 return;
719         this.effects |= EF_LOWPRECISION;
720         this.classname = "door";
721
722         if(this.noise == "")
723                 this.noise = "misc/talk.wav";
724         if(this.noise3 == "")
725                 this.noise3 = "misc/talk.wav";
726         precache_sound(this.noise);
727         precache_sound(this.noise3);
728
729         setblocked(this, door_blocked);
730         this.use = door_use;
731
732         if(this.dmg && (this.message == ""))
733                 this.message = "was squished";
734         if(this.dmg && (this.message2 == ""))
735                 this.message2 = "was squished by";
736
737         if (this.sounds > 0)
738         {
739                 this.noise2 = "plats/medplat1.wav";
740                 this.noise1 = "plats/medplat2.wav";
741         }
742
743         if(this.noise1 && this.noise1 != "") { precache_sound(this.noise1); }
744         if(this.noise2 && this.noise2 != "") { precache_sound(this.noise2); }
745
746         if (!this.speed)
747                 this.speed = 100;
748         if (!this.wait)
749                 this.wait = 3;
750         if (!this.lip)
751                 this.lip = 8;
752
753         this.pos1 = this.origin;
754         this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
755
756         if(this.spawnflags & DOOR_NONSOLID)
757                 this.solid = SOLID_NOT;
758
759 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
760 // but spawn in the open position
761         if (this.spawnflags & DOOR_START_OPEN)
762                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
763
764         this.state = STATE_BOTTOM;
765
766         if (this.health)
767         {
768                 //this.canteamdamage = true; // TODO
769                 this.takedamage = DAMAGE_YES;
770                 this.event_damage = door_damage;
771         }
772
773         if (this.items)
774                 this.wait = -1;
775
776         settouch(this, door_touch);
777
778 // LinkDoors can't be done until all of the doors have been spawned, so
779 // the sizes can be detected properly.
780         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
781
782         this.reset = door_reset;
783 }
784
785 #elif defined(CSQC)
786
787 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
788 {
789         int sf = ReadByte();
790
791         if(sf & SF_TRIGGER_INIT)
792         {
793                 this.classname = strzone(ReadString());
794                 this.spawnflags = ReadByte();
795
796                 this.mdl = strzone(ReadString());
797                 _setmodel(this, this.mdl);
798
799                 trigger_common_read(this, true);
800
801                 vector v;
802
803                 v.x = ReadCoord();
804                 v.y = ReadCoord();
805                 v.z = ReadCoord();
806                 this.pos1 = v;
807
808                 v.x = ReadCoord();
809                 v.y = ReadCoord();
810                 v.z = ReadCoord();
811                 this.pos2 = v;
812
813                 v.x = ReadCoord();
814                 v.y = ReadCoord();
815                 v.z = ReadCoord();
816                 this.size = v;
817
818                 this.wait = ReadShort();
819                 this.speed = ReadShort();
820                 this.lip = ReadByte();
821                 this.state = ReadByte();
822                 this.ltime = ReadCoord();
823
824                 this.solid = SOLID_BSP;
825                 set_movetype(this, MOVETYPE_PUSH);
826                 this.use = door_use;
827
828                 LinkDoors(this);
829
830                 if(this.spawnflags & DOOR_START_OPEN)
831                         door_init_startopen(this);
832
833                 this.move_time = time;
834                 set_movetype(this, MOVETYPE_PUSH);
835         }
836
837         if(sf & SF_TRIGGER_RESET)
838         {
839                 door_reset(this);
840         }
841
842         if(sf & SF_TRIGGER_UPDATE)
843         {
844                 this.origin_x = ReadCoord();
845                 this.origin_y = ReadCoord();
846                 this.origin_z = ReadCoord();
847                 setorigin(this, this.origin);
848
849                 this.pos1_x = ReadCoord();
850                 this.pos1_y = ReadCoord();
851                 this.pos1_z = ReadCoord();
852                 this.pos2_x = ReadCoord();
853                 this.pos2_y = ReadCoord();
854                 this.pos2_z = ReadCoord();
855         }
856         return true;
857 }
858
859 #endif