1 /*
2 ** p_switch.cpp
3 ** Switch and button maintenance and animation
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 
35 #include "templates.h"
36 #include "i_system.h"
37 #include "doomdef.h"
38 #include "p_local.h"
39 #include "p_lnspec.h"
40 #include "p_3dmidtex.h"
41 #include "m_random.h"
42 #include "g_game.h"
43 #include "s_sound.h"
44 #include "doomstat.h"
45 #include "r_state.h"
46 #include "w_wad.h"
47 #include "tarray.h"
48 #include "cmdlib.h"
49 #include "farchive.h"
50 
51 #include "gi.h"
52 
53 static FRandom pr_switchanim ("AnimSwitch");
54 
55 class DActiveButton : public DThinker
56 {
57 	DECLARE_CLASS (DActiveButton, DThinker)
58 public:
59 	DActiveButton ();
60 	DActiveButton (side_t *, int, FSwitchDef *, fixed_t x, fixed_t y, bool flippable);
61 
62 	void Serialize (FArchive &arc);
63 	void Tick ();
64 
65 	side_t			*m_Side;
66 	SBYTE			m_Part;
67 	bool			bFlippable;
68 	bool			bReturning;
69 	FSwitchDef		*m_SwitchDef;
70 	SDWORD			m_Frame;
71 	DWORD			m_Timer;
72 	fixed_t			m_X, m_Y;	// Location of timer sound
73 
74 protected:
75 	bool AdvanceFrame ();
76 };
77 
78 
79 //==========================================================================
80 //
81 // Start a button counting down till it turns off.
82 // [RH] Rewritten to remove MAXBUTTONS limit.
83 //
84 //==========================================================================
85 
P_StartButton(side_t * side,int Where,FSwitchDef * Switch,fixed_t x,fixed_t y,bool useagain)86 static bool P_StartButton (side_t *side, int Where, FSwitchDef *Switch, fixed_t x, fixed_t y, bool useagain)
87 {
88 	DActiveButton *button;
89 	TThinkerIterator<DActiveButton> iterator;
90 
91 	// See if button is already pressed
92 	while ( (button = iterator.Next ()) )
93 	{
94 		if (button->m_Side == side)
95 		{
96 			button->m_Timer=1;	// force advancing to the next frame
97 			return false;
98 		}
99 	}
100 
101 	new DActiveButton (side, Where, Switch, x, y, useagain);
102 	return true;
103 }
104 
105 //==========================================================================
106 //
107 // Checks whether a switch is reachable
108 // This is optional because old maps can rely on being able to
109 // use non-reachable switches.
110 //
111 //==========================================================================
112 
P_CheckSwitchRange(AActor * user,line_t * line,int sideno)113 bool P_CheckSwitchRange(AActor *user, line_t *line, int sideno)
114 {
115 	// Activated from an empty side -> always succeed
116 	side_t *side = line->sidedef[sideno];
117 	if (side == NULL)
118 		return true;
119 
120 	fixed_t checktop;
121 	fixed_t checkbot;
122 	sector_t *front = side->sector;
123 	FLineOpening open;
124 	int flags = line->flags;
125 
126 	if (!side->GetTexture(side_t::mid).isValid())
127 	{ // Do not force range checks for 3DMIDTEX lines if there is no actual midtexture.
128 		flags &= ~ML_3DMIDTEX;
129 	}
130 
131 	// 3DMIDTEX forces CHECKSWITCHRANGE because otherwise it might cause problems.
132 	if (!(flags & (ML_3DMIDTEX|ML_CHECKSWITCHRANGE)))
133 		return true;
134 
135 	// calculate the point where the user would touch the wall.
136 	divline_t dll, dlu;
137 	fixed_t inter, checkx, checky;
138 
139 	P_MakeDivline (line, &dll);
140 
141 	fixedvec3 pos = user->PosRelative(line);
142 	dlu.x = pos.x;
143 	dlu.y = pos.y;
144 	dlu.dx = finecosine[user->angle >> ANGLETOFINESHIFT];
145 	dlu.dy = finesine[user->angle >> ANGLETOFINESHIFT];
146 	inter = P_InterceptVector(&dll, &dlu);
147 
148 
149 	// Polyobjects must test the containing sector, not the one they originate from.
150 	if (line->sidedef[0]->Flags & WALLF_POLYOBJ)
151 	{
152 		// Get a check point slightly inside the polyobject so that this still works
153 		// if the polyobject lies directly on a sector boundary
154 		checkx = dll.x + FixedMul(dll.dx, inter + (FRACUNIT/100));
155 		checky = dll.y + FixedMul(dll.dy, inter + (FRACUNIT/100));
156 		front = P_PointInSector(checkx, checky);
157 	}
158 	else
159 	{
160 		checkx = dll.x + FixedMul(dll.dx, inter);
161 		checky = dll.y + FixedMul(dll.dy, inter);
162 	}
163 
164 
165 	// one sided line or polyobject
166 	if (line->sidedef[1] == NULL || (line->sidedef[0]->Flags & WALLF_POLYOBJ))
167 	{
168 	onesided:
169 		fixed_t sectorc = front->ceilingplane.ZatPoint(checkx, checky);
170 		fixed_t sectorf = front->floorplane.ZatPoint(checkx, checky);
171 		return (user->Top() >= sectorf && user->Z() <= sectorc);
172 	}
173 
174 	// Now get the information from the line.
175 	P_LineOpening(open, NULL, line, checkx, checky, pos.x, pos.y);
176 	if (open.range <= 0)
177 		goto onesided;
178 
179 	if ((TexMan.FindSwitch(side->GetTexture(side_t::top))) != NULL)
180 	{
181 
182 		// Check 3D floors on back side
183 		{
184 			sector_t * back = line->sidedef[1 - sideno]->sector;
185 			for (unsigned i = 0; i < back->e->XFloor.ffloors.Size(); i++)
186 			{
187 				F3DFloor *rover = back->e->XFloor.ffloors[i];
188 				if (!(rover->flags & FF_EXISTS)) continue;
189 				if (!(rover->flags & FF_UPPERTEXTURE)) continue;
190 
191 				if (user->Z() > rover->top.plane->ZatPoint(checkx, checky) ||
192 					user->Top() < rover->bottom.plane->ZatPoint(checkx, checky))
193 					continue;
194 
195 				// This 3D floor depicts a switch texture in front of the player's eyes
196 				return true;
197 			}
198 		}
199 
200 		return (user->Top() > open.top);
201 	}
202 	else if ((TexMan.FindSwitch(side->GetTexture(side_t::bottom))) != NULL)
203 	{
204 		// Check 3D floors on back side
205 		{
206 			sector_t * back = line->sidedef[1 - sideno]->sector;
207 			for (unsigned i = 0; i < back->e->XFloor.ffloors.Size(); i++)
208 			{
209 				F3DFloor *rover = back->e->XFloor.ffloors[i];
210 				if (!(rover->flags & FF_EXISTS)) continue;
211 				if (!(rover->flags & FF_LOWERTEXTURE)) continue;
212 
213 				if (user->Z() > rover->top.plane->ZatPoint(checkx, checky) ||
214 					user->Top() < rover->bottom.plane->ZatPoint(checkx, checky))
215 					continue;
216 
217 				// This 3D floor depicts a switch texture in front of the player's eyes
218 				return true;
219 			}
220 		}
221 
222 		return (user->Z() < open.bottom);
223 	}
224 	else if ((flags & ML_3DMIDTEX) || (TexMan.FindSwitch(side->GetTexture(side_t::mid))) != NULL)
225 	{
226 		// 3DMIDTEX lines will force a mid texture check if no switch is found on this line
227 		// to keep compatibility with Eternity's implementation.
228 		if (!P_GetMidTexturePosition(line, sideno, &checktop, &checkbot))
229 			return false;
230 		return user->Z() < checktop && user->Top() > checkbot;
231 	}
232 	else
233 	{
234 		// no switch found. Check whether the player can touch either top or bottom texture
235 		return (user->Top() > open.top) || (user->Z() < open.bottom);
236 	}
237 }
238 
239 //==========================================================================
240 //
241 // Function that changes wall texture.
242 // Tell it if switch is ok to use again (1=yes, it's a button).
243 //
244 //==========================================================================
245 
P_ChangeSwitchTexture(side_t * side,int useAgain,BYTE special,bool * quest)246 bool P_ChangeSwitchTexture (side_t *side, int useAgain, BYTE special, bool *quest)
247 {
248 	int texture;
249 	int sound;
250 	FSwitchDef *Switch;
251 
252 	if ((Switch = TexMan.FindSwitch (side->GetTexture(side_t::top))) != NULL)
253 	{
254 		texture = side_t::top;
255 	}
256 	else if ((Switch = TexMan.FindSwitch (side->GetTexture(side_t::bottom))) != NULL)
257 	{
258 		texture = side_t::bottom;
259 	}
260 	else if ((Switch = TexMan.FindSwitch (side->GetTexture(side_t::mid))) != NULL)
261 	{
262 		texture = side_t::mid;
263 	}
264 	else
265 	{
266 		if (quest != NULL)
267 		{
268 			*quest = false;
269 		}
270 		return false;
271 	}
272 
273 	// EXIT SWITCH?
274 	if (Switch->Sound != 0)
275 	{
276 		sound = Switch->Sound;
277 	}
278 	else
279 	{
280 		sound = S_FindSound (
281 			special == Exit_Normal ||
282 			special == Exit_Secret ||
283 			special == Teleport_NewMap ||
284 			special == Teleport_EndGame
285 		   ? "switches/exitbutn" : "switches/normbutn");
286 	}
287 
288 	// [RH] The original code played the sound at buttonlist->soundorg,
289 	//		which wasn't necessarily anywhere near the switch if it was
290 	//		facing a big sector (and which wasn't necessarily for the
291 	//		button just activated, either).
292 	fixed_t pt[2];
293 	line_t *line = side->linedef;
294 	bool playsound;
295 
296 	pt[0] = line->v1->x + (line->dx >> 1);
297 	pt[1] = line->v1->y + (line->dy >> 1);
298 	side->SetTexture(texture, Switch->frames[0].Texture);
299 	if (useAgain || Switch->NumFrames > 1)
300 	{
301 		playsound = P_StartButton (side, texture, Switch, pt[0], pt[1], !!useAgain);
302 	}
303 	else
304 	{
305 		playsound = true;
306 	}
307 	if (playsound)
308 	{
309 		S_Sound (pt[0], pt[1], 0, CHAN_VOICE|CHAN_LISTENERZ, sound, 1, ATTN_STATIC);
310 	}
311 	if (quest != NULL)
312 	{
313 		*quest = Switch->QuestPanel;
314 	}
315 	return true;
316 }
317 
318 //==========================================================================
319 //
320 // Button thinker
321 //
322 //==========================================================================
323 
IMPLEMENT_CLASS(DActiveButton)324 IMPLEMENT_CLASS (DActiveButton)
325 
326 DActiveButton::DActiveButton ()
327 {
328 	m_Side = NULL;
329 	m_Part = -1;
330 	m_SwitchDef = 0;
331 	m_Timer = 0;
332 	m_X = 0;
333 	m_Y = 0;
334 	bFlippable = false;
335 	bReturning = false;
336 	m_Frame = 0;
337 }
338 
DActiveButton(side_t * side,int Where,FSwitchDef * Switch,fixed_t x,fixed_t y,bool useagain)339 DActiveButton::DActiveButton (side_t *side, int Where, FSwitchDef *Switch,
340 							  fixed_t x, fixed_t y, bool useagain)
341 {
342 	m_Side = side;
343 	m_Part = SBYTE(Where);
344 	m_X = x;
345 	m_Y = y;
346 	bFlippable = useagain;
347 	bReturning = false;
348 
349 	m_SwitchDef = Switch;
350 	m_Frame = -1;
351 	AdvanceFrame ();
352 }
353 
354 //==========================================================================
355 //
356 //
357 //
358 //==========================================================================
359 
Serialize(FArchive & arc)360 void DActiveButton::Serialize (FArchive &arc)
361 {
362 	Super::Serialize (arc);
363 	arc << m_Side << m_Part << m_SwitchDef << m_Frame << m_Timer << bFlippable << m_X << m_Y << bReturning;
364 }
365 
366 //==========================================================================
367 //
368 //
369 //
370 //==========================================================================
371 
Tick()372 void DActiveButton::Tick ()
373 {
374 	if (m_SwitchDef == NULL)
375 	{
376 		// We lost our definition due to a bad savegame.
377 		Destroy();
378 		return;
379 	}
380 
381 	FSwitchDef *def = bReturning? m_SwitchDef->PairDef : m_SwitchDef;
382 	if (--m_Timer == 0)
383 	{
384 		if (m_Frame == def->NumFrames - 1)
385 		{
386 			bReturning = true;
387 			def = m_SwitchDef->PairDef;
388 			if (def != NULL)
389 			{
390 				m_Frame = -1;
391 				S_Sound (m_X, m_Y, 0, CHAN_VOICE|CHAN_LISTENERZ,
392 					def->Sound != 0 ? FSoundID(def->Sound) : FSoundID("switches/normbutn"),
393 					1, ATTN_STATIC);
394 				bFlippable = false;
395 			}
396 			else
397 			{
398 				Destroy ();
399 				return;
400 			}
401 		}
402 		bool killme = AdvanceFrame ();
403 
404 		m_Side->SetTexture(m_Part, def->frames[m_Frame].Texture);
405 
406 		if (killme)
407 		{
408 			Destroy ();
409 		}
410 	}
411 }
412 
413 //==========================================================================
414 //
415 //
416 //
417 //==========================================================================
418 
AdvanceFrame()419 bool DActiveButton::AdvanceFrame ()
420 {
421 	bool ret = false;
422 	FSwitchDef *def = bReturning? m_SwitchDef->PairDef : m_SwitchDef;
423 
424 	if (++m_Frame == def->NumFrames - 1)
425 	{
426 		if (bFlippable == true)
427 		{
428 			m_Timer = BUTTONTIME;
429 		}
430 		else
431 		{
432 			ret = true;
433 		}
434 	}
435 	else
436 	{
437 		m_Timer = def->frames[m_Frame].TimeMin;
438 		if (def->frames[m_Frame].TimeRnd != 0)
439 		{
440 			m_Timer += pr_switchanim(def->frames[m_Frame].TimeRnd);
441 		}
442 	}
443 	return ret;
444 }
445 
446