vkdoom_m/src/p_setup.cpp
Christoph Oelckers 3364988680 - moved the interpolator into FLevelLocals and refactored its use to happen outside the renderers.
There is no need to do this deep inside the renderer where it required code duplication and made it problematic to execute on multiple levels.
This is now being done before and after the top level call into the renderer in d_main.cpp.
This also serializes the interpolator itself to avoid problems with the Serialize functions adding the interpolations into the list which can only work with a single global instance.
2019-01-28 18:26:14 +01:00

691 lines
18 KiB
C++

//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1994-1996 Raven Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2018 Christoph Oelckers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
//
//-----------------------------------------------------------------------------
#include <math.h>
#ifdef _MSC_VER
#include <malloc.h> // for alloca()
#endif
#include "templates.h"
#include "d_player.h"
#include "m_argv.h"
#include "g_game.h"
#include "w_wad.h"
#include "p_local.h"
#include "p_effect.h"
#include "p_terrain.h"
#include "nodebuild.h"
#include "p_lnspec.h"
#include "c_console.h"
#include "p_acs.h"
#include "announcer.h"
#include "wi_stuff.h"
#include "doomerrors.h"
#include "gi.h"
#include "p_conversation.h"
#include "a_keys.h"
#include "s_sndseq.h"
#include "sbar.h"
#include "p_setup.h"
#include "r_data/r_interpolate.h"
#include "r_sky.h"
#include "cmdlib.h"
#include "md5.h"
#include "po_man.h"
#include "r_renderer.h"
#include "p_blockmap.h"
#include "r_utility.h"
#include "p_spec.h"
#include "g_levellocals.h"
#include "c_dispatch.h"
#include "a_dynlight.h"
#include "events.h"
#include "p_destructible.h"
#include "types.h"
#include "i_time.h"
#include "scripting/vm/vm.h"
#include "a_specialspot.h"
#include "maploader/maploader.h"
#include "p_acs.h"
#include "am_map.h"
#include "fragglescript/t_script.h"
void P_ClearUDMFKeys();
extern AActor *SpawnMapThing (int index, FMapThing *mthing, int position);
extern unsigned int R_OldBlend;
static void P_Shutdown ();
//===========================================================================
//
// P_PrecacheLevel
//
// Preloads all relevant graphics for the Level->
//
//===========================================================================
void hw_PrecacheTexture(uint8_t *texhitlist, TMap<PClassActor*, bool> &actorhitlist);
static void AddToList(uint8_t *hitlist, FTextureID texid, int bitmask)
{
if (hitlist[texid.GetIndex()] & bitmask) return; // already done, no need to process everything again.
hitlist[texid.GetIndex()] |= (uint8_t)bitmask;
for (auto anim : TexMan.mAnimations)
{
if (texid == anim->BasePic || (!anim->bDiscrete && anim->BasePic < texid && texid < anim->BasePic + anim->NumFrames))
{
for (int i = anim->BasePic.GetIndex(); i < anim->BasePic.GetIndex() + anim->NumFrames; i++)
{
hitlist[i] |= (uint8_t)bitmask;
}
}
}
auto switchdef = TexMan.FindSwitch(texid);
if (switchdef)
{
for (int i = 0; i < switchdef->NumFrames; i++)
{
hitlist[switchdef->frames[i].Texture.GetIndex()] |= (uint8_t)bitmask;
}
for (int i = 0; i < switchdef->PairDef->NumFrames; i++)
{
hitlist[switchdef->PairDef->frames[i].Texture.GetIndex()] |= (uint8_t)bitmask;
}
}
auto adoor = TexMan.FindAnimatedDoor(texid);
if (adoor)
{
for (int i = 0; i < adoor->NumTextureFrames; i++)
{
hitlist[adoor->TextureFrames[i].GetIndex()] |= (uint8_t)bitmask;
}
}
}
static void PrecacheLevel(FLevelLocals *Level)
{
if (demoplayback)
return;
int i;
TMap<PClassActor *, bool> actorhitlist;
int cnt = TexMan.NumTextures();
TArray<uint8_t> hitlist(cnt, true);
memset(hitlist.Data(), 0, cnt);
AActor *actor;
auto iterator = Level->GetThinkerIterator<AActor>();
while ((actor = iterator.Next()))
{
actorhitlist[actor->GetClass()] = true;
}
for (auto n : gameinfo.PrecachedClasses)
{
PClassActor *cls = PClass::FindActor(n);
if (cls != nullptr) actorhitlist[cls] = true;
}
for (unsigned i = 0; i < Level->info->PrecacheClasses.Size(); i++)
{
// Level->info can only store names, no class pointers.
PClassActor *cls = PClass::FindActor(Level->info->PrecacheClasses[i]);
if (cls != nullptr) actorhitlist[cls] = true;
}
for (i = Level->sectors.Size() - 1; i >= 0; i--)
{
AddToList(hitlist.Data(), Level->sectors[i].GetTexture(sector_t::floor), FTextureManager::HIT_Flat);
AddToList(hitlist.Data(), Level->sectors[i].GetTexture(sector_t::ceiling), FTextureManager::HIT_Flat);
}
for (i = Level->sides.Size() - 1; i >= 0; i--)
{
AddToList(hitlist.Data(), Level->sides[i].GetTexture(side_t::top), FTextureManager::HIT_Wall);
AddToList(hitlist.Data(), Level->sides[i].GetTexture(side_t::mid), FTextureManager::HIT_Wall);
AddToList(hitlist.Data(), Level->sides[i].GetTexture(side_t::bottom), FTextureManager::HIT_Wall);
}
// Sky texture is always present.
// Note that F_SKY1 is the name used to
// indicate a sky floor/ceiling as a flat,
// while the sky texture is stored like
// a wall texture, with an episode dependant
// name.
if (sky1texture.isValid())
{
AddToList(hitlist.Data(), sky1texture, FTextureManager::HIT_Sky);
}
if (sky2texture.isValid())
{
AddToList(hitlist.Data(), sky2texture, FTextureManager::HIT_Sky);
}
for (auto n : gameinfo.PrecachedTextures)
{
FTextureID tex = TexMan.CheckForTexture(n, ETextureType::Wall, FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ReturnFirst);
if (tex.Exists()) AddToList(hitlist.Data(), tex, FTextureManager::HIT_Wall);
}
for (unsigned i = 0; i < Level->info->PrecacheTextures.Size(); i++)
{
FTextureID tex = TexMan.CheckForTexture(Level->info->PrecacheTextures[i], ETextureType::Wall, FTextureManager::TEXMAN_Overridable | FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ReturnFirst);
if (tex.Exists()) AddToList(hitlist.Data(), tex, FTextureManager::HIT_Wall);
}
// This is just a temporary solution, until the hardware renderer's texture manager is in a better state.
if (!V_IsHardwareRenderer())
SWRenderer->Precache(hitlist.Data(), actorhitlist);
else
hw_PrecacheTexture(hitlist.Data(), actorhitlist);
}
//============================================================================
//
// clears all portal data for a new level start
//
//============================================================================
void FLevelLocals::ClearPortals()
{
Displacements.Create(1);
linePortals.Clear();
linkedPortals.Clear();
sectorPortals.Resize(2);
PortalBlockmap.Clear();
// The first entry must always be the default skybox. This is what every sector gets by default.
memset(&sectorPortals[0], 0, sizeof(sectorPortals[0]));
sectorPortals[0].mType = PORTS_SKYVIEWPOINT;
sectorPortals[0].mFlags = PORTSF_SKYFLATONLY;
// The second entry will be the default sky. This is for forcing a regular sky through the skybox picker
memset(&sectorPortals[1], 0, sizeof(sectorPortals[0]));
sectorPortals[1].mType = PORTS_SKYVIEWPOINT;
sectorPortals[1].mFlags = PORTSF_SKYFLATONLY;
// also clear the render data
for (auto &sub : subsectors)
{
for (int j = 0; j < 2; j++)
{
if (sub.portalcoverage[j].subsectors != nullptr)
{
delete[] sub.portalcoverage[j].subsectors;
sub.portalcoverage[j].subsectors = nullptr;
}
}
}
for (unsigned i = 0; i < portalGroups.Size(); i++)
{
delete portalGroups[i];
}
portalGroups.Clear();
linePortalSpans.Clear();
}
//==========================================================================
//
//
//
//==========================================================================
void FLevelLocals::ClearLevelData()
{
ClearAllSubsectorLinks(); // can't be done as part of the polyobj deletion process.
total_monsters = total_items = total_secrets =
killed_monsters = found_items = found_secrets =
wminfo.maxfrags = 0;
FStrifeDialogueNode *node;
while (StrifeDialogues.Pop (node))
{
delete node;
}
DialogueRoots.Clear();
ClassRoots.Clear();
// delete allocated data in the level arrays.
if (sectors.Size() > 0)
{
delete[] sectors[0].e;
}
for (auto &sub : subsectors)
{
if (sub.BSP != nullptr) delete sub.BSP;
}
ClearPortals();
interpolator.ClearInterpolations(); // [RH] Nothing to interpolate on a fresh level.
tagManager.Clear();
ClearTIDHashes();
Behaviors.UnloadModules();
SpotState = nullptr;
ACSThinker = nullptr;
FraggleScriptThinker = nullptr;
CorpseQueue.Clear();
canvasTextureInfo.EmptyList();
sections.Clear();
segs.Clear();
sectors.Clear();
linebuffer.Clear();
subsectorbuffer.Clear();
lines.Clear();
sides.Clear();
segbuffer.Clear();
loadsectors.Clear();
loadlines.Clear();
loadsides.Clear();
vertexes.Clear();
nodes.Clear();
gamenodes.Reset();
subsectors.Clear();
gamesubsectors.Reset();
rejectmatrix.Clear();
Zones.Clear();
blockmap.Clear();
Polyobjects.Clear();
for (auto &pb : PolyBlockMap)
{
polyblock_t *link = pb;
while (link != nullptr)
{
polyblock_t *next = link->next;
delete link;
link = next;
}
}
PolyBlockMap.Reset();
deathmatchstarts.Clear();
AllPlayerStarts.Clear();
memset(playerstarts, 0, sizeof(playerstarts));
Scrolls.Clear();
if (automap) automap->Destroy();
}
//==========================================================================
//
//
//
//==========================================================================
void P_FreeLevelData ()
{
// [ZZ] delete per-map event handlers
E_Shutdown(true);
R_FreePastViewers();
P_ClearUDMFKeys();
SN_StopAllSequences ();
DThinker::DestroyAllThinkers ();
level.ClearLevelData();
}
//===========================================================================
//
// P_SetupLevel
//
// [RH] position indicates the start spot to spawn at
//
//===========================================================================
void P_SetupLevel(FLevelLocals *Level, int position, bool newGame)
{
int i;
Level->ShaderStartTime = I_msTimeFS(); // indicate to the shader system that the level just started
// This is motivated as follows:
Level->maptype = MAPTYPE_UNKNOWN;
wminfo.partime = 180;
if (!savegamerestore)
{
Level->SetMusicVolume(Level->MusicVolume);
for (i = 0; i < MAXPLAYERS; ++i)
{
players[i].killcount = players[i].secretcount
= players[i].itemcount = 0;
}
}
for (i = 0; i < MAXPLAYERS; ++i)
{
players[i].mo = nullptr;
}
// [RH] Clear any scripted translation colors the previous level may have set.
for (i = 0; i < int(translationtables[TRANSLATION_LevelScripted].Size()); ++i)
{
FRemapTable *table = translationtables[TRANSLATION_LevelScripted][i];
if (table != nullptr)
{
delete table;
translationtables[TRANSLATION_LevelScripted][i] = nullptr;
}
}
translationtables[TRANSLATION_LevelScripted].Clear();
// Initial height of PointOfView will be set by player think.
players[consoleplayer].viewz = NO_VALUE;
// Make sure all sounds are stopped before Z_FreeTags.
S_Start();
// [RH] clear out the mid-screen message
C_MidPrint(nullptr, nullptr);
// Free all level data from the previous map
P_FreeLevelData();
MapData *map = P_OpenMapData(Level->MapName, true);
if (map == nullptr)
{
I_Error("Unable to open map '%s'\n", Level->MapName.GetChars());
}
// [ZZ] init per-map static handlers. we need to call this before everything is set up because otherwise scripts don't receive PlayerEntered event
// (which happens at god-knows-what stage in this function, but definitely not the last part, because otherwise it'd work to put E_InitStaticHandlers before the player spawning)
E_InitStaticHandlers(true);
// generate a checksum for the level, to be included and checked with savegames.
map->GetChecksum(Level->md5);
// find map num
Level->lumpnum = map->lumpnum;
if (newGame)
{
E_NewGame(EventHandlerType::PerMap);
}
MapLoader loader(Level);
loader.LoadLevel(map, Level->MapName.GetChars(), position);
delete map;
// if deathmatch, randomly spawn the active players
if (deathmatch)
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
players[i].mo = nullptr;
Level->DeathMatchSpawnPlayer(i);
}
}
}
// the same, but for random single/coop player starts
else if (Level->flags2 & LEVEL2_RANDOMPLAYERSTARTS)
{
for (i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i])
{
players[i].mo = nullptr;
FPlayerStart *mthing = Level->PickPlayerStart(i);
Level->SpawnPlayer(mthing, i, (Level->flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0);
}
}
}
// [SP] move unfriendly players around
// horribly hacky - yes, this needs rewritten.
if (Level->deathmatchstarts.Size() > 0)
{
for (i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i] && players[i].mo != nullptr)
{
if (!(players[i].mo->flags & MF_FRIENDLY))
{
AActor * oldSpawn = players[i].mo;
Level->DeathMatchSpawnPlayer(i);
oldSpawn->Destroy();
}
}
}
}
// Don't count monsters in end-of-level sectors if option is on
if (dmflags2 & DF2_NOCOUNTENDMONST)
{
auto it = Level->GetThinkerIterator<AActor>();
AActor * mo;
while ((mo = it.Next()))
{
if (mo->flags & MF_COUNTKILL)
{
if (mo->Sector->damageamount > 0 && (mo->Sector->Flags & (SECF_ENDGODMODE | SECF_ENDLEVEL)) == (SECF_ENDGODMODE | SECF_ENDLEVEL))
{
mo->ClearCounters();
}
}
}
}
T_PreprocessScripts(Level); // preprocess FraggleScript scripts
// build subsector connect matrix
// UNUSED P_ConnectSubsectors ();
R_OldBlend = 0xffffffff;
// [RH] Remove all particles
P_ClearParticles();
// preload graphics and sounds
if (precache)
{
PrecacheLevel(Level);
S_PrecacheLevel(Level);
}
if (deathmatch)
{
AnnounceGameStart();
}
// This check was previously done at run time each time the heightsec was checked.
// However, since 3D floors are static data, we can easily precalculate this and store it in the sector's flags for quick access.
for (auto &s : Level->sectors)
{
if (s.heightsec != nullptr)
{
// If any of these 3D floors render their planes, ignore heightsec.
for (auto &ff : s.e->XFloor.ffloors)
{
if ((ff->flags & (FF_EXISTS | FF_RENDERPLANES)) == (FF_EXISTS | FF_RENDERPLANES))
{
s.MoreFlags |= SECMF_IGNOREHEIGHTSEC; // mark the heightsec inactive.
}
}
}
}
// Create a backup of the map data so the savegame code can toss out all fields that haven't changed in order to reduce processing time and file size.
// Note that we want binary identity here, so assignment is not sufficient because it won't initialize any padding bytes.
// Note that none of these structures may contain non POD fields anyway.
Level->loadsectors.Resize(Level->sectors.Size());
memcpy(&Level->loadsectors[0], &Level->sectors[0], Level->sectors.Size() * sizeof(Level->sectors[0]));
Level->loadlines.Resize(Level->lines.Size());
memcpy(&Level->loadlines[0], &Level->lines[0], Level->lines.Size() * sizeof(Level->lines[0]));
Level->loadsides.Resize(Level->sides.Size());
memcpy(&Level->loadsides[0], &Level->sides[0], Level->sides.Size() * sizeof(Level->sides[0]));
Level->automap = AM_Create(Level);
Level->automap->LevelInit();
// [RH] Start lightning, if MAPINFO tells us to
if (Level->flags & LEVEL_STARTLIGHTNING)
{
Level->StartLightning();
}
}
//
// P_Init
//
void P_Init ()
{
atterm (P_Shutdown);
P_InitEffects (); // [RH]
P_InitTerrainTypes ();
P_InitKeyMessages ();
R_InitSprites ();
}
static void P_Shutdown ()
{
DThinker::DestroyThinkersInList(STAT_STATIC);
P_FreeLevelData ();
// [ZZ] delete global event handlers
E_Shutdown(false);
ST_Clear();
for (auto &p : players)
{
if (p.psprites != nullptr) p.psprites->Destroy();
}
}
//==========================================================================
//
// dumpgeometry
//
//==========================================================================
CCMD(dumpgeometry)
{
for (auto Level : AllLevels())
{
Printf("Geometry for %s\n", Level->MapName.GetChars());
for (auto &sector : Level->sectors)
{
Printf(PRINT_LOG, "Sector %d\n", sector.sectornum);
for (int j = 0; j<sector.subsectorcount; j++)
{
subsector_t * sub = sector.subsectors[j];
Printf(PRINT_LOG, " Subsector %d - real sector = %d - %s\n", int(sub->Index()), sub->sector->sectornum, sub->hacked & 1 ? "hacked" : "");
for (uint32_t k = 0; k<sub->numlines; k++)
{
seg_t * seg = sub->firstline + k;
if (seg->linedef)
{
Printf(PRINT_LOG, " (%4.4f, %4.4f), (%4.4f, %4.4f) - seg %d, linedef %d, side %d",
seg->v1->fX(), seg->v1->fY(), seg->v2->fX(), seg->v2->fY(),
seg->Index(), seg->linedef->Index(), seg->sidedef != seg->linedef->sidedef[0]);
}
else
{
Printf(PRINT_LOG, " (%4.4f, %4.4f), (%4.4f, %4.4f) - seg %d, miniseg",
seg->v1->fX(), seg->v1->fY(), seg->v2->fX(), seg->v2->fY(), seg->Index());
}
if (seg->PartnerSeg)
{
subsector_t * sub2 = seg->PartnerSeg->Subsector;
Printf(PRINT_LOG, ", back sector = %d, real back sector = %d", sub2->render_sector->sectornum, seg->PartnerSeg->frontsector->sectornum);
}
else if (seg->backsector)
{
Printf(PRINT_LOG, ", back sector = %d (no partnerseg)", seg->backsector->sectornum);
}
Printf(PRINT_LOG, "\n");
}
}
}
}
}
//==========================================================================
//
//
//
//==========================================================================
CCMD(listmapsections)
{
for (auto Level : AllLevels())
{
Printf("Map sections for %s:\n", Level->MapName.GetChars());
for (int i = 0; i < 100; i++)
{
for (auto &sub : Level->subsectors)
{
if (sub.mapsection == i)
{
Printf("Mapsection %d, sector %d, line %d\n", i, sub.render_sector->Index(), sub.firstline->linedef->Index());
break;
}
}
}
}
}
//==========================================================================
//
//
//
//==========================================================================
CUSTOM_CVAR(Bool, forcewater, false, CVAR_ARCHIVE | CVAR_SERVERINFO)
{
if (gamestate == GS_LEVEL) for (auto Level : AllLevels())
{
for (auto &sec : Level->sectors)
{
sector_t *hsec = sec.GetHeightSec();
if (hsec && !(hsec->MoreFlags & SECMF_UNDERWATER))
{
if (self)
{
hsec->MoreFlags |= SECMF_FORCEDUNDERWATER;
}
else
{
hsec->MoreFlags &= ~SECMF_FORCEDUNDERWATER;
}
}
}
}
}