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