757b848955fc7820e956c0a5da4e00c364d197ec
[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         if((this.spawnflags & DOOR_CRUSH)
31 #ifdef SVQC
32                 && (blocker.takedamage != DAMAGE_NO)
33 #elif defined(CSQC)
34                 && !IS_DEAD(blocker)
35 #endif
36         )
37         { // KIll Kill Kill!!
38 #ifdef SVQC
39                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
40 #endif
41         }
42         else
43         {
44 #ifdef SVQC
45                 if((this.dmg) && (blocker.takedamage == DAMAGE_YES))    // Shall we bite?
46                         Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
47 #endif
48
49                  // don't change direction for dead or dying stuff
50                 if(IS_DEAD(blocker)
51 #ifdef SVQC
52                         && (blocker.takedamage == DAMAGE_NO)
53 #endif
54                 )
55                 {
56                         if (this.wait >= 0)
57                         {
58                                 if (this.state == STATE_DOWN)
59                                 {
60                                         if (this.classname == "door")
61                                                 door_go_up(this, NULL, NULL);
62                                         else
63                                                 door_rotating_go_up(this, blocker);
64                                 }
65                                 else
66                                 {
67                                         if (this.classname == "door")
68                                                 door_go_down(this);
69                                         else
70                                                 door_rotating_go_down(this);
71                                 }
72                         }
73                 }
74 #ifdef SVQC
75                 else
76                 {
77                         //gib dying stuff just to make sure
78                         if((this.dmg) && (blocker.takedamage != DAMAGE_NO))    // Shall we bite?
79                                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
80                 }
81 #endif
82         }
83 }
84
85 void door_hit_top(entity this)
86 {
87         if (this.noise1 != "")
88                 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
89         this.state = STATE_TOP;
90         if (this.spawnflags & DOOR_TOGGLE)
91                 return;         // don't come down automatically
92         if (this.classname == "door")
93         {
94                 setthink(this, door_go_down);
95         } else
96         {
97                 setthink(this, door_rotating_go_down);
98         }
99         this.nextthink = this.ltime + this.wait;
100 }
101
102 void door_hit_bottom(entity this)
103 {
104         if (this.noise1 != "")
105                 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
106         this.state = STATE_BOTTOM;
107 }
108
109 void door_go_down(entity this)
110 {
111         if (this.noise2 != "")
112                 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
113         if (this.max_health)
114         {
115                 this.takedamage = DAMAGE_YES;
116         #ifdef SVQC
117                 SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
118         #elif defined(CSQC)
119                 this.health = this.max_health;
120         #endif
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 #ifdef SVQC
273         SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH) - damage);
274 #elif defined(CSQC)
275         this.health -= damage;
276 #endif
277
278         if (this.itemkeys)
279         {
280                 // don't allow opening doors through damage if keys are required
281                 return;
282         }
283
284 #ifdef SVQC
285         if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
286         {
287                 SetResourceAmountExplicit(this.owner, RESOURCE_HEALTH, this.owner.max_health);
288                 this.owner.takedamage = DAMAGE_NO;      // will be reset upon return
289                 door_use(this.owner, NULL, NULL);
290         }
291 #elif defined(CSQC)
292         if(this.health <= 0)
293         {
294                 this.owner.health = this.owner.max_health;
295                 this.owner.takedamage = DAMAGE_NO;
296                 door_use(this.owner, NULL, NULL);
297         }
298 #endif
299 }
300
301 .float door_finished;
302
303 /*
304 ================
305 door_touch
306
307 Prints messages
308 ================
309 */
310
311 void door_touch(entity this, entity toucher)
312 {
313         if (!IS_PLAYER(toucher))
314                 return;
315         if (this.owner.door_finished > time)
316                 return;
317
318         this.owner.door_finished = time + 2;
319
320 #ifdef SVQC
321         if (!(this.owner.dmg) && (this.owner.message != ""))
322         {
323                 if (IS_CLIENT(toucher))
324                         centerprint(toucher, this.owner.message);
325                 play2(toucher, this.owner.noise);
326         }
327 #endif
328 }
329
330 void door_generic_plat_blocked(entity this, entity blocker)
331 {
332         if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
333 #ifdef SVQC
334                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
335 #endif
336         }
337         else
338         {
339
340 #ifdef SVQC
341                 if((this.dmg) && (blocker.takedamage == DAMAGE_YES))    // Shall we bite?
342                         Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
343 #endif
344
345                  //Dont chamge direction for dead or dying stuff
346                 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
347                 {
348                         if (this.wait >= 0)
349                         {
350                                 if (this.state == STATE_DOWN)
351                                         door_rotating_go_up (this, blocker);
352                                 else
353                                         door_rotating_go_down (this);
354                         }
355                 }
356 #ifdef SVQC
357                 else
358                 {
359                         //gib dying stuff just to make sure
360                         if((this.dmg) && (blocker.takedamage != DAMAGE_NO))    // Shall we bite?
361                                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
362                 }
363 #endif
364         }
365 }
366
367 /*
368 =========================================
369 door trigger
370
371 Spawned if a door lacks a real activator
372 =========================================
373 */
374
375 void door_trigger_touch(entity this, entity toucher)
376 {
377 #ifdef SVQC
378         if (GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
379                 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
380 #elif defined(CSQC)
381         if (toucher.health < 1)
382                 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
383 #endif
384                         return;
385
386         if (time < this.door_finished)
387                 return;
388
389         // check if door is locked
390         if (!door_check_keys(this, toucher))
391                 return;
392
393         this.door_finished = time + 1;
394
395         door_use(this.owner, toucher, NULL);
396 }
397
398 void door_spawnfield(entity this, vector fmins, vector fmaxs)
399 {
400         entity  trigger;
401         vector  t1 = fmins, t2 = fmaxs;
402
403         trigger = new(doortriggerfield);
404         set_movetype(trigger, MOVETYPE_NONE);
405         trigger.solid = SOLID_TRIGGER;
406         trigger.owner = this;
407 #ifdef SVQC
408         settouch(trigger, door_trigger_touch);
409 #endif
410
411         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
412 }
413
414
415 /*
416 =============
417 LinkDoors
418
419
420 =============
421 */
422
423 entity LinkDoors_nextent(entity cur, entity near, entity pass)
424 {
425         while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
426         {
427         }
428         return cur;
429 }
430
431 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
432 {
433         float DELTA = 4;
434         if((e1.absmin_x > e2.absmax_x + DELTA)
435         || (e1.absmin_y > e2.absmax_y + DELTA)
436         || (e1.absmin_z > e2.absmax_z + DELTA)
437         || (e2.absmin_x > e1.absmax_x + DELTA)
438         || (e2.absmin_y > e1.absmax_y + DELTA)
439         || (e2.absmin_z > e1.absmax_z + DELTA)
440         ) { return false; }
441         return true;
442 }
443
444 #ifdef SVQC
445 void door_link();
446 #endif
447 void LinkDoors(entity this)
448 {
449         entity  t;
450         vector  cmins, cmaxs;
451
452 #ifdef SVQC
453         door_link();
454 #endif
455
456         if (this.enemy)
457                 return;         // already linked by another door
458         if (this.spawnflags & DOOR_DONT_LINK)
459         {
460                 this.owner = this.enemy = this;
461
462 #ifdef SVQC
463                 if (GetResourceAmount(this, RESOURCE_HEALTH))
464 #elif defined(CSQC)
465                 if(this.health)
466 #endif
467                         return;
468                 IFTARGETED
469                         return;
470                 if (this.items)
471                         return;
472
473                 door_spawnfield(this, this.absmin, this.absmax);
474
475                 return;         // don't want to link this door
476         }
477
478         FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
479
480         // set owner, and make a loop of the chain
481         LOG_TRACE("LinkDoors: linking doors:");
482         for(t = this; ; t = t.enemy)
483         {
484                 LOG_TRACE(" ", etos(t));
485                 t.owner = this;
486                 if(t.enemy == NULL)
487                 {
488                         t.enemy = this;
489                         break;
490                 }
491         }
492         LOG_TRACE("");
493
494         // collect health, targetname, message, size
495         cmins = this.absmin;
496         cmaxs = this.absmax;
497         for(t = this; ; t = t.enemy)
498         {
499         #ifdef SVQC
500                 if(GetResourceAmount(t, RESOURCE_HEALTH) && !GetResourceAmount(this, RESOURCE_HEALTH))
501                         SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(t, RESOURCE_HEALTH));
502         #elif defined(CSQC)
503                 if(t.health && !this.health)
504                         this.health = t.health;
505         #endif
506                 if((t.targetname != "") && (this.targetname == ""))
507                         this.targetname = t.targetname;
508                 if((t.message != "") && (this.message == ""))
509                         this.message = t.message;
510                 if (t.absmin_x < cmins_x)
511                         cmins_x = t.absmin_x;
512                 if (t.absmin_y < cmins_y)
513                         cmins_y = t.absmin_y;
514                 if (t.absmin_z < cmins_z)
515                         cmins_z = t.absmin_z;
516                 if (t.absmax_x > cmaxs_x)
517                         cmaxs_x = t.absmax_x;
518                 if (t.absmax_y > cmaxs_y)
519                         cmaxs_y = t.absmax_y;
520                 if (t.absmax_z > cmaxs_z)
521                         cmaxs_z = t.absmax_z;
522                 if(t.enemy == this)
523                         break;
524         }
525
526         // distribute health, targetname, message
527         for(t = this; t; t = t.enemy)
528         {
529         #ifdef SVQC
530                 SetResourceAmountExplicit(t, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
531         #elif defined(CSQC)
532                 t.health = this.health;
533         #endif
534                 t.targetname = this.targetname;
535                 t.message = this.message;
536                 if(t.enemy == this)
537                         break;
538         }
539
540         // shootable, or triggered doors just needed the owner/enemy links,
541         // they don't spawn a field
542
543 #ifdef SVQC
544         if (GetResourceAmount(this, RESOURCE_HEALTH))
545 #elif defined(CSQC)
546         if(this.health)
547 #endif
548                 return;
549         IFTARGETED
550                 return;
551         if (this.items)
552                 return;
553
554         door_spawnfield(this, cmins, cmaxs);
555 }
556
557 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
558
559 #ifdef SVQC
560 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
561 if two doors touch, they are assumed to be connected and operate as a unit.
562
563 TOGGLE causes the door to wait in both the start and end states for a trigger event.
564
565 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).
566
567 GOLD_KEY causes the door to open only if the activator holds a gold key.
568
569 SILVER_KEY causes the door to open only if the activator holds a silver key.
570
571 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
572 "angle"         determines the opening direction
573 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
574 "health"        if set, door must be shot open
575 "speed"         movement speed (100 default)
576 "wait"          wait before returning (3 default, -1 = never return)
577 "lip"           lip remaining at end of move (8 default)
578 "dmg"           damage to inflict when blocked (2 default)
579 "sounds"
580 0)      no sound
581 1)      stone
582 2)      base
583 3)      stone chain
584 4)      screechy metal
585 FIXME: only one sound set available at the time being
586
587 */
588
589 float door_send(entity this, entity to, float sf)
590 {
591         WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
592         WriteByte(MSG_ENTITY, sf);
593
594         if(sf & SF_TRIGGER_INIT)
595         {
596                 WriteString(MSG_ENTITY, this.classname);
597                 WriteByte(MSG_ENTITY, this.spawnflags);
598
599                 WriteString(MSG_ENTITY, this.model);
600
601                 trigger_common_write(this, true);
602
603                 WriteVector(MSG_ENTITY, this.pos1);
604                 WriteVector(MSG_ENTITY, this.pos2);
605
606                 WriteVector(MSG_ENTITY, this.size);
607
608                 WriteShort(MSG_ENTITY, this.wait);
609                 WriteShort(MSG_ENTITY, this.speed);
610                 WriteByte(MSG_ENTITY, this.lip);
611                 WriteByte(MSG_ENTITY, this.state);
612                 WriteCoord(MSG_ENTITY, this.ltime);
613         }
614
615         if(sf & SF_TRIGGER_RESET)
616         {
617                 // client makes use of this, we do not
618         }
619
620         if(sf & SF_TRIGGER_UPDATE)
621         {
622                 WriteVector(MSG_ENTITY, this.origin);
623
624                 WriteVector(MSG_ENTITY, this.pos1);
625                 WriteVector(MSG_ENTITY, this.pos2);
626         }
627
628         return true;
629 }
630
631 void door_link()
632 {
633         // set size now, as everything is loaded
634         //FixSize(this);
635         //Net_LinkEntity(this, false, 0, door_send);
636 }
637 #endif
638
639 void door_init_startopen(entity this)
640 {
641         setorigin(this, this.pos2);
642         this.pos2 = this.pos1;
643         this.pos1 = this.origin;
644
645 #ifdef SVQC
646         this.SendFlags |= SF_TRIGGER_UPDATE;
647 #endif
648 }
649
650 void door_reset(entity this)
651 {
652         setorigin(this, this.pos1);
653         this.velocity = '0 0 0';
654         this.state = STATE_BOTTOM;
655         setthink(this, func_null);
656         this.nextthink = 0;
657
658 #ifdef SVQC
659         this.SendFlags |= SF_TRIGGER_RESET;
660 #endif
661 }
662
663 #ifdef SVQC
664
665 // common code for func_door and func_door_rotating spawnfuncs
666 void door_init_shared(entity this)
667 {
668         this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
669
670         // unlock sound
671         if(this.noise == "")
672         {
673                 this.noise = "misc/talk.wav";
674         }
675         // door still locked sound
676         if(this.noise3 == "")
677         {
678                 this.noise3 = "misc/talk.wav";
679         }
680         precache_sound(this.noise);
681         precache_sound(this.noise3);
682
683         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
684         {
685                 this.message = "was squished";
686         }
687         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
688         {
689                 this.message2 = "was squished by";
690         }
691
692         // TODO: other soundpacks
693         if (this.sounds > 0)
694         {
695                 this.noise2 = "plats/medplat1.wav";
696                 this.noise1 = "plats/medplat2.wav";
697         }
698
699         // sound when door stops moving
700         if(this.noise1 && this.noise1 != "")
701         {
702                 precache_sound(this.noise1);
703         }
704         // sound when door is moving
705         if(this.noise2 && this.noise2 != "")
706         {
707                 precache_sound(this.noise2);
708         }
709
710         if (!this.wait)
711         {
712                 this.wait = 3;
713         }
714         if (!this.lip)
715         {
716                 this.lip = 8;
717         }
718
719         this.state = STATE_BOTTOM;
720
721         if (GetResourceAmount(this, RESOURCE_HEALTH))
722         {
723                 //this.canteamdamage = true; // TODO
724                 this.takedamage = DAMAGE_YES;
725                 this.event_damage = door_damage;
726         }
727
728         if (this.items)
729         {
730                 this.wait = -1;
731         }
732 }
733
734 // spawnflags require key (for now only func_door)
735 spawnfunc(func_door)
736 {
737         // Quake 1 keys compatibility
738         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
739                 this.itemkeys |= ITEM_KEY_BIT(0);
740         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
741                 this.itemkeys |= ITEM_KEY_BIT(1);
742
743         SetMovedir(this);
744
745         if (!InitMovingBrushTrigger(this))
746                 return;
747         this.effects |= EF_LOWPRECISION;
748         this.classname = "door";
749
750         setblocked(this, door_blocked);
751         this.use = door_use;
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         door_init_shared(this);
765
766         if (!this.speed)
767         {
768                 this.speed = 100;
769         }
770
771         settouch(this, door_touch);
772
773 // LinkDoors can't be done until all of the doors have been spawned, so
774 // the sizes can be detected properly.
775         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
776
777         this.reset = door_reset;
778 }
779
780 #elif defined(CSQC)
781
782 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
783 {
784         int sf = ReadByte();
785
786         if(sf & SF_TRIGGER_INIT)
787         {
788                 this.classname = strzone(ReadString());
789                 this.spawnflags = ReadByte();
790
791                 this.mdl = strzone(ReadString());
792                 _setmodel(this, this.mdl);
793
794                 trigger_common_read(this, true);
795
796                 this.pos1 = ReadVector();
797                 this.pos2 = ReadVector();
798
799                 this.size = ReadVector();
800
801                 this.wait = ReadShort();
802                 this.speed = ReadShort();
803                 this.lip = ReadByte();
804                 this.state = ReadByte();
805                 this.ltime = ReadCoord();
806
807                 this.solid = SOLID_BSP;
808                 set_movetype(this, MOVETYPE_PUSH);
809                 this.use = door_use;
810
811                 LinkDoors(this);
812
813                 if(this.spawnflags & DOOR_START_OPEN)
814                         door_init_startopen(this);
815
816                 this.move_time = time;
817                 set_movetype(this, MOVETYPE_PUSH);
818         }
819
820         if(sf & SF_TRIGGER_RESET)
821         {
822                 door_reset(this);
823         }
824
825         if(sf & SF_TRIGGER_UPDATE)
826         {
827                 this.origin = ReadVector();
828                 setorigin(this, this.origin);
829
830                 this.pos1 = ReadVector();
831                 this.pos2 = ReadVector();
832         }
833         return true;
834 }
835
836 #endif