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