1
2float DOOR_START_OPEN = 1;
3float DOOR_DONT_LINK = 4;
4float DOOR_GOLD_KEY = 8;
5float DOOR_SILVER_KEY = 16;
6float DOOR_TOGGLE = 32;
7
8/*
9
10Doors are similar to buttons, but can spawn a fat trigger field around them
11to open without a touch, and they link together to form simultanious
12double/quad doors.
13
14Door.owner is the master door.  If there is only one door, it points to itself.
15If multiple doors, all will point to a single one.
16
17Door.enemy chains from the master door through all doors linked in the chain.
18
19*/
20
21/*
22=============================================================================
23
24THINK FUNCTIONS
25
26=============================================================================
27*/
28
29void() door_go_down;
30void() door_go_up;
31
32void() door_blocked =
33{
34	T_Damage (other, self, self, self.dmg);
35
36// if a door has a negative wait, it would never come back if blocked,
37// so let it just squash the object to death real fast
38	if (self.wait >= 0)
39	{
40		if (self.state == STATE_DOWN)
41			door_go_up ();
42		else
43			door_go_down ();
44	}
45};
46
47
48void() door_hit_top =
49{
50	sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
51	self.state = STATE_TOP;
52	if (self.spawnflags & DOOR_TOGGLE)
53		return;		// don't come down automatically
54	self.think = door_go_down;
55	self.nextthink = self.ltime + self.wait;
56};
57
58void() door_hit_bottom =
59{
60	sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
61	self.state = STATE_BOTTOM;
62};
63
64void() door_go_down =
65{
66	sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
67	if (self.max_health)
68	{
69		self.takedamage = DAMAGE_YES;
70		self.health = self.max_health;
71	}
72
73	self.state = STATE_DOWN;
74	SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
75};
76
77void() door_go_up =
78{
79	if (self.state == STATE_UP)
80		return;		// allready going up
81
82	if (self.state == STATE_TOP)
83	{	// reset top wait time
84		self.nextthink = self.ltime + self.wait;
85		return;
86	}
87
88	sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
89	self.state = STATE_UP;
90	SUB_CalcMove (self.pos2, self.speed, door_hit_top);
91
92	SUB_UseTargets();
93};
94
95
96/*
97=============================================================================
98
99ACTIVATION FUNCTIONS
100
101=============================================================================
102*/
103
104void() door_fire =
105{
106	local entity 	oself;
107	local entity	starte;
108
109	if (self.owner != self)
110		objerror ("door_fire: self.owner != self");
111
112// play use key sound
113
114	if (self.items)
115		sound (self, CHAN_VOICE, self.noise4, 1, ATTN_NORM);
116
117	self.message = string_null;		// no more message
118	oself = self;
119
120	if (self.spawnflags & DOOR_TOGGLE)
121	{
122		if (self.state == STATE_UP || self.state == STATE_TOP)
123		{
124			starte = self;
125			do
126			{
127				door_go_down ();
128				self = self.enemy;
129			} while ( (self != starte) && (self != world) );
130			self = oself;
131			return;
132		}
133	}
134
135// trigger all paired doors
136	starte = self;
137	do
138	{
139		door_go_up ();
140		self = self.enemy;
141	} while ( (self != starte) && (self != world) );
142	self = oself;
143};
144
145
146void() door_use =
147{
148	local entity oself;
149
150	self.message = "";			// door message are for touch only
151	self.owner.message = "";
152	self.enemy.message = "";
153	oself = self;
154	self = self.owner;
155	door_fire ();
156	self = oself;
157};
158
159
160void() door_trigger_touch =
161{
162	if (other.health <= 0)
163		return;
164
165	if (time < self.attack_finished)
166		return;
167	self.attack_finished = time + 1;
168
169	activator = other;
170
171	self = self.owner;
172	door_use ();
173};
174
175
176void() door_killed =
177{
178	local entity oself;
179
180	oself = self;
181	self = self.owner;
182	self.health = self.max_health;
183	self.takedamage = DAMAGE_NO;	// wil be reset upon return
184	door_use ();
185	self = oself;
186};
187
188
189/*
190================
191door_touch
192
193Prints messages and opens key doors
194================
195*/
196void() door_touch =
197{
198	if (other.classname != "player")
199		return;
200	if (self.owner.attack_finished > time)
201		return;
202
203	self.owner.attack_finished = time + 2;
204
205	if (self.owner.message != "")
206	{
207		centerprint (other, self.owner.message);
208		sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM);
209	}
210
211// key door stuff
212	if (!self.items)
213		return;
214
215// FIXME: blink key on player's status bar
216	if ( (self.items & other.items) != self.items )
217	{
218		if (self.owner.items == IT_KEY1)
219		{
220			if (world.worldtype == 2)
221			{
222				centerprint (other, "You need the silver keycard");
223				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
224			}
225			else if (world.worldtype == 1)
226			{
227				centerprint (other, "You need the silver runekey");
228				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
229			}
230			else if (world.worldtype == 0)
231			{
232				centerprint (other, "You need the silver key");
233				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
234			}
235		}
236		else
237		{
238			if (world.worldtype == 2)
239			{
240				centerprint (other, "You need the gold keycard");
241				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
242			}
243			else if (world.worldtype == 1)
244			{
245				centerprint (other, "You need the gold runekey");
246				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
247			}
248			else if (world.worldtype == 0)
249			{
250				centerprint (other, "You need the gold key");
251				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
252			}
253		}
254		return;
255	}
256
257	other.items = other.items - self.items;
258	self.touch = SUB_Null;
259	if (self.enemy)
260		self.enemy.touch = SUB_Null;	// get paired door
261	door_use ();
262};
263
264/*
265=============================================================================
266
267SPAWNING FUNCTIONS
268
269=============================================================================
270*/
271
272
273entity(vector fmins, vector fmaxs) spawn_field =
274{
275	local entity	trigger;
276	local	vector	t1, t2;
277
278	trigger = spawn();
279	trigger.movetype = MOVETYPE_NONE;
280	trigger.solid = SOLID_TRIGGER;
281	trigger.owner = self;
282	trigger.touch = door_trigger_touch;
283
284	t1 = fmins;
285	t2 = fmaxs;
286	setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
287	return (trigger);
288};
289
290
291float (entity e1, entity e2) EntitiesTouching =
292{
293	if (e1.mins_x > e2.maxs_x)
294		return FALSE;
295	if (e1.mins_y > e2.maxs_y)
296		return FALSE;
297	if (e1.mins_z > e2.maxs_z)
298		return FALSE;
299	if (e1.maxs_x < e2.mins_x)
300		return FALSE;
301	if (e1.maxs_y < e2.mins_y)
302		return FALSE;
303	if (e1.maxs_z < e2.mins_z)
304		return FALSE;
305	return TRUE;
306};
307
308
309/*
310=============
311LinkDoors
312
313
314=============
315*/
316void() LinkDoors =
317{
318	local entity	t, starte;
319	local vector	cmins, cmaxs;
320
321	if (self.enemy)
322		return;		// already linked by another door
323	if (self.spawnflags & 4)
324	{
325		self.owner = self.enemy = self;
326		return;		// don't want to link this door
327	}
328
329	cmins = self.mins;
330	cmaxs = self.maxs;
331
332	starte = self;
333	t = self;
334
335	do
336	{
337		self.owner = starte;			// master door
338
339		if (self.health)
340			starte.health = self.health;
341		if (self.targetname)
342			starte.targetname = self.targetname;
343		if (self.message != "")
344			starte.message = self.message;
345
346		t = find (t, classname, self.classname);
347		if (!t)
348		{
349			self.enemy = starte;		// make the chain a loop
350
351		// shootable, fired, or key doors just needed the owner/enemy links,
352		// they don't spawn a field
353
354			self = self.owner;
355
356			if (self.health)
357				return;
358			if (self.targetname)
359				return;
360			if (self.items)
361				return;
362
363			self.owner.trigger_field = spawn_field(cmins, cmaxs);
364
365			return;
366		}
367
368		if (EntitiesTouching(self,t))
369		{
370			if (t.enemy)
371				objerror ("cross connected doors");
372
373			self.enemy = t;
374			self = t;
375
376			if (t.mins_x < cmins_x)
377				cmins_x = t.mins_x;
378			if (t.mins_y < cmins_y)
379				cmins_y = t.mins_y;
380			if (t.mins_z < cmins_z)
381				cmins_z = t.mins_z;
382			if (t.maxs_x > cmaxs_x)
383				cmaxs_x = t.maxs_x;
384			if (t.maxs_y > cmaxs_y)
385				cmaxs_y = t.maxs_y;
386			if (t.maxs_z > cmaxs_z)
387				cmaxs_z = t.maxs_z;
388		}
389	} while (1 );
390
391};
392
393
394/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
395if two doors touch, they are assumed to be connected and operate as a unit.
396
397TOGGLE causes the door to wait in both the start and end states for a trigger event.
398
399START_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 usefull for touch or takedamage doors).
400
401Key doors are allways wait -1.
402
403"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
404"angle"		determines the opening direction
405"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
406"health"	if set, door must be shot open
407"speed"		movement speed (100 default)
408"wait"		wait before returning (3 default, -1 = never return)
409"lip"		lip remaining at end of move (8 default)
410"dmg"		damage to inflict when blocked (2 default)
411"sounds"
4120)	no sound
4131)	stone
4142)	base
4153)	stone chain
4164)	screechy metal
417*/
418
419void() func_door =
420
421{
422
423	if (world.worldtype == 0)
424	{
425		precache_sound ("doors/medtry.wav");
426		precache_sound ("doors/meduse.wav");
427		self.noise3 = "doors/medtry.wav";
428		self.noise4 = "doors/meduse.wav";
429	}
430	else if (world.worldtype == 1)
431	{
432		precache_sound ("doors/runetry.wav");
433		precache_sound ("doors/runeuse.wav");
434		self.noise3 = "doors/runetry.wav";
435		self.noise4 = "doors/runeuse.wav";
436	}
437	else if (world.worldtype == 2)
438	{
439		precache_sound ("doors/basetry.wav");
440		precache_sound ("doors/baseuse.wav");
441		self.noise3 = "doors/basetry.wav";
442		self.noise4 = "doors/baseuse.wav";
443	}
444	else
445	{
446		dprint ("no worldtype set!\n");
447	}
448	if (self.sounds == 0)
449	{
450		precache_sound ("misc/null.wav");
451		precache_sound ("misc/null.wav");
452		self.noise1 = "misc/null.wav";
453		self.noise2 = "misc/null.wav";
454	}
455	if (self.sounds == 1)
456	{
457		precache_sound ("doors/drclos4.wav");
458		precache_sound ("doors/doormv1.wav");
459		self.noise1 = "doors/drclos4.wav";
460		self.noise2 = "doors/doormv1.wav";
461	}
462	if (self.sounds == 2)
463	{
464		precache_sound ("doors/hydro1.wav");
465		precache_sound ("doors/hydro2.wav");
466		self.noise2 = "doors/hydro1.wav";
467		self.noise1 = "doors/hydro2.wav";
468	}
469	if (self.sounds == 3)
470	{
471		precache_sound ("doors/stndr1.wav");
472		precache_sound ("doors/stndr2.wav");
473		self.noise2 = "doors/stndr1.wav";
474		self.noise1 = "doors/stndr2.wav";
475	}
476	if (self.sounds == 4)
477	{
478		precache_sound ("doors/ddoor1.wav");
479		precache_sound ("doors/ddoor2.wav");
480		self.noise1 = "doors/ddoor2.wav";
481		self.noise2 = "doors/ddoor1.wav";
482	}
483
484
485	SetMovedir ();
486
487	self.max_health = self.health;
488	self.solid = SOLID_BSP;
489	self.movetype = MOVETYPE_PUSH;
490	setorigin (self, self.origin);
491	setmodel (self, self.model);
492	self.classname = "door";
493
494	self.blocked = door_blocked;
495	self.use = door_use;
496
497	if (self.spawnflags & DOOR_SILVER_KEY)
498		self.items = IT_KEY1;
499	if (self.spawnflags & DOOR_GOLD_KEY)
500		self.items = IT_KEY2;
501
502	if (!self.speed)
503		self.speed = 100;
504	if (!self.wait)
505		self.wait = 3;
506	if (!self.lip)
507		self.lip = 8;
508	if (!self.dmg)
509		self.dmg = 2;
510
511	self.pos1 = self.origin;
512	self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
513
514// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
515// but spawn in the open position
516	if (self.spawnflags & DOOR_START_OPEN)
517	{
518		setorigin (self, self.pos2);
519		self.pos2 = self.pos1;
520		self.pos1 = self.origin;
521	}
522
523	self.state = STATE_BOTTOM;
524
525	if (self.health)
526	{
527		self.takedamage = DAMAGE_YES;
528		self.th_die = door_killed;
529	}
530
531	if (self.items)
532		self.wait = -1;
533
534	self.touch = door_touch;
535
536// LinkDoors can't be done until all of the doors have been spawned, so
537// the sizes can be detected properly.
538	self.think = LinkDoors;
539	self.nextthink = self.ltime + 0.1;
540};
541
542/*
543=============================================================================
544
545SECRET DOORS
546
547=============================================================================
548*/
549
550void() fd_secret_move1;
551void() fd_secret_move2;
552void() fd_secret_move3;
553void() fd_secret_move4;
554void() fd_secret_move5;
555void() fd_secret_move6;
556void() fd_secret_done;
557
558float SECRET_OPEN_ONCE = 1;		// stays open
559float SECRET_1ST_LEFT = 2;		// 1st move is left of arrow
560float SECRET_1ST_DOWN = 4;		// 1st move is down from arrow
561float SECRET_NO_SHOOT = 8;		// only opened by trigger
562float SECRET_YES_SHOOT = 16;	// shootable even if targeted
563
564
565void () fd_secret_use =
566{
567	local float temp;
568
569	self.health = 10000;
570
571	// exit if still moving around...
572	if (self.origin != self.oldorigin)
573		return;
574
575	self.message = string_null;		// no more message
576
577	SUB_UseTargets();				// fire all targets / killtargets
578
579	if (!(self.spawnflags & SECRET_NO_SHOOT))
580	{
581		self.th_pain = SUB_Null;
582		self.takedamage = DAMAGE_NO;
583	}
584	self.velocity = '0 0 0';
585
586	// Make a sound, wait a little...
587
588	sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
589	self.nextthink = self.ltime + 0.1;
590
591	temp = 1 - (self.spawnflags & SECRET_1ST_LEFT);	// 1 or -1
592	makevectors(self.mangle);
593
594	if (!self.t_width)
595	{
596		if (self.spawnflags & SECRET_1ST_DOWN)
597			self. t_width = fabs(v_up * self.size);
598		else
599			self. t_width = fabs(v_right * self.size);
600	}
601
602	if (!self.t_length)
603		self. t_length = fabs(v_forward * self.size);
604
605	if (self.spawnflags & SECRET_1ST_DOWN)
606		self.dest1 = self.origin - v_up * self.t_width;
607	else
608		self.dest1 = self.origin + v_right * (self.t_width * temp);
609
610	self.dest2 = self.dest1 + v_forward * self.t_length;
611	SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
612	sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
613};
614
615// Wait after first movement...
616void () fd_secret_move1 =
617{
618	self.nextthink = self.ltime + 1.0;
619	self.think = fd_secret_move2;
620	sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
621};
622
623// Start moving sideways w/sound...
624void () fd_secret_move2 =
625{
626	sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
627	SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
628};
629
630// Wait here until time to go back...
631void () fd_secret_move3 =
632{
633	sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
634	if (!(self.spawnflags & SECRET_OPEN_ONCE))
635	{
636		self.nextthink = self.ltime + self.wait;
637		self.think = fd_secret_move4;
638	}
639};
640
641// Move backward...
642void () fd_secret_move4 =
643{
644	sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
645	SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
646};
647
648// Wait 1 second...
649void () fd_secret_move5 =
650{
651	self.nextthink = self.ltime + 1.0;
652	self.think = fd_secret_move6;
653	sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
654};
655
656void () fd_secret_move6 =
657{
658	sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
659	SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
660};
661
662void () fd_secret_done =
663{
664	if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT)
665	{
666		self.health = 10000;
667		self.takedamage = DAMAGE_YES;
668		self.th_pain = fd_secret_use;
669	}
670	sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
671};
672
673void () secret_blocked =
674{
675	if (time < self.attack_finished)
676		return;
677	self.attack_finished = time + 0.5;
678	T_Damage (other, self, self, self.dmg);
679};
680
681/*
682================
683secret_touch
684
685Prints messages
686================
687*/
688void() secret_touch =
689{
690	if (other.classname != "player")
691		return;
692	if (self.attack_finished > time)
693		return;
694
695	self.attack_finished = time + 2;
696
697	if (self.message)
698	{
699		centerprint (other, self.message);
700		sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
701	}
702};
703
704
705/*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
706Basic secret door. Slides back, then to the side. Angle determines direction.
707wait  = # of seconds before coming back
7081st_left = 1st move is left of arrow
7091st_down = 1st move is down from arrow
710always_shoot = even if targeted, keep shootable
711t_width = override WIDTH to move back (or height if going down)
712t_length = override LENGTH to move sideways
713"dmg"		damage to inflict when blocked (2 default)
714
715If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
716"sounds"
7171) medieval
7182) metal
7193) base
720*/
721
722void () func_door_secret =
723{
724	if (self.sounds == 0)
725		self.sounds = 3;
726	if (self.sounds == 1)
727	{
728		precache_sound ("doors/latch2.wav");
729		precache_sound ("doors/winch2.wav");
730		precache_sound ("doors/drclos4.wav");
731		self.noise1 = "doors/latch2.wav";
732		self.noise2 = "doors/winch2.wav";
733		self.noise3 = "doors/drclos4.wav";
734	}
735	if (self.sounds == 2)
736	{
737		precache_sound ("doors/airdoor1.wav");
738		precache_sound ("doors/airdoor2.wav");
739		self.noise2 = "doors/airdoor1.wav";
740		self.noise1 = "doors/airdoor2.wav";
741		self.noise3 = "doors/airdoor2.wav";
742	}
743	if (self.sounds == 3)
744	{
745		precache_sound ("doors/basesec1.wav");
746		precache_sound ("doors/basesec2.wav");
747		self.noise2 = "doors/basesec1.wav";
748		self.noise1 = "doors/basesec2.wav";
749		self.noise3 = "doors/basesec2.wav";
750	}
751
752	if (!self.dmg)
753		self.dmg = 2;
754
755	// Magic formula...
756	self.mangle = self.angles;
757	self.angles = '0 0 0';
758	self.solid = SOLID_BSP;
759	self.movetype = MOVETYPE_PUSH;
760	self.classname = "door";
761	setmodel (self, self.model);
762	setorigin (self, self.origin);
763
764	self.touch = secret_touch;
765	self.blocked = secret_blocked;
766	self.speed = 50;
767	self.use = fd_secret_use;
768	if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
769	{
770		self.health = 10000;
771		self.takedamage = DAMAGE_YES;
772		self.th_pain = fd_secret_use;
773	}
774	self.oldorigin = self.origin;
775	if (!self.wait)
776		self.wait = 5;		// 5 seconds before closing
777};
778