1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
27 //** Switches, buttons. Two-state animation. Exits.
29 //**************************************************************************
30 #include "../gamedefs.h"
31 #include "../sound/sound.h"
32 #include "p_levelinfo.h"
34 #include "../utils/ntvalueioex.h"
37 #define BUTTONTIME (1.0f) /* 1 second */
47 static TMapNC
<int, TSwitch
*> switchTextures
;
48 static bool switchTexturesInited
= false;
51 //==========================================================================
53 // InitSwitchTextureCache
55 //==========================================================================
56 void InitSwitchTextureCache () noexcept
{
57 if (!switchTexturesInited
) {
58 switchTexturesInited
= true;
59 //GCon->Logf(NAME_Debug, "initializing switch texture cache...");
60 for (TSwitch
*sw
: Switches
) {
61 if (sw
->Tex
> 0) switchTextures
.put(sw
->Tex
, sw
);
67 //**************************************************************************
69 // change the texture of a wall switch to its opposite
71 //**************************************************************************
72 class VThinkButton
: public VThinker
{
73 DECLARE_CLASS(VThinkButton
, VThinker
, 0)
74 NO_DEFAULT_CONSTRUCTOR(VThinkButton
)
82 vuint8 UseAgain
; // boolean
83 vint32 tbversion
; // v1 stores more data
84 VTextureID SwitchDefTexture
;
85 TVec ActivateSoundOrg
;
87 Flag_SoundOrgSet
= 1u<<0,
91 virtual void SerialiseOther (VStream
&) override
;
92 virtual void Tick (float) override
;
97 IMPLEMENT_CLASS(V
, ThinkButton
)
100 //==========================================================================
102 // VLevelInfo::IsSwitchTexture
104 //==========================================================================
105 bool VLevelInfo::IsSwitchTexture (int texid
) {
106 if (texid
<= 0) return false;
107 InitSwitchTextureCache();
109 for (TSwitch *sw : Switches) {
110 if (sw->Tex > 0 && sw->Tex == texid) return true;
114 auto pp
= switchTextures
.get(texid
);
119 //==========================================================================
123 //==========================================================================
124 static bool CalcSoundOrigin (TVec
*outsndorg
, const line_t
*line
, VEntity
*activator
, const sector_t
*sec
) {
126 if (outsndorg
) *outsndorg
= TVec(INFINITY
, INFINITY
, INFINITY
);
129 // for polyobjects, use polyobject anchor
130 //FIXME: start polyobject sequence instead?
131 const polyobj_t
*po
= line
->pobj();
133 if (outsndorg
) *outsndorg
= po
->startSpot
;
136 // use center of the line, and activator/sector z
137 TVec sndorg
= ((*line
->v1
)+(*line
->v2
))*0.5f
; // center of the line
139 // use activator z position
140 sndorg
.z
= activator
->Origin
.z
+max2(0.0f
, activator
->Height
*0.5f
);
143 sndorg
.z
= (sec
->floor
.minz
+sec
->floor
.maxz
)*0.5f
+8.0f
;
145 sndorg
.z
= 0.0f
; // just in case
147 if (outsndorg
) *outsndorg
= sndorg
;
152 //==========================================================================
154 // VLevelInfo::ChangeSwitchTexture
156 // Function that changes wall texture.
157 // Tell it if switch is ok to use again (1=yes, it's a button).
159 //==========================================================================
160 bool VLevelInfo::ChangeSwitchTexture (line_t
*line
, VEntity
*activator
, int sidenum
, bool useAgain
, VName DefaultSound
, bool &Quest
, const TVec
*org
) {
162 if (sidenum
< 0 || sidenum
>= XLevel
->NumSides
) return false;
163 InitSwitchTextureCache();
165 TSwitch
**swpp
= nullptr;
166 EBWhere where
= SWITCH_Top
;
169 swpp
= switchTextures
.get(XLevel
->Sides
[sidenum
].TopTexture
);
172 XLevel
->Sides
[sidenum
].TopTexture
= (*swpp
)->Frames
[0].Texture
;
176 swpp
= switchTextures
.get(XLevel
->Sides
[sidenum
].MidTexture
);
178 where
= SWITCH_Middle
;
179 XLevel
->Sides
[sidenum
].MidTexture
= (*swpp
)->Frames
[0].Texture
;
183 swpp
= switchTextures
.get(XLevel
->Sides
[sidenum
].BottomTexture
);
185 where
= SWITCH_Bottom
;
186 XLevel
->Sides
[sidenum
].BottomTexture
= (*swpp
)->Frames
[0].Texture
;
191 if (!swpp
) return false;
196 TVec
outsndorg(INFINITY
, INFINITY
, INFINITY
);
197 bool calcSoundOrigin
= true;
199 bool PlaySound
= true;
200 if (useAgain
|| sw
->NumFrames
> 1) {
201 PlaySound
= StartButton(line
, activator
, sidenum
, where
, sw
->InternalIndex
, DefaultSound
, useAgain
, outsndorg
);
202 calcSoundOrigin
= false;
206 if (calcSoundOrigin
) (void)CalcSoundOrigin(&outsndorg
, line
, activator
, XLevel
->Sides
[sidenum
].Sector
);
207 sector_t
*sec
= XLevel
->Sides
[sidenum
].Sector
;
208 const int sndid
= (sw
->Sound
? sw
->Sound
: GSoundManager
->GetSoundID(DefaultSound
));
210 SectorStartSound(sec
, sndid
, 0/*channel*/, 1.0f
/*volume*/, 1.0f
/*attenuation*/, org
);
211 } else if (isFiniteF(outsndorg
.x
)) {
212 SectorStartSound(sec
, sndid
, 0/*channel*/, 1.0f
/*volume*/, 1.0f
/*attenuation*/, &outsndorg
);
214 SectorStartSound(sec
, sndid
, 0/*channel*/, 1.0f
/*volume*/, 1.0f
/*attenuation*/);
223 //==========================================================================
225 // VLevelInfo::StartButton
227 // start a button counting down till it turns off
228 // FIXME: make this faster!
230 //==========================================================================
231 bool VLevelInfo::StartButton (line_t
*line
, VEntity
*activator
, int sidenum
, vuint8 w
, int SwitchDef
, VName DefaultSound
, bool UseAgain
, TVec
&outsndorg
) {
232 outsndorg
.x
= outsndorg
.y
= outsndorg
.z
= INFINITY
;
233 if (sidenum
< 0 || sidenum
>= XLevel
->NumSides
) return false;
235 // see if button is already pressed
236 for (TThinkerIterator
<VThinkButton
> Btn(XLevel
); Btn
; ++Btn
) {
237 if (Btn
->Side
== sidenum
) {
238 // force advancing to the next frame
240 if (Btn
->Flags
&VThinkButton::Flag_SoundOrgSet
) outsndorg
= Btn
->ActivateSoundOrg
;
245 VThinkButton
*But
= (VThinkButton
*)XLevel
->SpawnThinker(VThinkButton::StaticClass());
246 if (!But
) return false;
247 if (But
->IsA(VThinkButton::StaticClass())) {
250 But
->SwitchDef
= SwitchDef
;
252 But
->DefaultSound
= DefaultSound
;
253 But
->UseAgain
= (UseAgain
? 1 : 0);
255 But
->SwitchDefTexture
= Switches
[SwitchDef
]->Tex
;
258 if (CalcSoundOrigin(&sndorg
, line
, activator
, XLevel
->Sides
[sidenum
].Sector
)) {
259 But
->Flags
= VThinkButton::Flag_SoundOrgSet
;
261 GCon
->Logf(NAME_Debug
, "%s: sndorg=(%g,%g,%g); sectorsndorg=(%g,%g,%g)",
262 (activator
? activator
->GetClass()->GetName() : "<none>"),
263 sndorg
.x
, sndorg
.y
, sndorg
.z
,
264 XLevel
->Sides
[sidenum
].Sector
->soundorg
.x
, XLevel
->Sides
[sidenum
].Sector
->soundorg
.y
, XLevel
->Sides
[sidenum
].Sector
->soundorg
.z
);
266 But
->ActivateSoundOrg
= sndorg
;
273 But
->DestroyThinker(); // just in case
279 //==========================================================================
281 // VThinkButton::SerialiseOther
283 //==========================================================================
284 void VThinkButton::SerialiseOther (VStream
&Strm
) {
285 Super::SerialiseOther(Strm
);
286 if (tbversion
== 1) {
287 VNTValueIOEx
vio(&Strm
);
288 vio
.io(VName("switch.texture"), SwitchDefTexture
);
289 if (Strm
.IsLoading()) {
291 for (int idx
= Switches
.length()-1; idx
>= 0; --idx
) {
292 TSwitch
*sw
= Switches
[idx
];
293 if (sw
->Tex
== SwitchDefTexture
) {
295 if (idx
!= SwitchDef
) {
296 GCon
->Logf(NAME_Warning
, "switch index changed from %d to %d (this may break the game!)", SwitchDef
, idx
);
298 //GCon->Logf("*** switch index %d found!", SwitchDef);
305 GCon
->Logf(NAME_Error
, "switch index for old index %d not found (this WILL break the game!)", SwitchDef
);
310 GCon
->Log(NAME_Warning
, "*** old switch data found in save, this may break the game!");
315 //==========================================================================
317 // VThinkButton::Tick
319 //==========================================================================
320 void VThinkButton::Tick (float DeltaTime
) {
321 if (DeltaTime
<= 0.0f
) return;
325 if (SwitchDef
>= 0 && SwitchDef
< Switches
.length()) {
326 TSwitch
*Def
= Switches
[SwitchDef
];
327 if (Frame
== Def
->NumFrames
-1) {
328 SwitchDef
= Def
->PairIndex
;
329 Def
= Switches
[Def
->PairIndex
];
331 sector_t
*sec
= XLevel
->Sides
[Side
].Sector
;
332 const int sndid
= (Def
->Sound
? Def
->Sound
: GSoundManager
->GetSoundID(DefaultSound
));
333 if (Flags
&Flag_SoundOrgSet
) {
334 // make "deactivate" sound at the exact same place as "activate" sound
335 Level
->SectorStartSound(sec
, sndid
, 0/*channel*/, 1.0f
/*volume*/, 1.0f
/*attenuation*/, &ActivateSoundOrg
);
337 Level
->SectorStartSound(sec
, sndid
, 0/*channel*/, 1.0f
/*volume*/, 1.0f
/*attenuation*/);
342 bool KillMe
= AdvanceFrame();
343 if (Side
>= 0 && Side
< XLevel
->NumSides
) {
344 if (Where
== SWITCH_Middle
) {
345 XLevel
->Sides
[Side
].MidTexture
= Def
->Frames
[Frame
].Texture
;
346 } else if (Where
== SWITCH_Bottom
) {
347 XLevel
->Sides
[Side
].BottomTexture
= Def
->Frames
[Frame
].Texture
;
350 XLevel
->Sides
[Side
].TopTexture
= Def
->Frames
[Frame
].Texture
;
353 if (KillMe
) DestroyThinker();
362 //==========================================================================
364 // VThinkButton::AdvanceFrame
366 //==========================================================================
367 bool VThinkButton::AdvanceFrame () {
370 TSwitch
*Def
= Switches
[SwitchDef
];
371 if (Frame
== Def
->NumFrames
-1) {
378 if (Def
->Frames
[Frame
].RandomRange
) {
379 Timer
= (Def
->Frames
[Frame
].BaseTime
+FRandom()*Def
->Frames
[Frame
].RandomRange
)/35.0f
;
381 Timer
= Def
->Frames
[Frame
].BaseTime
/35.0f
;