Clear out .health and .armorvalue from the client side
[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                 SetResourceAmountExplicit(this, RESOURCE_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         SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_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 (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
277         {
278                 SetResourceAmountExplicit(this.owner, RESOURCE_HEALTH, this.owner.max_health);
279                 this.owner.takedamage = DAMAGE_NO;      // will be reset upon return
280                 door_use(this.owner, NULL, 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 (GetResourceAmount(toucher, RESOURCE_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 (GetResourceAmount(this, RESOURCE_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(GetResourceAmount(t, RESOURCE_HEALTH) && !GetResourceAmount(this, RESOURCE_HEALTH))
478                         SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(t, RESOURCE_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                 SetResourceAmountExplicit(t, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_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 (GetResourceAmount(this, RESOURCE_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         // set size now, as everything is loaded
599         //FixSize(this);
600         //Net_LinkEntity(this, false, 0, door_send);
601 }
602 #endif
603
604 void door_init_startopen(entity this)
605 {
606         setorigin(this, this.pos2);
607         this.pos2 = this.pos1;
608         this.pos1 = this.origin;
609
610 #ifdef SVQC
611         this.SendFlags |= SF_TRIGGER_UPDATE;
612 #endif
613 }
614
615 void door_reset(entity this)
616 {
617         setorigin(this, this.pos1);
618         this.velocity = '0 0 0';
619         this.state = STATE_BOTTOM;
620         setthink(this, func_null);
621         this.nextthink = 0;
622
623 #ifdef SVQC
624         this.SendFlags |= SF_TRIGGER_RESET;
625 #endif
626 }
627
628 #ifdef SVQC
629
630 // common code for func_door and func_door_rotating spawnfuncs
631 void door_init_shared(entity this)
632 {
633         this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
634
635         // unlock sound
636         if(this.noise == "")
637         {
638                 this.noise = "misc/talk.wav";
639         }
640         // door still locked sound
641         if(this.noise3 == "")
642         {
643                 this.noise3 = "misc/talk.wav";
644         }
645         precache_sound(this.noise);
646         precache_sound(this.noise3);
647
648         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
649         {
650                 this.message = "was squished";
651         }
652         if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
653         {
654                 this.message2 = "was squished by";
655         }
656
657         // TODO: other soundpacks
658         if (this.sounds > 0)
659         {
660                 this.noise2 = "plats/medplat1.wav";
661                 this.noise1 = "plats/medplat2.wav";
662         }
663
664         // sound when door stops moving
665         if(this.noise1 && this.noise1 != "")
666         {
667                 precache_sound(this.noise1);
668         }
669         // sound when door is moving
670         if(this.noise2 && this.noise2 != "")
671         {
672                 precache_sound(this.noise2);
673         }
674
675         if (!this.wait)
676         {
677                 this.wait = 3;
678         }
679         if (!this.lip)
680         {
681                 this.lip = 8;
682         }
683
684         this.state = STATE_BOTTOM;
685
686         if (GetResourceAmount(this, RESOURCE_HEALTH))
687         {
688                 //this.canteamdamage = true; // TODO
689                 this.takedamage = DAMAGE_YES;
690                 this.event_damage = door_damage;
691         }
692
693         if (this.items)
694         {
695                 this.wait = -1;
696         }
697 }
698
699 // spawnflags require key (for now only func_door)
700 spawnfunc(func_door)
701 {
702         // Quake 1 keys compatibility
703         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
704                 this.itemkeys |= ITEM_KEY_BIT(0);
705         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
706                 this.itemkeys |= ITEM_KEY_BIT(1);
707
708         SetMovedir(this);
709
710         if (!InitMovingBrushTrigger(this))
711                 return;
712         this.effects |= EF_LOWPRECISION;
713         this.classname = "door";
714
715         setblocked(this, door_blocked);
716         this.use = door_use;
717
718         this.pos1 = this.origin;
719         this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
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         if (!this.speed)
732         {
733                 this.speed = 100;
734         }
735
736         settouch(this, door_touch);
737
738 // LinkDoors can't be done until all of the doors have been spawned, so
739 // the sizes can be detected properly.
740         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
741
742         this.reset = door_reset;
743 }
744
745 #elif defined(CSQC)
746
747 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
748 {
749         int sf = ReadByte();
750
751         if(sf & SF_TRIGGER_INIT)
752         {
753                 this.classname = strzone(ReadString());
754                 this.spawnflags = ReadByte();
755
756                 this.mdl = strzone(ReadString());
757                 _setmodel(this, this.mdl);
758
759                 trigger_common_read(this, true);
760
761                 this.pos1 = ReadVector();
762                 this.pos2 = ReadVector();
763
764                 this.size = ReadVector();
765
766                 this.wait = ReadShort();
767                 this.speed = ReadShort();
768                 this.lip = ReadByte();
769                 this.state = ReadByte();
770                 this.ltime = ReadCoord();
771
772                 this.solid = SOLID_BSP;
773                 set_movetype(this, MOVETYPE_PUSH);
774                 this.use = door_use;
775
776                 LinkDoors(this);
777
778                 if(this.spawnflags & DOOR_START_OPEN)
779                         door_init_startopen(this);
780
781                 this.move_time = time;
782                 set_movetype(this, MOVETYPE_PUSH);
783         }
784
785         if(sf & SF_TRIGGER_RESET)
786         {
787                 door_reset(this);
788         }
789
790         if(sf & SF_TRIGGER_UPDATE)
791         {
792                 this.origin = ReadVector();
793                 setorigin(this, this.origin);
794
795                 this.pos1 = ReadVector();
796                 this.pos2 = ReadVector();
797         }
798         return true;
799 }
800
801 #endif