]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/triggers/func/door.qc
Merge branch 'master' into terencehill/accuracy_shotgun
[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, DMG_NOWEP, 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, 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                         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, DMG_NOWEP, 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, .entity weaponentity, 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, DMG_NOWEP, 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, DMG_NOWEP, 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, DMG_NOWEP, 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                 WriteVector(MSG_ENTITY, this.pos1);
632                 WriteVector(MSG_ENTITY, this.pos2);
633
634                 WriteVector(MSG_ENTITY, this.size);
635
636                 WriteShort(MSG_ENTITY, this.wait);
637                 WriteShort(MSG_ENTITY, this.speed);
638                 WriteByte(MSG_ENTITY, this.lip);
639                 WriteByte(MSG_ENTITY, this.state);
640                 WriteCoord(MSG_ENTITY, this.ltime);
641         }
642
643         if(sf & SF_TRIGGER_RESET)
644         {
645                 // client makes use of this, we do not
646         }
647
648         if(sf & SF_TRIGGER_UPDATE)
649         {
650                 WriteVector(MSG_ENTITY, this.origin);
651
652                 WriteVector(MSG_ENTITY, this.pos1);
653                 WriteVector(MSG_ENTITY, this.pos2);
654         }
655
656         return true;
657 }
658
659 void door_link()
660 {
661         // set size now, as everything is loaded
662         //FixSize(this);
663         //Net_LinkEntity(this, false, 0, door_send);
664 }
665 #endif
666
667 void door_init_startopen(entity this)
668 {
669         setorigin(this, this.pos2);
670         this.pos2 = this.pos1;
671         this.pos1 = this.origin;
672
673 #ifdef SVQC
674         this.SendFlags |= SF_TRIGGER_UPDATE;
675 #endif
676 }
677
678 void door_reset(entity this)
679 {
680         setorigin(this, this.pos1);
681         this.velocity = '0 0 0';
682         this.state = STATE_BOTTOM;
683         setthink(this, func_null);
684         this.nextthink = 0;
685
686 #ifdef SVQC
687         this.SendFlags |= SF_TRIGGER_RESET;
688 #endif
689 }
690
691 #ifdef SVQC
692
693 // spawnflags require key (for now only func_door)
694 spawnfunc(func_door)
695 {
696         // Quake 1 keys compatibility
697         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
698                 this.itemkeys |= ITEM_KEY_BIT(0);
699         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
700                 this.itemkeys |= ITEM_KEY_BIT(1);
701
702         SetMovedir(this);
703
704         this.max_health = this.health;
705         if (!InitMovingBrushTrigger(this))
706                 return;
707         this.effects |= EF_LOWPRECISION;
708         this.classname = "door";
709
710         if(this.noise == "")
711                 this.noise = "misc/talk.wav";
712         if(this.noise3 == "")
713                 this.noise3 = "misc/talk.wav";
714         precache_sound(this.noise);
715         precache_sound(this.noise3);
716
717         setblocked(this, door_blocked);
718         this.use = door_use;
719
720         if(this.dmg && (this.message == ""))
721                 this.message = "was squished";
722         if(this.dmg && (this.message2 == ""))
723                 this.message2 = "was squished by";
724
725         if (this.sounds > 0)
726         {
727                 this.noise2 = "plats/medplat1.wav";
728                 this.noise1 = "plats/medplat2.wav";
729         }
730
731         if(this.noise1 && this.noise1 != "") { precache_sound(this.noise1); }
732         if(this.noise2 && this.noise2 != "") { precache_sound(this.noise2); }
733
734         if (!this.speed)
735                 this.speed = 100;
736         if (!this.wait)
737                 this.wait = 3;
738         if (!this.lip)
739                 this.lip = 8;
740
741         this.pos1 = this.origin;
742         this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
743
744         if(this.spawnflags & DOOR_NONSOLID)
745                 this.solid = SOLID_NOT;
746
747 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
748 // but spawn in the open position
749         if (this.spawnflags & DOOR_START_OPEN)
750                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
751
752         this.state = STATE_BOTTOM;
753
754         if (this.health)
755         {
756                 //this.canteamdamage = true; // TODO
757                 this.takedamage = DAMAGE_YES;
758                 this.event_damage = door_damage;
759         }
760
761         if (this.items)
762                 this.wait = -1;
763
764         settouch(this, door_touch);
765
766 // LinkDoors can't be done until all of the doors have been spawned, so
767 // the sizes can be detected properly.
768         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
769
770         this.reset = door_reset;
771 }
772
773 #elif defined(CSQC)
774
775 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
776 {
777         int sf = ReadByte();
778
779         if(sf & SF_TRIGGER_INIT)
780         {
781                 this.classname = strzone(ReadString());
782                 this.spawnflags = ReadByte();
783
784                 this.mdl = strzone(ReadString());
785                 _setmodel(this, this.mdl);
786
787                 trigger_common_read(this, true);
788
789                 this.pos1 = ReadVector();
790                 this.pos2 = ReadVector();
791
792                 this.size = ReadVector();
793
794                 this.wait = ReadShort();
795                 this.speed = ReadShort();
796                 this.lip = ReadByte();
797                 this.state = ReadByte();
798                 this.ltime = ReadCoord();
799
800                 this.solid = SOLID_BSP;
801                 set_movetype(this, MOVETYPE_PUSH);
802                 this.use = door_use;
803
804                 LinkDoors(this);
805
806                 if(this.spawnflags & DOOR_START_OPEN)
807                         door_init_startopen(this);
808
809                 this.move_time = time;
810                 set_movetype(this, MOVETYPE_PUSH);
811         }
812
813         if(sf & SF_TRIGGER_RESET)
814         {
815                 door_reset(this);
816         }
817
818         if(sf & SF_TRIGGER_UPDATE)
819         {
820                 this.origin = ReadVector();
821                 setorigin(this, this.origin);
822
823                 this.pos1 = ReadVector();
824                 this.pos2 = ReadVector();
825         }
826         return true;
827 }
828
829 #endif