engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / psim / p_switch.cpp
blob36377c1e5da6c9209d3da4a16f6a10bd21eaf896
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
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.
16 //**
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.
21 //**
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/>.
24 //**
25 //**************************************************************************
26 //**
27 //** Switches, buttons. Two-state animation. Exits.
28 //**
29 //**************************************************************************
30 #include "../gamedefs.h"
31 #include "../sound/sound.h"
32 #include "p_levelinfo.h"
33 #include "p_entity.h"
34 #include "../utils/ntvalueioex.h"
37 #define BUTTONTIME (1.0f) /* 1 second */
40 enum EBWhere {
41 SWITCH_Top,
42 SWITCH_Middle,
43 SWITCH_Bottom
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)
76 vint32 Side;
77 vuint8 Where;
78 vint32 SwitchDef;
79 vint32 Frame;
80 float Timer;
81 VName DefaultSound;
82 vuint8 UseAgain; // boolean
83 vint32 tbversion; // v1 stores more data
84 VTextureID SwitchDefTexture;
85 TVec ActivateSoundOrg;
86 enum {
87 Flag_SoundOrgSet = 1u<<0,
89 vuint32 Flags;
91 virtual void SerialiseOther (VStream &) override;
92 virtual void Tick (float) override;
93 bool AdvanceFrame ();
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;
112 return false;
114 auto pp = switchTextures.get(texid);
115 return !!pp;
119 //==========================================================================
121 // CalcSoundOrigin
123 //==========================================================================
124 static bool CalcSoundOrigin (TVec *outsndorg, const line_t *line, VEntity *activator, const sector_t *sec) {
125 if (!line) {
126 if (outsndorg) *outsndorg = TVec(INFINITY, INFINITY, INFINITY);
127 return false;
129 // for polyobjects, use polyobject anchor
130 //FIXME: start polyobject sequence instead?
131 const polyobj_t *po = line->pobj();
132 if (po) {
133 if (outsndorg) *outsndorg = po->startSpot;
134 return true;
136 // use center of the line, and activator/sector z
137 TVec sndorg = ((*line->v1)+(*line->v2))*0.5f; // center of the line
138 if (activator) {
139 // use activator z position
140 sndorg.z = activator->Origin.z+max2(0.0f, activator->Height*0.5f);
141 } else if (sec) {
142 // use sector floor
143 sndorg.z = (sec->floor.minz+sec->floor.maxz)*0.5f+8.0f;
144 } else {
145 sndorg.z = 0.0f; // just in case
147 if (outsndorg) *outsndorg = sndorg;
148 return true;
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) {
161 Quest = false;
162 if (sidenum < 0 || sidenum >= XLevel->NumSides) return false;
163 InitSwitchTextureCache();
165 TSwitch **swpp = nullptr;
166 EBWhere where = SWITCH_Top;
167 do {
168 // top?
169 swpp = switchTextures.get(XLevel->Sides[sidenum].TopTexture);
170 if (swpp) {
171 where = SWITCH_Top;
172 XLevel->Sides[sidenum].TopTexture = (*swpp)->Frames[0].Texture;
173 break;
175 // middle?
176 swpp = switchTextures.get(XLevel->Sides[sidenum].MidTexture);
177 if (swpp) {
178 where = SWITCH_Middle;
179 XLevel->Sides[sidenum].MidTexture = (*swpp)->Frames[0].Texture;
180 break;
182 // bottom?
183 swpp = switchTextures.get(XLevel->Sides[sidenum].BottomTexture);
184 if (swpp) {
185 where = SWITCH_Bottom;
186 XLevel->Sides[sidenum].BottomTexture = (*swpp)->Frames[0].Texture;
187 break;
189 } while (0);
191 if (!swpp) return false;
193 TSwitch *sw = *swpp;
194 vassert(sw);
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;
205 if (PlaySound) {
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));
209 if (org) {
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);
213 } else {
214 SectorStartSound(sec, sndid, 0/*channel*/, 1.0f/*volume*/, 1.0f/*attenuation*/);
218 Quest = sw->Quest;
219 return true;
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
239 Btn->Timer = 0.001f;
240 if (Btn->Flags&VThinkButton::Flag_SoundOrgSet) outsndorg = Btn->ActivateSoundOrg;
241 return false;
245 VThinkButton *But = (VThinkButton *)XLevel->SpawnThinker(VThinkButton::StaticClass());
246 if (!But) return false;
247 if (But->IsA(VThinkButton::StaticClass())) {
248 But->Side = sidenum;
249 But->Where = w;
250 But->SwitchDef = SwitchDef;
251 But->Frame = -1;
252 But->DefaultSound = DefaultSound;
253 But->UseAgain = (UseAgain ? 1 : 0);
254 But->tbversion = 1;
255 But->SwitchDefTexture = Switches[SwitchDef]->Tex;
256 But->AdvanceFrame();
257 TVec sndorg;
258 if (CalcSoundOrigin(&sndorg, line, activator, XLevel->Sides[sidenum].Sector)) {
259 But->Flags = VThinkButton::Flag_SoundOrgSet;
260 #if 0
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);
265 #endif
266 But->ActivateSoundOrg = sndorg;
267 outsndorg = sndorg;
268 } else {
269 But->Flags = 0u;
271 return true;
272 } else {
273 But->DestroyThinker(); // just in case
274 return false;
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()) {
290 bool found = false;
291 for (int idx = Switches.length()-1; idx >= 0; --idx) {
292 TSwitch *sw = Switches[idx];
293 if (sw->Tex == SwitchDefTexture) {
294 found = true;
295 if (idx != SwitchDef) {
296 GCon->Logf(NAME_Warning, "switch index changed from %d to %d (this may break the game!)", SwitchDef, idx);
297 } else {
298 //GCon->Logf("*** switch index %d found!", SwitchDef);
300 SwitchDef = idx;
301 break;
304 if (!found) {
305 GCon->Logf(NAME_Error, "switch index for old index %d not found (this WILL break the game!)", SwitchDef);
306 SwitchDef = -1;
309 } else {
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;
322 // do buttons
323 Timer -= DeltaTime;
324 if (Timer <= 0.0f) {
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];
330 Frame = -1;
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);
336 } else {
337 Level->SectorStartSound(sec, sndid, 0/*channel*/, 1.0f/*volume*/, 1.0f/*attenuation*/);
339 UseAgain = 0;
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;
348 } else {
349 // texture_top
350 XLevel->Sides[Side].TopTexture = Def->Frames[Frame].Texture;
353 if (KillMe) DestroyThinker();
354 } else {
355 UseAgain = 0;
356 DestroyThinker();
362 //==========================================================================
364 // VThinkButton::AdvanceFrame
366 //==========================================================================
367 bool VThinkButton::AdvanceFrame () {
368 ++Frame;
369 bool Ret = false;
370 TSwitch *Def = Switches[SwitchDef];
371 if (Frame == Def->NumFrames-1) {
372 if (UseAgain) {
373 Timer = BUTTONTIME;
374 } else {
375 Ret = true;
377 } else {
378 if (Def->Frames[Frame].RandomRange) {
379 Timer = (Def->Frames[Frame].BaseTime+FRandom()*Def->Frames[Frame].RandomRange)/35.0f;
380 } else {
381 Timer = Def->Frames[Frame].BaseTime/35.0f;
384 return Ret;