//----------------------------------------------------------------------------- // // 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 #ifdef _MSC_VER #include // 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 "fragglescript/t_fs.h" #include "a_specialspot.h" #include "maploader/maploader.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 &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 actorhitlist; int cnt = TexMan.NumTextures(); TArray hitlist(cnt, true); memset(hitlist.Data(), 0, cnt); AActor *actor; TThinkerIterator iterator; 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(§orPortals[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(§orPortals[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() { total_monsters = total_items = total_secrets = killed_monsters = found_items = found_secrets = wminfo.maxfrags = 0; // 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(); SpotState = 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(); } //========================================================================== // // // //========================================================================== void P_FreeLevelData () { // [ZZ] delete per-map event handlers E_Shutdown(true); R_FreePastViewers(); P_ClearUDMFKeys(); // [RH] Clear all ThingID hash chains. AActor::ClearTIDHashes(); interpolator.ClearInterpolations(); // [RH] Nothing to interpolate on a fresh level. FPolyObj::ClearAllSubsectorLinks(); // can't be done as part of the polyobj deletion process. SN_StopAllSequences (); DThinker::DestroyAllThinkers (); tagManager.Clear(); FBehavior::StaticUnloadModules (); P_FreeStrifeConversations (); level.ClearLevelData(); } //=========================================================================== // // P_SetupLevel // // [RH] position indicates the start spot to spawn at // //=========================================================================== void P_SetupLevel(const char *lumpname, 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(lumpname, true); if (map == nullptr) { I_Error("Unable to open map '%s'\n", lumpname); } // [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, lumpname, 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; G_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 = G_PickPlayerStart(i); P_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; G_DeathMatchSpawnPlayer(i); oldSpawn->Destroy(); } } } } // Don't count monsters in end-of-level sectors if option is on if (dmflags2 & DF2_NOCOUNTENDMONST) { TThinkerIterator it; 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(); } 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. } } } } P_ResetSightCounters(true); // 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])); } // // 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 §or : level.sectors) { Printf(PRINT_LOG, "Sector %d\n", sector.sectornum); for (int j = 0; jIndex()), sub->sector->sectornum, sub->hacked & 1 ? "hacked" : ""); for (uint32_t k = 0; knumlines; 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 (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; } } } }