]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mapobjects/func/door.qc
Fix error spam that occurs when standing in the way of rotating doors
[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_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                                 {
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 (time < this.door_finished)
373                 return;
374
375         // check if door is locked
376         if (!door_check_keys(this, toucher))
377                 return;
378
379         this.door_finished = time + 1;
380
381         door_use(this.owner, toucher, NULL);
382 }
383
384 void door_spawnfield(entity this, vector fmins, vector fmaxs)
385 {
386         entity  trigger;
387         vector  t1 = fmins, t2 = fmaxs;
388
389         trigger = new(doortriggerfield);
390         set_movetype(trigger, MOVETYPE_NONE);
391         trigger.solid = SOLID_TRIGGER;
392         trigger.owner = this;
393 #ifdef SVQC
394         settouch(trigger, door_trigger_touch);
395 #endif
396
397         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
398 }
399
400
401 /*
402 =============
403 LinkDoors
404
405
406 =============
407 */
408
409 entity LinkDoors_nextent(entity cur, entity near, entity pass)
410 {
411         while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
412         {
413         }
414         return cur;
415 }
416
417 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
418 {
419         float DELTA = 4;
420         if((e1.absmin_x > e2.absmax_x + DELTA)
421         || (e1.absmin_y > e2.absmax_y + DELTA)
422         || (e1.absmin_z > e2.absmax_z + DELTA)
423         || (e2.absmin_x > e1.absmax_x + DELTA)
424         || (e2.absmin_y > e1.absmax_y + DELTA)
425         || (e2.absmin_z > e1.absmax_z + DELTA)
426         ) { return false; }
427         return true;
428 }
429
430 #ifdef SVQC
431 void door_link();
432 #endif
433 void LinkDoors(entity this)
434 {
435         entity  t;
436         vector  cmins, cmaxs;
437
438 #ifdef SVQC
439         door_link();
440 #endif
441
442         if (this.enemy)
443                 return;         // already linked by another door
444         if (this.spawnflags & DOOR_DONT_LINK)
445         {
446                 this.owner = this.enemy = this;
447
448                 if (GetResource(this, RES_HEALTH))
449                         return;
450                 if(this.targetname && this.targetname != "")
451                         return;
452                 if (this.items)
453                         return;
454
455                 door_spawnfield(this, this.absmin, this.absmax);
456
457                 return;         // don't want to link this door
458         }
459
460         FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
461
462         // set owner, and make a loop of the chain
463         LOG_TRACE("LinkDoors: linking doors:");
464         for(t = this; ; t = t.enemy)
465         {
466                 LOG_TRACE(" ", etos(t));
467                 t.owner = this;
468                 if(t.enemy == NULL)
469                 {
470                         t.enemy = this;
471                         break;
472                 }
473         }
474         LOG_TRACE("");
475
476         // collect health, targetname, message, size
477         cmins = this.absmin;
478         cmaxs = this.absmax;
479         for(t = this; ; t = t.enemy)
480         {
481                 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
482                         SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
483                 if((t.targetname != "") && (this.targetname == ""))
484                         this.targetname = t.targetname;
485                 if((t.message != "") && (this.message == ""))
486                         this.message = t.message;
487                 if (t.absmin_x < cmins_x)
488                         cmins_x = t.absmin_x;
489                 if (t.absmin_y < cmins_y)
490                         cmins_y = t.absmin_y;
491                 if (t.absmin_z < cmins_z)
492                         cmins_z = t.absmin_z;
493                 if (t.absmax_x > cmaxs_x)
494                         cmaxs_x = t.absmax_x;
495                 if (t.absmax_y > cmaxs_y)
496                         cmaxs_y = t.absmax_y;
497                 if (t.absmax_z > cmaxs_z)
498                         cmaxs_z = t.absmax_z;
499                 if(t.enemy == this)
500                         break;
501         }
502
503         // distribute health, targetname, message
504         for(t = this; t; t = t.enemy)
505         {
506                 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
507                 t.targetname = this.targetname;
508                 t.message = this.message;
509                 if(t.enemy == this)
510                         break;
511         }
512
513         // shootable, or triggered doors just needed the owner/enemy links,
514         // they don't spawn a field
515
516         if (GetResource(this, RES_HEALTH))
517                 return;
518         if(this.targetname && this.targetname != "")
519                 return;
520         if (this.items)
521                 return;
522
523         door_spawnfield(this, cmins, cmaxs);
524 }
525
526 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
527
528 #ifdef SVQC
529 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
530 if two doors touch, they are assumed to be connected and operate as a unit.
531
532 TOGGLE causes the door to wait in both the start and end states for a trigger event.
533
534 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).
535
536 GOLD_KEY causes the door to open only if the activator holds a gold key.
537
538 SILVER_KEY causes the door to open only if the activator holds a silver key.
539
540 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
541 "angle"         determines the opening direction
542 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
543 "health"        if set, door must be shot open
544 "speed"         movement speed (100 default)
545 "wait"          wait before returning (3 default, -1 = never return)
546 "lip"           lip remaining at end of move (8 default)
547 "dmg"           damage to inflict when blocked (2 default)
548 "sounds"
549 0)      no sound
550 1)      stone
551 2)      base
552 3)      stone chain
553 4)      screechy metal
554 FIXME: only one sound set available at the time being
555
556 */
557
558 float door_send(entity this, entity to, float sf)
559 {
560         WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
561         WriteByte(MSG_ENTITY, sf);
562
563         if(sf & SF_TRIGGER_INIT)
564         {
565                 WriteString(MSG_ENTITY, this.classname);
566                 WriteByte(MSG_ENTITY, this.spawnflags);
567
568                 WriteString(MSG_ENTITY, this.model);
569
570                 trigger_common_write(this, true);
571
572                 WriteVector(MSG_ENTITY, this.pos1);
573                 WriteVector(MSG_ENTITY, this.pos2);
574
575                 WriteVector(MSG_ENTITY, this.size);
576
577                 WriteShort(MSG_ENTITY, this.wait);
578                 WriteShort(MSG_ENTITY, this.speed);
579                 WriteByte(MSG_ENTITY, this.lip);
580                 WriteByte(MSG_ENTITY, this.state);
581                 WriteCoord(MSG_ENTITY, this.ltime);
582         }
583
584         if(sf & SF_TRIGGER_RESET)
585         {
586                 // client makes use of this, we do not
587         }
588
589         if(sf & SF_TRIGGER_UPDATE)
590         {
591                 WriteVector(MSG_ENTITY, this.origin);
592
593                 WriteVector(MSG_ENTITY, this.pos1);
594                 WriteVector(MSG_ENTITY, this.pos2);
595         }
596
597         return true;
598 }
599
600 void door_link()
601 {
602         //Net_LinkEntity(this, false, 0, door_send);
603 }
604 #endif
605
606 void door_init_startopen(entity this)
607 {
608         setorigin(this, this.pos2);
609         this.pos2 = this.pos1;
610         this.pos1 = this.origin;
611
612 #ifdef SVQC
613         this.SendFlags |= SF_TRIGGER_UPDATE;
614 #endif
615 }
616
617 void door_reset(entity this)
618 {
619         setorigin(this, this.pos1);
620         this.velocity = '0 0 0';
621         this.state = STATE_BOTTOM;
622         setthink(this, func_null);
623         this.nextthink = 0;
624
625 #ifdef SVQC
626         this.SendFlags |= SF_TRIGGER_RESET;
627 #endif
628 }
629
630 #ifdef SVQC
631
632 // common code for func_door and func_door_rotating spawnfuncs
633 void door_init_shared(entity this)
634 {
635         this.max_health = GetResource(this, RES_HEALTH);
636
637         // unlock sound
638         if(this.noise == "")
639         {
640                 this.noise = "misc/talk.wav";
641         }
642         // door still locked sound
643         if(this.noise3 == "")
644         {
645                 this.noise3 = "misc/talk.wav";
646         }
647         precache_sound(this.noise);
648         precache_sound(this.noise3);
649
650         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
651         {
652                 this.message = "was squished";
653         }
654         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
655         {
656                 this.message2 = "was squished by";
657         }
658
659         // TODO: other soundpacks
660         if (this.sounds > 0 || q3compat)
661         {
662                 // Doors in Q3 always have sounds (they're hard coded)
663                 this.noise2 = "plats/medplat1.wav";
664                 this.noise1 = "plats/medplat2.wav";
665         }
666
667         if (q3compat)
668         {
669                 // CPMA adds these fields for overriding the engine sounds
670                 string s = GetField_fullspawndata(this, "sound_start", true);
671                 string e = GetField_fullspawndata(this, "sound_end", true);
672
673                 if (s)
674                         this.noise2 = strzone(s);
675                 if (e)
676                         this.noise1 = strzone(e);
677         }
678
679         // sound when door stops moving
680         if(this.noise1 && this.noise1 != "")
681         {
682                 precache_sound(this.noise1);
683         }
684         // sound when door is moving
685         if(this.noise2 && this.noise2 != "")
686         {
687                 precache_sound(this.noise2);
688         }
689
690         if(autocvar_sv_doors_always_open)
691         {
692                 this.wait = -1;
693         }
694         else if (!this.wait)
695         {
696                 this.wait = 3;
697         }
698
699         if (!this.lip)
700         {
701                 this.lip = 8;
702         }
703
704         this.state = STATE_BOTTOM;
705
706         if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
707         {
708                 //this.canteamdamage = true; // TODO
709                 this.takedamage = DAMAGE_YES;
710                 this.event_damage = door_damage;
711         }
712
713         if (this.items)
714         {
715                 this.wait = -1;
716         }
717 }
718
719 // spawnflags require key (for now only func_door)
720 spawnfunc(func_door)
721 {
722         // Quake 1 keys compatibility
723         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
724                 this.itemkeys |= ITEM_KEY_BIT(0);
725         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
726                 this.itemkeys |= ITEM_KEY_BIT(1);
727
728         SetMovedir(this);
729
730         if (!InitMovingBrushTrigger(this))
731                 return;
732         this.effects |= EF_LOWPRECISION;
733         this.classname = "door";
734
735         setblocked(this, door_blocked);
736         this.use = door_use;
737
738         if(this.spawnflags & DOOR_NONSOLID)
739                 this.solid = SOLID_NOT;
740
741 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
742 // but spawn in the open position
743         if (this.spawnflags & DOOR_START_OPEN)
744                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
745
746         door_init_shared(this);
747
748         this.pos1 = this.origin;
749         this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
750
751         if(autocvar_sv_doors_always_open)
752         {
753                 this.speed = max(750, this.speed);
754         }
755         else if (!this.speed)
756         {
757                 if (q3compat)
758                         this.speed = 400;
759                 else
760                         this.speed = 100;
761         }
762
763         settouch(this, door_touch);
764
765 // LinkDoors can't be done until all of the doors have been spawned, so
766 // the sizes can be detected properly.
767         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
768
769         this.reset = door_reset;
770 }
771
772 #elif defined(CSQC)
773
774 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
775 {
776         int sf = ReadByte();
777
778         if(sf & SF_TRIGGER_INIT)
779         {
780                 this.classname = strzone(ReadString());
781                 this.spawnflags = ReadByte();
782
783                 this.mdl = strzone(ReadString());
784                 _setmodel(this, this.mdl);
785
786                 trigger_common_read(this, true);
787
788                 this.pos1 = ReadVector();
789                 this.pos2 = ReadVector();
790
791                 this.size = ReadVector();
792
793                 this.wait = ReadShort();
794                 this.speed = ReadShort();
795                 this.lip = ReadByte();
796                 this.state = ReadByte();
797                 this.ltime = ReadCoord();
798
799                 this.solid = SOLID_BSP;
800                 set_movetype(this, MOVETYPE_PUSH);
801                 this.use = door_use;
802
803                 LinkDoors(this);
804
805                 if(this.spawnflags & DOOR_START_OPEN)
806                         door_init_startopen(this);
807
808                 this.move_time = time;
809                 set_movetype(this, MOVETYPE_PUSH);
810         }
811
812         if(sf & SF_TRIGGER_RESET)
813         {
814                 door_reset(this);
815         }
816
817         if(sf & SF_TRIGGER_UPDATE)
818         {
819                 this.origin = ReadVector();
820                 setorigin(this, this.origin);
821
822                 this.pos1 = ReadVector();
823                 this.pos2 = ReadVector();
824         }
825         return true;
826 }
827
828 #endif