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