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