mirror of
https://github.com/ZDoom/gzdoom.git
synced 2025-03-10 19:31:57 +00:00
The side effects here broke other maps and this is really too glitchy to be turned on unless really necesasary.
3259 lines
85 KiB
C++
3259 lines
85 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// Copyright 1993-1996 id Software
|
|
// Copyright 1994-1996 Raven Software
|
|
// Copyright 1999-2016 Randy Heit
|
|
// Copyright 2002-2018 Christoph Oelckers
|
|
// Copyright 2010 James Haley
|
|
//
|
|
// 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:
|
|
// Do all the WAD I/O, get map description,
|
|
// set up initial state and misc. LUTs.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/* For code that originates from ZDoom the following applies:
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
|
|
#include <math.h>
|
|
#include "maploader.h"
|
|
#include "c_cvars.h"
|
|
#include "actor.h"
|
|
#include "g_levellocals.h"
|
|
#include "p_lnspec.h"
|
|
|
|
#include "v_text.h"
|
|
#include "p_setup.h"
|
|
#include "gi.h"
|
|
#include "doomerrors.h"
|
|
#include "types.h"
|
|
#include "w_wad.h"
|
|
#include "p_conversation.h"
|
|
#include "v_video.h"
|
|
#include "i_time.h"
|
|
#include "m_argv.h"
|
|
#include "fragglescript/t_fs.h"
|
|
#include "swrenderer/r_swrenderer.h"
|
|
#include "hwrenderer/data/flatvertices.h"
|
|
#include "xlat/xlat.h"
|
|
|
|
enum
|
|
{
|
|
MISSING_TEXTURE_WARN_LIMIT = 20
|
|
};
|
|
|
|
CVAR (Bool, genblockmap, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG);
|
|
CVAR (Bool, gennodes, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG);
|
|
|
|
inline bool P_LoadBuildMap(uint8_t *mapdata, size_t len, FMapThing **things, int *numthings)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Now that ZDoom again gives the option of using Doom's original teleport
|
|
// behavior, only teleport dests in a sector with a 0 tag need to be
|
|
// given a TID. And since Doom format maps don't have TIDs, we can safely
|
|
// give them TID 1.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::TranslateTeleportThings ()
|
|
{
|
|
AActor *dest;
|
|
auto iterator = Level->GetThinkerIterator<AActor>(NAME_TeleportDest);
|
|
bool foundSomething = false;
|
|
|
|
while ( (dest = iterator.Next()) )
|
|
{
|
|
if (!Level->SectorHasTags(dest->Sector))
|
|
{
|
|
dest->tid = 1;
|
|
dest->AddToHash ();
|
|
foundSomething = true;
|
|
}
|
|
}
|
|
|
|
if (foundSomething)
|
|
{
|
|
for (auto &line : Level->lines)
|
|
{
|
|
if (line.special == Teleport)
|
|
{
|
|
if (line.args[1] == 0)
|
|
{
|
|
line.args[0] = 1;
|
|
}
|
|
}
|
|
else if (line.special == Teleport_NoFog)
|
|
{
|
|
if (line.args[2] == 0)
|
|
{
|
|
line.args[0] = 1;
|
|
}
|
|
}
|
|
else if (line.special == Teleport_ZombieChanger)
|
|
{
|
|
if (line.args[1] == 0)
|
|
{
|
|
line.args[0] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// Sets a sidedef's texture and prints a message if it's not present.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SetTexture (side_t *side, int position, const char *name, FMissingTextureTracker &track)
|
|
{
|
|
static const char *positionnames[] = { "top", "middle", "bottom" };
|
|
static const char *sidenames[] = { "first", "second" };
|
|
|
|
FTextureID texture = TexMan.CheckForTexture (name, ETextureType::Wall,
|
|
FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny);
|
|
|
|
if (!texture.Exists())
|
|
{
|
|
if (++track[name].Count <= MISSING_TEXTURE_WARN_LIMIT)
|
|
{
|
|
// Print an error that lists all references to this sidedef.
|
|
// We must scan the linedefs manually for all references to this sidedef.
|
|
for(unsigned i = 0; i < Level->lines.Size(); i++)
|
|
{
|
|
for(int j = 0; j < 2; j++)
|
|
{
|
|
if (Level->lines[i].sidedef[j] == side)
|
|
{
|
|
Printf(TEXTCOLOR_RED "Unknown %s texture '"
|
|
TEXTCOLOR_ORANGE "%s" TEXTCOLOR_RED
|
|
"' on %s side of linedef %d\n",
|
|
positionnames[position], name, sidenames[j], i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
texture = TexMan.GetDefaultTexture();
|
|
}
|
|
side->SetTexture(position, texture);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Sets a sidedef's texture and prints a message if it's not present.
|
|
// (Passing index separately is for UDMF which does not have sectors allocated yet)
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SetTexture (sector_t *sector, int index, int position, const char *name, FMissingTextureTracker &track, bool truncate)
|
|
{
|
|
static const char *positionnames[] = { "floor", "ceiling" };
|
|
char name8[9];
|
|
if (truncate)
|
|
{
|
|
strncpy(name8, name, 8);
|
|
name8[8] = 0;
|
|
name = name8;
|
|
}
|
|
|
|
FTextureID texture = TexMan.CheckForTexture (name, ETextureType::Flat,
|
|
FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny);
|
|
|
|
if (!texture.Exists())
|
|
{
|
|
if (++track[name].Count <= MISSING_TEXTURE_WARN_LIMIT)
|
|
{
|
|
Printf(TEXTCOLOR_RED"Unknown %s texture '"
|
|
TEXTCOLOR_ORANGE "%s" TEXTCOLOR_RED
|
|
"' in sector %d\n",
|
|
positionnames[position], name, index);
|
|
}
|
|
texture = TexMan.GetDefaultTexture();
|
|
}
|
|
sector->SetTexture(position, texture);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// SummarizeMissingTextures
|
|
//
|
|
// Lists textures that were missing more than MISSING_TEXTURE_WARN_LIMIT
|
|
// times.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SummarizeMissingTextures(const FMissingTextureTracker &missing)
|
|
{
|
|
FMissingTextureTracker::ConstIterator it(missing);
|
|
FMissingTextureTracker::ConstPair *pair;
|
|
|
|
while (it.NextPair(pair))
|
|
{
|
|
if (pair->Value.Count > MISSING_TEXTURE_WARN_LIMIT)
|
|
{
|
|
Printf(TEXTCOLOR_RED "Missing texture '"
|
|
TEXTCOLOR_ORANGE "%s" TEXTCOLOR_RED
|
|
"' is used %d more times\n",
|
|
pair->Key.GetChars(), pair->Value.Count - MISSING_TEXTURE_WARN_LIMIT);
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// [RH] Figure out blends for deep water sectors
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SetTexture (side_t *side, int position, uint32_t *blend, const char *name)
|
|
{
|
|
FTextureID texture;
|
|
if ((*blend = R_ColormapNumForName (name)) == 0)
|
|
{
|
|
texture = TexMan.CheckForTexture (name, ETextureType::Wall,
|
|
FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny);
|
|
if (!texture.Exists())
|
|
{
|
|
char name2[9];
|
|
char *stop;
|
|
strncpy (name2, name, 8);
|
|
name2[8] = 0;
|
|
*blend = strtoul (name2, &stop, 16);
|
|
texture = FNullTextureID();
|
|
}
|
|
else
|
|
{
|
|
*blend = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
texture = FNullTextureID();
|
|
}
|
|
side->SetTexture(position, texture);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SetTextureNoErr (side_t *side, int position, uint32_t *color, const char *name, bool *validcolor, bool isFog)
|
|
{
|
|
FTextureID texture;
|
|
*validcolor = false;
|
|
texture = TexMan.CheckForTexture (name, ETextureType::Wall,
|
|
FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny);
|
|
if (!texture.Exists())
|
|
{
|
|
char name2[9];
|
|
char *stop;
|
|
strncpy (name2, name+1, 7);
|
|
name2[7] = 0;
|
|
if (*name != '#')
|
|
{
|
|
*color = strtoul (name, &stop, 16);
|
|
texture = FNullTextureID();
|
|
*validcolor = (*stop == 0) && (stop >= name + 2) && (stop <= name + 6);
|
|
return;
|
|
}
|
|
else // Support for Legacy's color format!
|
|
{
|
|
int l=(int)strlen(name);
|
|
texture = FNullTextureID();
|
|
*validcolor = false;
|
|
if (l>=7)
|
|
{
|
|
for(stop=name2;stop<name2+6;stop++) if (!isxdigit(*stop)) *stop='0';
|
|
|
|
int factor = l==7? 0 : clamp<int> ((name2[6]&223)-'A', 0, 25);
|
|
|
|
name2[6]=0; int blue=strtol(name2+4,nullptr,16);
|
|
name2[4]=0; int green=strtol(name2+2,nullptr,16);
|
|
name2[2]=0; int red=strtol(name2,nullptr,16);
|
|
|
|
if (!isFog)
|
|
{
|
|
if (factor==0)
|
|
{
|
|
*validcolor=false;
|
|
return;
|
|
}
|
|
factor = factor * 255 / 25;
|
|
}
|
|
else
|
|
{
|
|
factor=0;
|
|
}
|
|
|
|
*color=MAKEARGB(factor, red, green, blue);
|
|
texture = FNullTextureID();
|
|
*validcolor = true;
|
|
return;
|
|
}
|
|
}
|
|
texture = FNullTextureID();
|
|
}
|
|
side->SetTexture(position, texture);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Sound enviroment handling
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::FloodZone (sector_t *sec, int zonenum)
|
|
{
|
|
if (sec->ZoneNumber == zonenum)
|
|
return;
|
|
|
|
sec->ZoneNumber = zonenum;
|
|
|
|
for (auto check : sec->Lines)
|
|
{
|
|
sector_t *other;
|
|
|
|
if (check->sidedef[1] == nullptr || (check->flags & ML_ZONEBOUNDARY))
|
|
continue;
|
|
|
|
if (check->frontsector == sec)
|
|
{
|
|
assert(check->backsector != nullptr);
|
|
other = check->backsector;
|
|
}
|
|
else
|
|
{
|
|
assert(check->frontsector != nullptr);
|
|
other = check->frontsector;
|
|
}
|
|
|
|
if (other->ZoneNumber != zonenum)
|
|
FloodZone (other, zonenum);
|
|
}
|
|
}
|
|
|
|
void MapLoader::FloodZones ()
|
|
{
|
|
int z = 0, i;
|
|
ReverbContainer *reverb;
|
|
|
|
for (auto &sec : Level->sectors)
|
|
{
|
|
if (sec.ZoneNumber == 0xFFFF)
|
|
{
|
|
FloodZone (&sec, z++);
|
|
}
|
|
}
|
|
Level->Zones.Resize(z);
|
|
reverb = S_FindEnvironment(Level->DefaultEnvironment);
|
|
if (reverb == nullptr)
|
|
{
|
|
Printf("Sound environment %d, %d not found\n", Level->DefaultEnvironment >> 8, Level->DefaultEnvironment & 255);
|
|
reverb = DefaultEnvironments[0];
|
|
}
|
|
for (i = 0; i < z; ++i)
|
|
{
|
|
Level->Zones[i].Environment = reverb;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadVertexes
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadVertexes(MapData * map)
|
|
{
|
|
// Determine number of vertices:
|
|
// total lump length / vertex record length.
|
|
unsigned numvertexes = map->Size(ML_VERTEXES) / sizeof(mapvertex_t);
|
|
|
|
if (numvertexes == 0)
|
|
{
|
|
I_Error("Map has no vertices.\n");
|
|
}
|
|
|
|
// Allocate memory for buffer.
|
|
Level->vertexes.Alloc(numvertexes);
|
|
|
|
auto &fr = map->Reader(ML_VERTEXES);
|
|
|
|
// Copy and convert vertex coordinates, internal representation as fixed.
|
|
for (auto &v : Level->vertexes)
|
|
{
|
|
int16_t x = fr.ReadInt16();
|
|
int16_t y = fr.ReadInt16();
|
|
|
|
v.set(double(x), double(y));
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadZSegs
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadZSegs (FileReader &data)
|
|
{
|
|
for (auto &seg : Level->segs)
|
|
{
|
|
line_t *ldef;
|
|
uint32_t v1 = data.ReadUInt32();
|
|
uint32_t v2 = data.ReadUInt32();
|
|
uint16_t line = data.ReadUInt16();
|
|
uint8_t side = data.ReadUInt8();
|
|
|
|
seg.v1 = &Level->vertexes[v1];
|
|
seg.v2 = &Level->vertexes[v2];
|
|
seg.linedef = ldef = &Level->lines[line];
|
|
seg.sidedef = ldef->sidedef[side];
|
|
seg.frontsector = ldef->sidedef[side]->sector;
|
|
if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != nullptr)
|
|
{
|
|
seg.backsector = ldef->sidedef[side^1]->sector;
|
|
}
|
|
else
|
|
{
|
|
seg.backsector = 0;
|
|
ldef->flags &= ~ML_TWOSIDED;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadGLZSegs
|
|
//
|
|
// This is the GL nodes version of the above function.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadGLZSegs (FileReader &data, int type)
|
|
{
|
|
for (unsigned i = 0; i < Level->subsectors.Size(); ++i)
|
|
{
|
|
|
|
for (size_t j = 0; j < Level->subsectors[i].numlines; ++j)
|
|
{
|
|
seg_t *seg;
|
|
uint32_t v1 = data.ReadUInt32();
|
|
uint32_t partner = data.ReadUInt32();
|
|
uint32_t line;
|
|
|
|
if (type >= 2)
|
|
{
|
|
line = data.ReadUInt32();
|
|
}
|
|
else
|
|
{
|
|
line = data.ReadUInt16();
|
|
if (line == 0xffff) line = 0xffffffff;
|
|
}
|
|
uint8_t side = data.ReadUInt8();
|
|
|
|
seg = Level->subsectors[i].firstline + j;
|
|
seg->v1 = &Level->vertexes[v1];
|
|
if (j == 0)
|
|
{
|
|
seg[Level->subsectors[i].numlines - 1].v2 = seg->v1;
|
|
}
|
|
else
|
|
{
|
|
seg[-1].v2 = seg->v1;
|
|
}
|
|
|
|
seg->PartnerSeg = partner == 0xffffffffu? nullptr : &Level->segs[partner];
|
|
if (line != 0xFFFFFFFF)
|
|
{
|
|
line_t *ldef;
|
|
|
|
seg->linedef = ldef = &Level->lines[line];
|
|
seg->sidedef = ldef->sidedef[side];
|
|
seg->frontsector = ldef->sidedef[side]->sector;
|
|
if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != nullptr)
|
|
{
|
|
seg->backsector = ldef->sidedef[side^1]->sector;
|
|
}
|
|
else
|
|
{
|
|
seg->backsector = 0;
|
|
ldef->flags &= ~ML_TWOSIDED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
seg->linedef = nullptr;
|
|
seg->sidedef = nullptr;
|
|
seg->frontsector = seg->backsector = Level->subsectors[i].firstline->frontsector;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadZNodes
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadZNodes(FileReader &data, int glnodes)
|
|
{
|
|
// Read extra vertices added during node building
|
|
unsigned int i;
|
|
|
|
uint32_t orgVerts = data.ReadUInt32();
|
|
uint32_t newVerts = data.ReadUInt32();
|
|
if (orgVerts > Level->vertexes.Size())
|
|
{ // These nodes are based on a map with more vertex data than we have.
|
|
// We can't use them.
|
|
throw CRecoverableError("Incorrect number of vertexes in nodes.\n");
|
|
}
|
|
auto oldvertexes = &Level->vertexes[0];
|
|
if (orgVerts + newVerts != Level->vertexes.Size())
|
|
{
|
|
Level->vertexes.Reserve(newVerts);
|
|
}
|
|
for (i = 0; i < newVerts; ++i)
|
|
{
|
|
fixed_t x = data.ReadInt32();
|
|
fixed_t y = data.ReadInt32();
|
|
Level->vertexes[i + orgVerts].set(x, y);
|
|
}
|
|
if (oldvertexes != &Level->vertexes[0])
|
|
{
|
|
for (auto &line : Level->lines)
|
|
{
|
|
line.v1 = line.v1 - oldvertexes + &Level->vertexes[0];
|
|
line.v2 = line.v2 - oldvertexes + &Level->vertexes[0];
|
|
}
|
|
}
|
|
|
|
// Read the subsectors
|
|
uint32_t currSeg;
|
|
uint32_t numSubs = data.ReadUInt32();
|
|
Level->subsectors.Alloc(numSubs);
|
|
memset (&Level->subsectors[0], 0, Level->subsectors.Size()*sizeof(subsector_t));
|
|
|
|
for (i = currSeg = 0; i < numSubs; ++i)
|
|
{
|
|
uint32_t numsegs = data.ReadUInt32();
|
|
Level->subsectors[i].firstline = (seg_t *)(size_t)currSeg; // Oh damn. I should have stored the seg count sooner.
|
|
Level->subsectors[i].numlines = numsegs;
|
|
currSeg += numsegs;
|
|
}
|
|
|
|
// Read the segs
|
|
uint32_t numSegs = data.ReadUInt32();
|
|
|
|
// The number of segs stored should match the number of
|
|
// segs used by subsectors.
|
|
if (numSegs != currSeg)
|
|
{
|
|
throw CRecoverableError("Incorrect number of segs in nodes.\n");
|
|
}
|
|
|
|
Level->segs.Alloc(numSegs);
|
|
memset (&Level->segs[0], 0, numSegs*sizeof(seg_t));
|
|
|
|
for (auto &sub : Level->subsectors)
|
|
{
|
|
sub.firstline = &Level->segs[(size_t)sub.firstline];
|
|
}
|
|
|
|
if (glnodes == 0)
|
|
{
|
|
LoadZSegs (data);
|
|
}
|
|
else
|
|
{
|
|
LoadGLZSegs (data, glnodes);
|
|
}
|
|
|
|
// Read nodes
|
|
uint32_t numNodes = data.ReadUInt32();
|
|
|
|
auto &nodes = Level->nodes;
|
|
nodes.Alloc(numNodes);
|
|
memset (&nodes[0], 0, sizeof(node_t)*numNodes);
|
|
|
|
for (i = 0; i < numNodes; ++i)
|
|
{
|
|
if (glnodes < 3)
|
|
{
|
|
nodes[i].x = data.ReadInt16() * FRACUNIT;
|
|
nodes[i].y = data.ReadInt16() * FRACUNIT;
|
|
nodes[i].dx = data.ReadInt16() * FRACUNIT;
|
|
nodes[i].dy = data.ReadInt16() * FRACUNIT;
|
|
}
|
|
else
|
|
{
|
|
nodes[i].x = data.ReadInt32();
|
|
nodes[i].y = data.ReadInt32();
|
|
nodes[i].dx = data.ReadInt32();
|
|
nodes[i].dy = data.ReadInt32();
|
|
}
|
|
for (int j = 0; j < 2; ++j)
|
|
{
|
|
for (int k = 0; k < 4; ++k)
|
|
{
|
|
nodes[i].bbox[j][k] = data.ReadInt16();
|
|
}
|
|
}
|
|
for (int m = 0; m < 2; ++m)
|
|
{
|
|
uint32_t child = data.ReadUInt32();
|
|
if (child & 0x80000000)
|
|
{
|
|
nodes[i].children[m] = (uint8_t *)&Level->subsectors[child & 0x7FFFFFFF] + 1;
|
|
}
|
|
else
|
|
{
|
|
nodes[i].children[m] = &nodes[child];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
bool MapLoader::LoadExtendedNodes (FileReader &dalump, uint32_t id)
|
|
{
|
|
int type;
|
|
bool compressed;
|
|
|
|
switch (id)
|
|
{
|
|
case MAKE_ID('Z','N','O','D'):
|
|
type = 0;
|
|
compressed = true;
|
|
break;
|
|
|
|
case MAKE_ID('Z','G','L','N'):
|
|
type = 1;
|
|
compressed = true;
|
|
break;
|
|
|
|
case MAKE_ID('Z','G','L','2'):
|
|
type = 2;
|
|
compressed = true;
|
|
break;
|
|
|
|
case MAKE_ID('Z','G','L','3'):
|
|
type = 3;
|
|
compressed = true;
|
|
break;
|
|
|
|
case MAKE_ID('X','N','O','D'):
|
|
type = 0;
|
|
compressed = false;
|
|
break;
|
|
|
|
case MAKE_ID('X','G','L','N'):
|
|
type = 1;
|
|
compressed = false;
|
|
break;
|
|
|
|
case MAKE_ID('X','G','L','2'):
|
|
type = 2;
|
|
compressed = false;
|
|
break;
|
|
|
|
case MAKE_ID('X','G','L','3'):
|
|
type = 3;
|
|
compressed = false;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (compressed)
|
|
{
|
|
FileReader zip;
|
|
if (zip.OpenDecompressor(dalump, -1, METHOD_ZLIB, false))
|
|
{
|
|
LoadZNodes(zip, type);
|
|
}
|
|
else
|
|
{
|
|
Printf("Error loading nodes: Corrupt data.\n");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LoadZNodes(dalump, type);
|
|
}
|
|
return true;
|
|
}
|
|
catch (CRecoverableError &error)
|
|
{
|
|
Printf("Error loading nodes: %s\n", error.GetMessage());
|
|
|
|
Level->subsectors.Clear();
|
|
Level->segs.Clear();
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_CheckV4Nodes
|
|
// http://www.sbsoftware.com/files/DeePBSPV4specs.txt
|
|
//
|
|
//===========================================================================
|
|
|
|
static bool P_CheckV4Nodes(MapData *map)
|
|
{
|
|
char header[8];
|
|
|
|
map->Read(ML_NODES, header, 8);
|
|
return !memcmp(header, "xNd4\0\0\0\0", 8);
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadSegs
|
|
//
|
|
// killough 5/3/98: reformatted, cleaned up
|
|
//
|
|
//===========================================================================
|
|
|
|
struct badseg
|
|
{
|
|
badseg(int t, int s, int d) : badtype(t), badsegnum(s), baddata(d) {}
|
|
int badtype;
|
|
int badsegnum;
|
|
int baddata;
|
|
};
|
|
|
|
template<class segtype>
|
|
bool MapLoader::LoadSegs (MapData * map)
|
|
{
|
|
uint32_t numvertexes = Level->vertexes.Size();
|
|
TArray<uint8_t> vertchanged(numvertexes, true);
|
|
uint32_t segangle;
|
|
//int ptp_angle; // phares 10/4/98
|
|
//int delta_angle; // phares 10/4/98
|
|
uint32_t vnum1,vnum2; // phares 10/4/98
|
|
int lumplen = map->Size(ML_SEGS);
|
|
|
|
memset(vertchanged.Data(), 0, numvertexes); // phares 10/4/98
|
|
|
|
unsigned numsegs = lumplen / sizeof(segtype);
|
|
|
|
if (numsegs == 0)
|
|
{
|
|
Printf ("This map has no segs.\n");
|
|
Level->subsectors.Clear();
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
|
|
Level->segs.Alloc(numsegs);
|
|
auto &segs = Level->segs;
|
|
memset (&segs[0], 0, numsegs*sizeof(seg_t));
|
|
|
|
auto data = map->Read(ML_SEGS);
|
|
|
|
for (auto &sub : Level->subsectors)
|
|
{
|
|
sub.firstline = &segs[(size_t)sub.firstline];
|
|
}
|
|
|
|
// phares: 10/4/98: Vertchanged is an array that represents the vertices.
|
|
// Mark those used by linedefs. A marked vertex is one that is not a
|
|
// candidate for movement further down.
|
|
|
|
for (auto &line : Level->lines)
|
|
{
|
|
vertchanged[Index(line.v1)] = vertchanged[Index(line.v2)] = 1;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (unsigned i = 0; i < numsegs; i++)
|
|
{
|
|
seg_t *li = &segs[i];
|
|
segtype *ml = ((segtype *) data.Data()) + i;
|
|
|
|
int side, linedef;
|
|
line_t *ldef;
|
|
|
|
vnum1 = ml->V1();
|
|
vnum2 = ml->V2();
|
|
|
|
if (vnum1 >= numvertexes || vnum2 >= numvertexes)
|
|
{
|
|
throw badseg(0, i, MAX(vnum1, vnum2));
|
|
}
|
|
|
|
li->v1 = &Level->vertexes[vnum1];
|
|
li->v2 = &Level->vertexes[vnum2];
|
|
|
|
segangle = (uint16_t)LittleShort(ml->angle);
|
|
|
|
// phares 10/4/98: In the case of a lineseg that was created by splitting
|
|
// another line, it appears that the line angle is inherited from the
|
|
// father line. Due to roundoff, the new vertex may have been placed 'off
|
|
// the line'. When you get close to such a line, and it is very short,
|
|
// it's possible that the roundoff error causes 'firelines', the thin
|
|
// lines that can draw from screen top to screen bottom occasionally. This
|
|
// is due to all the angle calculations that are done based on the line
|
|
// angle, the angles from the viewer to the vertices, and the viewer's
|
|
// angle in the world. In the case of firelines, the rounded-off position
|
|
// of one of the vertices determines one of these angles, and introduces
|
|
// an error in the scaling factor for mapping textures and determining
|
|
// where on the screen the ceiling and floor spans should be shown. For a
|
|
// fireline, the engine thinks the ceiling bottom and floor top are at the
|
|
// midpoint of the screen. So you get ceilings drawn all the way down to the
|
|
// screen midpoint, and floors drawn all the way up. Thus 'firelines'. The
|
|
// name comes from the original sighting, which involved a fire texture.
|
|
//
|
|
// To correct this, reset the vertex that was added so that it sits ON the
|
|
// split line.
|
|
//
|
|
// To know which of the two vertices was added, its number is greater than
|
|
// that of the last of the author-created vertices. If both vertices of the
|
|
// line were added by splitting, pick the higher-numbered one. Once you've
|
|
// changed a vertex, don't change it again if it shows up in another seg.
|
|
//
|
|
// To determine if there's an error in the first place, find the
|
|
// angle of the line between the two seg vertices. If it's one degree or more
|
|
// off, then move one vertex. This may seem insignificant, but one degree
|
|
// errors _can_ cause firelines.
|
|
|
|
DAngle ptp_angle = (li->v2->fPos() - li->v1->fPos()).Angle();
|
|
DAngle seg_angle = AngleToFloat(segangle << 16);
|
|
DAngle delta_angle = absangle(ptp_angle, seg_angle);
|
|
|
|
if (delta_angle >= 1.)
|
|
{
|
|
double dis = (li->v2->fPos() - li->v1->fPos()).Length();
|
|
DVector2 delta = seg_angle.ToVector(dis);
|
|
if ((vnum2 > vnum1) && (vertchanged[vnum2] == 0))
|
|
{
|
|
li->v2->set(li->v1->fPos() + delta);
|
|
vertchanged[vnum2] = 1; // this was changed
|
|
}
|
|
else if (vertchanged[vnum1] == 0)
|
|
{
|
|
li->v1->set(li->v2->fPos() - delta);
|
|
vertchanged[vnum1] = 1; // this was changed
|
|
}
|
|
}
|
|
|
|
linedef = LittleShort(ml->linedef);
|
|
if ((unsigned)linedef >= Level->lines.Size())
|
|
{
|
|
throw badseg(1, i, linedef);
|
|
}
|
|
ldef = &Level->lines[linedef];
|
|
li->linedef = ldef;
|
|
side = LittleShort(ml->side);
|
|
if (side != 0 && side != 1)
|
|
{
|
|
throw badseg(3, i, side);
|
|
}
|
|
if ((unsigned)(Index(ldef->sidedef[side])) >= Level->sides.Size())
|
|
{
|
|
throw badseg(2, i, Index(ldef->sidedef[side]));
|
|
}
|
|
li->sidedef = ldef->sidedef[side];
|
|
li->frontsector = ldef->sidedef[side]->sector;
|
|
|
|
// killough 5/3/98: ignore 2s flag if second sidedef missing:
|
|
if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != nullptr)
|
|
{
|
|
li->backsector = ldef->sidedef[side^1]->sector;
|
|
}
|
|
else
|
|
{
|
|
li->backsector = 0;
|
|
ldef->flags &= ~ML_TWOSIDED;
|
|
}
|
|
}
|
|
}
|
|
catch (const badseg &bad) // the preferred way is to catch by (const) reference.
|
|
{
|
|
switch (bad.badtype)
|
|
{
|
|
case 0:
|
|
Printf ("Seg %d references a nonexistant vertex %d (max %d).\n", bad.badsegnum, bad.baddata, numvertexes);
|
|
break;
|
|
|
|
case 1:
|
|
Printf ("Seg %d references a nonexistant linedef %d (max %u).\n", bad.badsegnum, bad.baddata, Level->lines.Size());
|
|
break;
|
|
|
|
case 2:
|
|
Printf ("The linedef for seg %d references a nonexistant sidedef %d (max %d).\n", bad.badsegnum, bad.baddata, Level->sides.Size());
|
|
break;
|
|
|
|
case 3:
|
|
Printf("Sidedef reference in seg %d is %d (must be 0 or 1).\n", bad.badsegnum, bad.baddata);
|
|
break;
|
|
}
|
|
Printf ("The BSP will be rebuilt.\n");
|
|
Level->segs.Clear();
|
|
Level->subsectors.Clear();
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadSubsectors
|
|
//
|
|
//===========================================================================
|
|
|
|
template<class subsectortype, class segtype>
|
|
bool MapLoader::LoadSubsectors (MapData * map)
|
|
{
|
|
uint32_t maxseg = map->Size(ML_SEGS) / sizeof(segtype);
|
|
|
|
unsigned numsubsectors = map->Size(ML_SSECTORS) / sizeof(subsectortype);
|
|
|
|
if (numsubsectors == 0 || maxseg == 0 )
|
|
{
|
|
Printf ("This map has an incomplete BSP tree.\n");
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
|
|
auto &subsectors = Level->subsectors;
|
|
subsectors.Alloc(numsubsectors);
|
|
auto &fr = map->Reader(ML_SSECTORS);
|
|
|
|
memset (&subsectors[0], 0, numsubsectors*sizeof(subsector_t));
|
|
|
|
for (unsigned i = 0; i < numsubsectors; i++)
|
|
{
|
|
subsectortype subd;
|
|
|
|
subd.numsegs = sizeof(subd.numsegs) == 2 ? fr.ReadUInt16() : fr.ReadUInt32();
|
|
subd.firstseg = sizeof(subd.firstseg) == 2 ? fr.ReadUInt16() : fr.ReadUInt32();
|
|
|
|
if (subd.numsegs == 0)
|
|
{
|
|
Printf ("Subsector %i is empty.\n", i);
|
|
Level->subsectors.Clear();
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
|
|
subsectors[i].numlines = subd.numsegs;
|
|
subsectors[i].firstline = (seg_t *)(size_t)subd.firstseg;
|
|
|
|
if ((size_t)subsectors[i].firstline >= maxseg)
|
|
{
|
|
Printf ("Subsector %d contains invalid segs %u-%u\n"
|
|
"The BSP will be rebuilt.\n", i, (unsigned)((size_t)subsectors[i].firstline),
|
|
(unsigned)((size_t)subsectors[i].firstline) + subsectors[i].numlines - 1);
|
|
Level->nodes.Clear();
|
|
Level->subsectors.Clear();
|
|
return false;
|
|
}
|
|
else if ((size_t)subsectors[i].firstline + subsectors[i].numlines > maxseg)
|
|
{
|
|
Printf ("Subsector %d contains invalid segs %u-%u\n"
|
|
"The BSP will be rebuilt.\n", i, maxseg,
|
|
(unsigned)((size_t)subsectors[i].firstline) + subsectors[i].numlines - 1);
|
|
|
|
Level->nodes.Clear();
|
|
Level->subsectors.Clear();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadSectors
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadSectors (MapData *map, FMissingTextureTracker &missingtex)
|
|
{
|
|
mapsector_t *ms;
|
|
sector_t* ss;
|
|
int defSeqType;
|
|
int lumplen = map->Size(ML_SECTORS);
|
|
|
|
unsigned numsectors = lumplen / sizeof(mapsector_t);
|
|
Level->sectors.Alloc(numsectors);
|
|
auto sectors = &Level->sectors[0];
|
|
memset (sectors, 0, numsectors*sizeof(sector_t));
|
|
|
|
if (Level->flags & LEVEL_SNDSEQTOTALCTRL)
|
|
defSeqType = 0;
|
|
else
|
|
defSeqType = -1;
|
|
|
|
auto msp = map->Read(ML_SECTORS);
|
|
ms = (mapsector_t*)msp.Data();
|
|
ss = sectors;
|
|
|
|
// Extended properties
|
|
sectors[0].e = new extsector_t[numsectors];
|
|
|
|
for (unsigned i = 0; i < numsectors; i++, ss++, ms++)
|
|
{
|
|
ss->e = §ors[0].e[i];
|
|
ss->Level = Level;
|
|
if (!map->HasBehavior) ss->Flags |= SECF_FLOORDROP;
|
|
ss->SetPlaneTexZ(sector_t::floor, (double)LittleShort(ms->floorheight));
|
|
ss->floorplane.set(0, 0, 1., -ss->GetPlaneTexZ(sector_t::floor));
|
|
ss->SetPlaneTexZ(sector_t::ceiling, (double)LittleShort(ms->ceilingheight));
|
|
ss->ceilingplane.set(0, 0, -1., ss->GetPlaneTexZ(sector_t::ceiling));
|
|
SetTexture(ss, i, sector_t::floor, ms->floorpic, missingtex, true);
|
|
SetTexture(ss, i, sector_t::ceiling, ms->ceilingpic, missingtex, true);
|
|
ss->lightlevel = LittleShort(ms->lightlevel);
|
|
if (map->HasBehavior)
|
|
ss->special = LittleShort(ms->special);
|
|
else // [RH] Translate to new sector special
|
|
ss->special = Level->TranslateSectorSpecial (LittleShort(ms->special));
|
|
Level->tagManager.AddSectorTag(i, LittleShort(ms->tag));
|
|
ss->thinglist = nullptr;
|
|
ss->touching_thinglist = nullptr; // phares 3/14/98
|
|
ss->sectorportal_thinglist = nullptr;
|
|
ss->touching_renderthings = nullptr;
|
|
ss->seqType = defSeqType;
|
|
ss->SeqName = NAME_None;
|
|
ss->nextsec = -1; //jff 2/26/98 add fields to support locking out
|
|
ss->prevsec = -1; // stair retriggering until build completes
|
|
memset(ss->SpecialColors, -1, sizeof(ss->SpecialColors));
|
|
memset(ss->AdditiveColors, 0, sizeof(ss->AdditiveColors));
|
|
|
|
ss->SetAlpha(sector_t::floor, 1.);
|
|
ss->SetAlpha(sector_t::ceiling, 1.);
|
|
ss->SetXScale(sector_t::floor, 1.); // [RH] floor and ceiling scaling
|
|
ss->SetYScale(sector_t::floor, 1.);
|
|
ss->SetXScale(sector_t::ceiling, 1.);
|
|
ss->SetYScale(sector_t::ceiling, 1.);
|
|
|
|
ss->heightsec = nullptr; // sector used to get floor and ceiling height
|
|
// killough 3/7/98: end changes
|
|
|
|
ss->gravity = 1.f; // [RH] Default sector gravity of 1.0
|
|
ss->ZoneNumber = 0xFFFF;
|
|
ss->terrainnum[sector_t::ceiling] = ss->terrainnum[sector_t::floor] = -1;
|
|
|
|
|
|
|
|
// [RH] Sectors default to white light with the default fade.
|
|
// If they are outside (have a sky ceiling), they use the outside fog.
|
|
ss->Colormap.LightColor = PalEntry(255, 255, 255);
|
|
if (Level->outsidefog != 0xff000000 && (ss->GetTexture(sector_t::ceiling) == skyflatnum || (ss->special&0xff) == Sector_Outside))
|
|
{
|
|
ss->Colormap.FadeColor.SetRGB(Level->outsidefog);
|
|
}
|
|
else if (Level->flags & LEVEL_HASFADETABLE)
|
|
{
|
|
ss->Colormap.FadeColor= 0x939393; // The true color software renderer needs this. (The hardware renderer will ignore this value if LEVEL_HASFADETABLE is set.)
|
|
}
|
|
else
|
|
{
|
|
ss->Colormap.FadeColor.SetRGB(Level->fadeto);
|
|
}
|
|
|
|
|
|
// killough 8/28/98: initialize all sectors to normal friction
|
|
ss->friction = ORIG_FRICTION;
|
|
ss->movefactor = ORIG_FRICTION_FACTOR;
|
|
ss->sectornum = i;
|
|
ss->ibocount = -1;
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadNodes
|
|
//
|
|
//===========================================================================
|
|
|
|
template<class nodetype, class subsectortype>
|
|
bool MapLoader::LoadNodes (MapData * map)
|
|
{
|
|
FMemLump data;
|
|
int j;
|
|
int k;
|
|
nodetype *mn;
|
|
node_t* no;
|
|
uint16_t* used;
|
|
int lumplen = map->Size(ML_NODES);
|
|
int maxss = map->Size(ML_SSECTORS) / sizeof(subsectortype);
|
|
|
|
unsigned numnodes = (lumplen - nodetype::NF_LUMPOFFSET) / sizeof(nodetype);
|
|
|
|
if ((numnodes == 0 && maxss != 1) || maxss == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto &nodes = Level->nodes;
|
|
nodes.Alloc(numnodes);
|
|
used = (uint16_t *)alloca (sizeof(uint16_t)*numnodes);
|
|
memset (used, 0, sizeof(uint16_t)*numnodes);
|
|
|
|
auto mnp = map->Read(ML_NODES);
|
|
mn = (nodetype*)(mnp.Data() + nodetype::NF_LUMPOFFSET);
|
|
no = &nodes[0];
|
|
|
|
for (unsigned i = 0; i < numnodes; i++, no++, mn++)
|
|
{
|
|
no->x = LittleShort(mn->x)<<FRACBITS;
|
|
no->y = LittleShort(mn->y)<<FRACBITS;
|
|
no->dx = LittleShort(mn->dx)<<FRACBITS;
|
|
no->dy = LittleShort(mn->dy)<<FRACBITS;
|
|
for (j = 0; j < 2; j++)
|
|
{
|
|
int child = mn->Child(j);
|
|
if (child & nodetype::NF_SUBSECTOR)
|
|
{
|
|
child &= ~nodetype::NF_SUBSECTOR;
|
|
if (child >= maxss)
|
|
{
|
|
Printf ("BSP node %d references invalid subsector %d.\n"
|
|
"The BSP will be rebuilt.\n", i, child);
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
no->children[j] = (uint8_t *)&Level->subsectors[child] + 1;
|
|
}
|
|
else if ((unsigned)child >= numnodes)
|
|
{
|
|
Printf ("BSP node %d references invalid node %d.\n"
|
|
"The BSP will be rebuilt.\n", i, Index(((node_t *)no->children[j])));
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
else if (used[child])
|
|
{
|
|
Printf ("BSP node %d references node %d,\n"
|
|
"which is already used by node %d.\n"
|
|
"The BSP will be rebuilt.\n", i, child, used[child]-1);
|
|
Level->nodes.Clear();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
no->children[j] = &nodes[child];
|
|
used[child] = j + 1;
|
|
}
|
|
for (k = 0; k < 4; k++)
|
|
{
|
|
no->bbox[j][k] = (float)LittleShort(mn->bbox[j][k]);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// SetMapThingUserData
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SetMapThingUserData(AActor *actor, unsigned udi)
|
|
{
|
|
if (actor == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
while (MapThingsUserData[udi].Key != NAME_None)
|
|
{
|
|
FName varname = MapThingsUserData[udi].Key;
|
|
PField *var = dyn_cast<PField>(actor->GetClass()->FindSymbol(varname, true));
|
|
|
|
if (var == nullptr || (var->Flags & (VARF_Native|VARF_Private|VARF_Protected|VARF_Static)) || !var->Type->isScalar())
|
|
{
|
|
DPrintf(DMSG_WARNING, "%s is not a writable user variable in class %s\n", varname.GetChars(),
|
|
actor->GetClass()->TypeName.GetChars());
|
|
}
|
|
else
|
|
{ // Set the value of the specified user variable.
|
|
void *addr = reinterpret_cast<uint8_t *>(actor) + var->Offset;
|
|
if (var->Type == TypeString)
|
|
{
|
|
var->Type->InitializeValue(addr, &MapThingsUserData[udi].StringVal);
|
|
}
|
|
else if (var->Type->isFloat())
|
|
{
|
|
var->Type->SetValue(addr, MapThingsUserData[udi].FloatVal);
|
|
}
|
|
else if (var->Type->isInt() || var->Type == TypeBool)
|
|
{
|
|
var->Type->SetValue(addr, MapThingsUserData[udi].IntVal);
|
|
}
|
|
}
|
|
|
|
udi++;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadThings
|
|
//
|
|
//===========================================================================
|
|
|
|
uint16_t MakeSkill(int flags)
|
|
{
|
|
uint16_t res = 0;
|
|
if (flags & 1) res |= 1+2;
|
|
if (flags & 2) res |= 4;
|
|
if (flags & 4) res |= 8+16;
|
|
return res;
|
|
}
|
|
|
|
void MapLoader::LoadThings (MapData * map)
|
|
{
|
|
mapthing_t *mt;
|
|
auto mtp = map->Read(ML_THINGS);
|
|
int numthings = mtp.Size() / sizeof(mapthing_t);
|
|
mt = (mapthing_t*)mtp.Data();
|
|
|
|
MapThingsConverted.Resize(numthings);
|
|
FMapThing *mti = &MapThingsConverted[0];
|
|
|
|
// [RH] ZDoom now uses Hexen-style maps as its native format.
|
|
// Since this is the only place where Doom-style Things are ever
|
|
// referenced, we translate them into a Hexen-style thing.
|
|
for (int i=0 ; i < numthings; i++, mt++)
|
|
{
|
|
// [RH] At this point, monsters unique to Doom II were weeded out
|
|
// if the IWAD wasn't for Doom II. P_SpawnMapThing() can now
|
|
// handle these and more cases better, so we just pass it
|
|
// everything and let it decide what to do with them.
|
|
|
|
// [RH] Need to translate the spawn flags to Hexen format.
|
|
short flags = LittleShort(mt->options);
|
|
|
|
memset (&mti[i], 0, sizeof(mti[i]));
|
|
|
|
mti[i].Gravity = 1;
|
|
mti[i].Conversation = 0;
|
|
mti[i].SkillFilter = MakeSkill(flags);
|
|
mti[i].ClassFilter = 0xffff; // Doom map format doesn't have class flags so spawn for all player classes
|
|
mti[i].RenderStyle = STYLE_Count;
|
|
mti[i].Alpha = -1;
|
|
mti[i].Health = 1;
|
|
mti[i].FloatbobPhase = -1;
|
|
|
|
mti[i].pos.X = LittleShort(mt->x);
|
|
mti[i].pos.Y = LittleShort(mt->y);
|
|
mti[i].angle = LittleShort(mt->angle);
|
|
mti[i].EdNum = LittleShort(mt->type);
|
|
mti[i].info = DoomEdMap.CheckKey(mti[i].EdNum);
|
|
|
|
|
|
#ifndef NO_EDATA
|
|
if (mti[i].info != nullptr && mti[i].info->Special == SMT_EDThing)
|
|
{
|
|
ProcessEDMapthing(&mti[i], flags);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
flags &= ~MTF_SKILLMASK;
|
|
mti[i].flags = (short)((flags & 0xf) | 0x7e0);
|
|
if (gameinfo.gametype == GAME_Strife)
|
|
{
|
|
mti[i].flags &= ~MTF_AMBUSH;
|
|
if (flags & STF_SHADOW) mti[i].flags |= MTF_SHADOW;
|
|
if (flags & STF_ALTSHADOW) mti[i].flags |= MTF_ALTSHADOW;
|
|
if (flags & STF_STANDSTILL) mti[i].flags |= MTF_STANDSTILL;
|
|
if (flags & STF_AMBUSH) mti[i].flags |= MTF_AMBUSH;
|
|
if (flags & STF_FRIENDLY) mti[i].flags |= MTF_FRIENDLY;
|
|
}
|
|
else
|
|
{
|
|
if (flags & BTF_BADEDITORCHECK)
|
|
{
|
|
flags &= 0x1F;
|
|
}
|
|
if (flags & BTF_NOTDEATHMATCH) mti[i].flags &= ~MTF_DEATHMATCH;
|
|
if (flags & BTF_NOTCOOPERATIVE) mti[i].flags &= ~MTF_COOPERATIVE;
|
|
if (flags & BTF_FRIENDLY) mti[i].flags |= MTF_FRIENDLY;
|
|
}
|
|
if (flags & BTF_NOTSINGLE) mti[i].flags &= ~MTF_SINGLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// [RH]
|
|
// P_LoadThings2
|
|
//
|
|
// Same as P_LoadThings() except it assumes Things are
|
|
// saved Hexen-style. Position also controls which single-
|
|
// player start spots are spawned by filtering out those
|
|
// whose first parameter don't match position.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadThings2 (MapData * map)
|
|
{
|
|
int lumplen = map->Size(ML_THINGS);
|
|
int numthings = lumplen / sizeof(mapthinghexen_t);
|
|
|
|
MapThingsConverted.Resize(numthings);
|
|
FMapThing *mti = &MapThingsConverted[0];
|
|
|
|
auto mtp = map->Read(ML_THINGS);
|
|
mapthinghexen_t *mth = (mapthinghexen_t*)mtp.Data();
|
|
|
|
for(int i = 0; i< numthings; i++)
|
|
{
|
|
memset (&mti[i], 0, sizeof(mti[i]));
|
|
|
|
mti[i].thingid = LittleShort(mth[i].thingid);
|
|
mti[i].pos.X = LittleShort(mth[i].x);
|
|
mti[i].pos.Y = LittleShort(mth[i].y);
|
|
mti[i].pos.Z = LittleShort(mth[i].z);
|
|
mti[i].angle = LittleShort(mth[i].angle);
|
|
mti[i].EdNum = LittleShort(mth[i].type);
|
|
mti[i].info = DoomEdMap.CheckKey(mti[i].EdNum);
|
|
mti[i].flags = LittleShort(mth[i].flags);
|
|
mti[i].special = mth[i].special;
|
|
for(int j=0;j<5;j++) mti[i].args[j] = mth[i].args[j];
|
|
mti[i].SkillFilter = MakeSkill(mti[i].flags);
|
|
mti[i].ClassFilter = (mti[i].flags & MTF_CLASS_MASK) >> MTF_CLASS_SHIFT;
|
|
mti[i].flags &= ~(MTF_SKILLMASK|MTF_CLASS_MASK);
|
|
if (Level->flags2 & LEVEL2_HEXENHACK)
|
|
{
|
|
mti[i].flags &= 0x7ff; // mask out Strife flags if playing an original Hexen map.
|
|
}
|
|
|
|
mti[i].Gravity = 1;
|
|
mti[i].RenderStyle = STYLE_Count;
|
|
mti[i].Alpha = -1;
|
|
mti[i].Health = 1;
|
|
mti[i].FloatbobPhase = -1;
|
|
mti[i].friendlyseeblocks = -1;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SpawnThings (int position)
|
|
{
|
|
int numthings = MapThingsConverted.Size();
|
|
|
|
for (int i=0; i < numthings; i++)
|
|
{
|
|
AActor *actor = Level->SpawnMapThing (i, &MapThingsConverted[i], position);
|
|
unsigned *udi = MapThingsUserDataIndex.CheckKey((unsigned)i);
|
|
if (udi != nullptr)
|
|
{
|
|
SetMapThingUserData(actor, *udi);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadLineDefs
|
|
//
|
|
// killough 4/4/98: split into two functions, to allow sidedef overloading
|
|
//
|
|
// [RH] Actually split into four functions to allow for Hexen and Doom
|
|
// linedefs.
|
|
//
|
|
//===========================================================================
|
|
|
|
//===========================================================================
|
|
//
|
|
// [RH] Set line id (as appropriate) here
|
|
// for Doom format maps this must be done in P_TranslateLineDef because
|
|
// the tag doesn't always go into the first arg.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SetLineID (int i, line_t *ld)
|
|
{
|
|
if (Level->maptype == MAPTYPE_HEXEN)
|
|
{
|
|
int setid = -1;
|
|
switch (ld->special)
|
|
{
|
|
case Line_SetIdentification:
|
|
if (!(Level->flags2 & LEVEL2_HEXENHACK))
|
|
{
|
|
setid = ld->args[0] + 256 * ld->args[4];
|
|
ld->flags |= ld->args[1]<<16;
|
|
}
|
|
else
|
|
{
|
|
setid = ld->args[0];
|
|
}
|
|
ld->special = 0;
|
|
break;
|
|
|
|
case TranslucentLine:
|
|
setid = ld->args[0];
|
|
ld->flags |= ld->args[3]<<16;
|
|
break;
|
|
|
|
case Teleport_Line:
|
|
case Scroll_Texture_Model:
|
|
setid = ld->args[0];
|
|
break;
|
|
|
|
case Polyobj_StartLine:
|
|
setid = ld->args[3];
|
|
break;
|
|
|
|
case Polyobj_ExplicitLine:
|
|
setid = ld->args[4];
|
|
break;
|
|
|
|
case Plane_Align:
|
|
if (!(Level->ib_compatflags & BCOMPATF_NOSLOPEID)) setid = ld->args[2];
|
|
break;
|
|
|
|
case Static_Init:
|
|
if (ld->args[1] == Init_SectorLink) setid = ld->args[0];
|
|
break;
|
|
|
|
case Line_SetPortal:
|
|
setid = ld->args[1]; // 0 = target id, 1 = this id, 2 = plane anchor
|
|
break;
|
|
}
|
|
if (setid != -1)
|
|
{
|
|
Level->tagManager.AddLineID(i, setid);
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SaveLineSpecial (line_t *ld)
|
|
{
|
|
if (ld->sidedef[0] == nullptr)
|
|
return;
|
|
|
|
uint32_t sidenum = Index(ld->sidedef[0]);
|
|
// killough 4/4/98: support special sidedef interpretation below
|
|
// [RH] Save Static_Init only if it's interested in the textures
|
|
if (ld->special != Static_Init || ld->args[1] == Init_Color)
|
|
{
|
|
sidetemp[sidenum].a.special = ld->special;
|
|
sidetemp[sidenum].a.tag = ld->args[0];
|
|
}
|
|
else
|
|
{
|
|
sidetemp[sidenum].a.special = 0;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::FinishLoadingLineDef(line_t *ld, int alpha)
|
|
{
|
|
bool additive = false;
|
|
|
|
ld->frontsector = ld->sidedef[0] != nullptr ? ld->sidedef[0]->sector : nullptr;
|
|
ld->backsector = ld->sidedef[1] != nullptr ? ld->sidedef[1]->sector : nullptr;
|
|
double dx = (ld->v2->fX() - ld->v1->fX());
|
|
double dy = (ld->v2->fY() - ld->v1->fY());
|
|
int linenum = Index(ld);
|
|
|
|
if (ld->frontsector == nullptr)
|
|
{
|
|
Printf ("Line %d has no front sector\n", linemap[linenum]);
|
|
}
|
|
|
|
// [RH] Set some new sidedef properties
|
|
int len = (int)(g_sqrt (dx*dx + dy*dy) + 0.5f);
|
|
|
|
if (ld->sidedef[0] != nullptr)
|
|
{
|
|
ld->sidedef[0]->linedef = ld;
|
|
ld->sidedef[0]->TexelLength = len;
|
|
|
|
}
|
|
if (ld->sidedef[1] != nullptr)
|
|
{
|
|
ld->sidedef[1]->linedef = ld;
|
|
ld->sidedef[1]->TexelLength = len;
|
|
}
|
|
|
|
switch (ld->special)
|
|
{ // killough 4/11/98: handle special types
|
|
case TranslucentLine: // killough 4/11/98: translucent 2s textures
|
|
// [RH] Second arg controls how opaque it is.
|
|
if (alpha == SHRT_MIN)
|
|
{
|
|
alpha = ld->args[1];
|
|
additive = !!ld->args[2];
|
|
}
|
|
else if (alpha < 0)
|
|
{
|
|
alpha = -alpha;
|
|
additive = true;
|
|
}
|
|
|
|
double dalpha = alpha / 255.;
|
|
if (!ld->args[0])
|
|
{
|
|
ld->alpha = dalpha;
|
|
if (additive)
|
|
{
|
|
ld->flags |= ML_ADDTRANS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (unsigned j = 0; j < Level->lines.Size(); j++)
|
|
{
|
|
if (Level->LineHasId(j, ld->args[0]))
|
|
{
|
|
Level->lines[j].alpha = dalpha;
|
|
if (additive)
|
|
{
|
|
Level->lines[j].flags |= ML_ADDTRANS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ld->special = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// killough 4/4/98: delay using sidedefs until they are loaded
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::FinishLoadingLineDefs ()
|
|
{
|
|
for (auto &line : Level->lines)
|
|
{
|
|
FinishLoadingLineDef(&line, sidetemp[Index(line.sidedef[0])].a.alpha);
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::SetSideNum (side_t **sidenum_p, uint16_t sidenum)
|
|
{
|
|
if (sidenum == NO_INDEX)
|
|
{
|
|
*sidenum_p = nullptr;
|
|
}
|
|
else if (sidecount < (int)Level->sides.Size())
|
|
{
|
|
sidetemp[sidecount].a.map = sidenum;
|
|
*sidenum_p = &Level->sides[sidecount++];
|
|
}
|
|
else
|
|
{
|
|
I_Error ("%d sidedefs is not enough\n", sidecount);
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadLineDefs (MapData * map)
|
|
{
|
|
int i, skipped;
|
|
line_t *ld;
|
|
maplinedef_t *mld;
|
|
|
|
auto mldf = map->Read(ML_LINEDEFS);
|
|
int numlines = mldf.Size() / sizeof(maplinedef_t);
|
|
linemap.Resize(numlines);
|
|
|
|
// [RH] Count the number of sidedef references. This is the number of
|
|
// sidedefs we need. The actual number in the SIDEDEFS lump might be less.
|
|
// Lines with 0 length are also removed.
|
|
|
|
for (skipped = sidecount = i = 0; i < numlines; )
|
|
{
|
|
mld = ((maplinedef_t*)mldf.Data()) + i;
|
|
unsigned v1 = LittleShort(mld->v1);
|
|
unsigned v2 = LittleShort(mld->v2);
|
|
|
|
if (v1 >= Level->vertexes.Size() || v2 >= Level->vertexes.Size())
|
|
{
|
|
I_Error ("Line %d has invalid vertices: %d and/or %d.\nThe map only contains %u vertices.", i+skipped, v1, v2, Level->vertexes.Size());
|
|
}
|
|
else if (v1 == v2 ||
|
|
(Level->vertexes[v1].fX() == Level->vertexes[v2].fX() && Level->vertexes[v1].fY() == Level->vertexes[v2].fY()))
|
|
{
|
|
Printf ("Removing 0-length line %d\n", i+skipped);
|
|
memmove (mld, mld+1, sizeof(*mld)*(numlines-i-1));
|
|
ForceNodeBuild = true;
|
|
skipped++;
|
|
numlines--;
|
|
}
|
|
else
|
|
{
|
|
// patch missing first sides instead of crashing out.
|
|
// Visual glitches are better than not being able to play.
|
|
if (LittleShort(mld->sidenum[0]) == NO_INDEX)
|
|
{
|
|
Printf("Line %d has no first side.\n", i);
|
|
mld->sidenum[0] = 0;
|
|
}
|
|
sidecount++;
|
|
if (LittleShort(mld->sidenum[1]) != NO_INDEX)
|
|
sidecount++;
|
|
linemap[i] = i+skipped;
|
|
i++;
|
|
}
|
|
}
|
|
Level->lines.Alloc(numlines);
|
|
memset(&Level->lines[0], 0, numlines * sizeof(line_t));
|
|
|
|
AllocateSideDefs (map, sidecount);
|
|
|
|
mld = (maplinedef_t *)mldf.Data();
|
|
ld = &Level->lines[0];
|
|
for (i = 0; i < numlines; i++, mld++, ld++)
|
|
{
|
|
ld->alpha = 1.; // [RH] Opaque by default
|
|
ld->portalindex = UINT_MAX;
|
|
ld->portaltransferred = UINT_MAX;
|
|
|
|
// [RH] Translate old linedef special and flags to be
|
|
// compatible with the new format.
|
|
|
|
mld->special = LittleShort(mld->special);
|
|
mld->tag = LittleShort(mld->tag);
|
|
mld->flags = LittleShort(mld->flags);
|
|
Level->TranslateLineDef (ld, mld, -1);
|
|
// do not assign the tag for Extradata lines.
|
|
if (ld->special != Static_Init || (ld->args[1] != Init_EDLine && ld->args[1] != Init_EDSector))
|
|
{
|
|
Level->tagManager.AddLineID(i, mld->tag);
|
|
}
|
|
#ifndef NO_EDATA
|
|
if (ld->special == Static_Init && ld->args[1] == Init_EDLine)
|
|
{
|
|
ProcessEDLinedef(ld, mld->tag);
|
|
}
|
|
#endif
|
|
|
|
ld->v1 = &Level->vertexes[LittleShort(mld->v1)];
|
|
ld->v2 = &Level->vertexes[LittleShort(mld->v2)];
|
|
|
|
SetSideNum (&ld->sidedef[0], LittleShort(mld->sidenum[0]));
|
|
SetSideNum (&ld->sidedef[1], LittleShort(mld->sidenum[1]));
|
|
|
|
ld->AdjustLine ();
|
|
SaveLineSpecial (ld);
|
|
if (Level->flags2 & LEVEL2_CLIPMIDTEX) ld->flags |= ML_CLIP_MIDTEX;
|
|
if (Level->flags2 & LEVEL2_WRAPMIDTEX) ld->flags |= ML_WRAP_MIDTEX;
|
|
if (Level->flags2 & LEVEL2_CHECKSWITCHRANGE) ld->flags |= ML_CHECKSWITCHRANGE;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// [RH] Same as P_LoadLineDefs() except it uses Hexen-style LineDefs.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadLineDefs2 (MapData * map)
|
|
{
|
|
int i, skipped;
|
|
line_t *ld;
|
|
int lumplen = map->Size(ML_LINEDEFS);
|
|
maplinedef2_t *mld;
|
|
|
|
int numlines = lumplen / sizeof(maplinedef2_t);
|
|
linemap.Resize(numlines);
|
|
|
|
auto mldf = map->Read(ML_LINEDEFS);
|
|
|
|
// [RH] Remove any lines that have 0 length and count sidedefs used
|
|
for (skipped = sidecount = i = 0; i < numlines; )
|
|
{
|
|
mld = ((maplinedef2_t*)mldf.Data()) + i;
|
|
|
|
if (mld->v1 == mld->v2 ||
|
|
(Level->vertexes[LittleShort(mld->v1)].fX() == Level->vertexes[LittleShort(mld->v2)].fX() &&
|
|
Level->vertexes[LittleShort(mld->v1)].fY() == Level->vertexes[LittleShort(mld->v2)].fY()))
|
|
{
|
|
Printf ("Removing 0-length line %d\n", i+skipped);
|
|
memmove (mld, mld+1, sizeof(*mld)*(numlines-i-1));
|
|
skipped++;
|
|
numlines--;
|
|
}
|
|
else
|
|
{
|
|
// patch missing first sides instead of crashing out.
|
|
// Visual glitches are better than not being able to play.
|
|
if (LittleShort(mld->sidenum[0]) == NO_INDEX)
|
|
{
|
|
Printf("Line %d has no first side.\n", i);
|
|
mld->sidenum[0] = 0;
|
|
}
|
|
sidecount++;
|
|
if (LittleShort(mld->sidenum[1]) != NO_INDEX)
|
|
sidecount++;
|
|
linemap[i] = i+skipped;
|
|
i++;
|
|
}
|
|
}
|
|
if (skipped > 0)
|
|
{
|
|
ForceNodeBuild = true;
|
|
}
|
|
Level->lines.Alloc(numlines);
|
|
memset(&Level->lines[0], 0, numlines * sizeof(line_t));
|
|
|
|
AllocateSideDefs (map, sidecount);
|
|
|
|
mld = (maplinedef2_t *)mldf.Data();
|
|
ld = &Level->lines[0];
|
|
for (i = 0; i < numlines; i++, mld++, ld++)
|
|
{
|
|
int j;
|
|
|
|
ld->portalindex = UINT_MAX;
|
|
ld->portaltransferred = UINT_MAX;
|
|
|
|
for (j = 0; j < 5; j++)
|
|
ld->args[j] = mld->args[j];
|
|
|
|
ld->flags = LittleShort(mld->flags);
|
|
ld->special = mld->special;
|
|
|
|
ld->v1 = &Level->vertexes[LittleShort(mld->v1)];
|
|
ld->v2 = &Level->vertexes[LittleShort(mld->v2)];
|
|
ld->alpha = 1.; // [RH] Opaque by default
|
|
|
|
SetSideNum (&ld->sidedef[0], LittleShort(mld->sidenum[0]));
|
|
SetSideNum (&ld->sidedef[1], LittleShort(mld->sidenum[1]));
|
|
|
|
ld->AdjustLine ();
|
|
SetLineID(i, ld);
|
|
SaveLineSpecial (ld);
|
|
if (Level->flags2 & LEVEL2_CLIPMIDTEX) ld->flags |= ML_CLIP_MIDTEX;
|
|
if (Level->flags2 & LEVEL2_WRAPMIDTEX) ld->flags |= ML_WRAP_MIDTEX;
|
|
if (Level->flags2 & LEVEL2_CHECKSWITCHRANGE) ld->flags |= ML_CHECKSWITCHRANGE;
|
|
|
|
// convert the activation type
|
|
ld->activation = 1 << GET_SPAC(ld->flags);
|
|
if (ld->activation == SPAC_AnyCross)
|
|
{ // this is really PTouch
|
|
ld->activation = SPAC_Impact | SPAC_PCross;
|
|
}
|
|
else if (ld->activation == SPAC_Impact)
|
|
{ // In non-UMDF maps, Impact implies PCross
|
|
ld->activation = SPAC_Impact | SPAC_PCross;
|
|
}
|
|
ld->flags &= ~ML_SPAC_MASK;
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::AllocateSideDefs (MapData *map, int count)
|
|
{
|
|
int i;
|
|
|
|
Level->sides.Alloc(count);
|
|
memset(&Level->sides[0], 0, count * sizeof(side_t));
|
|
|
|
sidetemp.Resize(MAX<int>(count, Level->vertexes.Size()));
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
sidetemp[i].a.special = sidetemp[i].a.tag = 0;
|
|
sidetemp[i].a.alpha = SHRT_MIN;
|
|
sidetemp[i].a.map = NO_SIDE;
|
|
}
|
|
int numsides = map->Size(ML_SIDEDEFS) / sizeof(mapsidedef_t);
|
|
if (count < numsides)
|
|
{
|
|
Printf ("Map has %d unused sidedefs\n", numsides - count);
|
|
}
|
|
numsides = count;
|
|
sidecount = 0;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// [RH] Group sidedefs into loops so that we can easily determine
|
|
// what walls any particular wall neighbors.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoopSidedefs (bool firstloop)
|
|
{
|
|
int i;
|
|
|
|
int numsides = Level->sides.Size();
|
|
sidetemp.Resize(MAX<int>(Level->vertexes.Size(), numsides));
|
|
|
|
for (i = 0; i < (int)Level->vertexes.Size(); ++i)
|
|
{
|
|
sidetemp[i].b.first = NO_SIDE;
|
|
sidetemp[i].b.next = NO_SIDE;
|
|
}
|
|
for (; i < numsides; ++i)
|
|
{
|
|
sidetemp[i].b.next = NO_SIDE;
|
|
}
|
|
|
|
for (i = 0; i < numsides; ++i)
|
|
{
|
|
// For each vertex, build a list of sidedefs that use that vertex
|
|
// as their left edge.
|
|
line_t *line = Level->sides[i].linedef;
|
|
int lineside = (line->sidedef[0] != &Level->sides[i]);
|
|
int vert = lineside ? Index(line->v2) : Index(line->v1);
|
|
|
|
sidetemp[i].b.lineside = lineside;
|
|
sidetemp[i].b.next = sidetemp[vert].b.first;
|
|
sidetemp[vert].b.first = i;
|
|
|
|
// Set each side so that it is the only member of its loop
|
|
Level->sides[i].LeftSide = NO_SIDE;
|
|
Level->sides[i].RightSide = NO_SIDE;
|
|
}
|
|
|
|
// For each side, find the side that is to its right and set the
|
|
// loop pointers accordingly. If two sides share a left vertex, the
|
|
// one that forms the smallest angle is assumed to be the right one.
|
|
for (i = 0; i < numsides; ++i)
|
|
{
|
|
uint32_t right;
|
|
line_t *line = Level->sides[i].linedef;
|
|
|
|
// If the side's line only exists in a single sector,
|
|
// then consider that line to be a self-contained loop
|
|
// instead of as part of another loop
|
|
if (line->frontsector == line->backsector)
|
|
{
|
|
side_t* rightside = line->sidedef[!sidetemp[i].b.lineside];
|
|
|
|
if (nullptr == rightside)
|
|
{
|
|
// There is no right side!
|
|
if (firstloop) Printf ("Line %d's right edge is unconnected\n", linemap[Index(line)]);
|
|
continue;
|
|
}
|
|
|
|
right = Index(rightside);
|
|
}
|
|
else
|
|
{
|
|
if (sidetemp[i].b.lineside)
|
|
{
|
|
right = Index(line->v1);
|
|
}
|
|
else
|
|
{
|
|
right = Index(line->v2);
|
|
}
|
|
|
|
right = sidetemp[right].b.first;
|
|
|
|
if (right == NO_SIDE)
|
|
{
|
|
// There is no right side!
|
|
if (firstloop) Printf ("Line %d's right edge is unconnected\n", linemap[Index(line)]);
|
|
continue;
|
|
}
|
|
|
|
if (sidetemp[right].b.next != NO_SIDE)
|
|
{
|
|
int bestright = right; // Shut up, GCC
|
|
DAngle bestang = 360.;
|
|
line_t *leftline, *rightline;
|
|
DAngle ang1, ang2, ang;
|
|
|
|
leftline = Level->sides[i].linedef;
|
|
ang1 = leftline->Delta().Angle();
|
|
if (!sidetemp[i].b.lineside)
|
|
{
|
|
ang1 += 180;
|
|
}
|
|
|
|
while (right != NO_SIDE)
|
|
{
|
|
if (Level->sides[right].LeftSide == NO_SIDE)
|
|
{
|
|
rightline = Level->sides[right].linedef;
|
|
if (rightline->frontsector != rightline->backsector)
|
|
{
|
|
ang2 = rightline->Delta().Angle();
|
|
if (sidetemp[right].b.lineside)
|
|
{
|
|
ang2 += 180;
|
|
}
|
|
|
|
ang = (ang2 - ang1).Normalized360();
|
|
|
|
if (ang != 0 && ang <= bestang)
|
|
{
|
|
bestright = right;
|
|
bestang = ang;
|
|
}
|
|
}
|
|
}
|
|
right = sidetemp[right].b.next;
|
|
}
|
|
right = bestright;
|
|
}
|
|
}
|
|
assert((unsigned)i<(unsigned)numsides);
|
|
assert(right<(unsigned)numsides);
|
|
Level->sides[i].RightSide = right;
|
|
Level->sides[right].LeftSide = i;
|
|
}
|
|
|
|
// We keep the sidedef init info around until after polyobjects are initialized,
|
|
// so don't delete just yet.
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
int MapLoader::DetermineTranslucency (int lumpnum)
|
|
{
|
|
auto tranmap = Wads.OpenLumpReader (lumpnum);
|
|
uint8_t index;
|
|
PalEntry newcolor;
|
|
PalEntry newcolor2;
|
|
|
|
tranmap.Seek (GPalette.BlackIndex * 256 + GPalette.WhiteIndex, FileReader::SeekSet);
|
|
tranmap.Read (&index, 1);
|
|
|
|
newcolor = GPalette.BaseColors[GPalette.Remap[index]];
|
|
|
|
tranmap.Seek (GPalette.WhiteIndex * 256 + GPalette.BlackIndex, FileReader::SeekSet);
|
|
tranmap.Read (&index, 1);
|
|
newcolor2 = GPalette.BaseColors[GPalette.Remap[index]];
|
|
if (newcolor2.r == 255) // if black on white results in white it's either
|
|
// fully transparent or additive
|
|
{
|
|
if (developer >= DMSG_NOTIFY)
|
|
{
|
|
char lumpname[9];
|
|
lumpname[8] = 0;
|
|
Wads.GetLumpName (lumpname, lumpnum);
|
|
Printf ("%s appears to be additive translucency %d (%d%%)\n", lumpname, newcolor.r,
|
|
newcolor.r*100/255);
|
|
}
|
|
return -newcolor.r;
|
|
}
|
|
|
|
if (developer >= DMSG_NOTIFY)
|
|
{
|
|
char lumpname[9];
|
|
lumpname[8] = 0;
|
|
Wads.GetLumpName (lumpname, lumpnum);
|
|
Printf ("%s appears to be translucency %d (%d%%)\n", lumpname, newcolor.r,
|
|
newcolor.r*100/255);
|
|
}
|
|
return newcolor.r;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::ProcessSideTextures(bool checktranmap, side_t *sd, sector_t *sec, intmapsidedef_t *msd, int special, int tag, short *alpha, FMissingTextureTracker &missingtex)
|
|
{
|
|
switch (special)
|
|
{
|
|
case Transfer_Heights: // variable colormap via 242 linedef
|
|
// [RH] The colormap num we get here isn't really a colormap,
|
|
// but a packed ARGB word for blending, so we also allow
|
|
// the blend to be specified directly by the texture names
|
|
// instead of figuring something out from the colormap.
|
|
if (sec != nullptr)
|
|
{
|
|
SetTexture (sd, side_t::bottom, &sec->bottommap, msd->bottomtexture);
|
|
SetTexture (sd, side_t::mid, &sec->midmap, msd->midtexture);
|
|
SetTexture (sd, side_t::top, &sec->topmap, msd->toptexture);
|
|
}
|
|
break;
|
|
|
|
case Static_Init:
|
|
// [RH] Set sector color and fog
|
|
// upper "texture" is light color
|
|
// lower "texture" is fog color
|
|
{
|
|
uint32_t color = MAKERGB(255,255,255), fog = 0;
|
|
bool colorgood, foggood;
|
|
|
|
SetTextureNoErr (sd, side_t::bottom, &fog, msd->bottomtexture, &foggood, true);
|
|
SetTextureNoErr (sd, side_t::top, &color, msd->toptexture, &colorgood, false);
|
|
SetTexture(sd, side_t::mid, msd->midtexture, missingtex);
|
|
|
|
if (colorgood | foggood)
|
|
{
|
|
for (unsigned s = 0; s < Level->sectors.Size(); s++)
|
|
{
|
|
if (Level->SectorHasTag(s, tag))
|
|
{
|
|
if (colorgood)
|
|
{
|
|
Level->sectors[s].Colormap.LightColor.SetRGB(color);
|
|
Level->sectors[s].Colormap.BlendFactor = APART(color);
|
|
}
|
|
if (foggood) Level->sectors[s].Colormap.FadeColor.SetRGB(fog);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Sector_Set3DFloor:
|
|
if (msd->toptexture[0]=='#')
|
|
{
|
|
sd->SetTexture(side_t::top, FNullTextureID() +(int)(-strtoll(&msd->toptexture[1], nullptr, 10))); // store the alpha as a negative texture index
|
|
// This will be sorted out by the 3D-floor code later.
|
|
}
|
|
else
|
|
{
|
|
SetTexture(sd, side_t::top, msd->toptexture, missingtex);
|
|
}
|
|
|
|
SetTexture(sd, side_t::mid, msd->midtexture, missingtex);
|
|
SetTexture(sd, side_t::bottom, msd->bottomtexture, missingtex);
|
|
break;
|
|
|
|
case TranslucentLine: // killough 4/11/98: apply translucency to 2s normal texture
|
|
if (checktranmap)
|
|
{
|
|
int lumpnum;
|
|
|
|
if (strnicmp ("TRANMAP", msd->midtexture, 8) == 0)
|
|
{
|
|
// The translator set the alpha argument already; no reason to do it again.
|
|
sd->SetTexture(side_t::mid, FNullTextureID());
|
|
}
|
|
else if ((lumpnum = Wads.CheckNumForName (msd->midtexture)) > 0 &&
|
|
Wads.LumpLength (lumpnum) == 65536)
|
|
{
|
|
*alpha = (short)DetermineTranslucency (lumpnum);
|
|
sd->SetTexture(side_t::mid, FNullTextureID());
|
|
}
|
|
else
|
|
{
|
|
SetTexture(sd, side_t::mid, msd->midtexture, missingtex);
|
|
}
|
|
|
|
SetTexture(sd, side_t::top, msd->toptexture, missingtex);
|
|
SetTexture(sd, side_t::bottom, msd->bottomtexture, missingtex);
|
|
break;
|
|
}
|
|
// Fallthrough for Hexen maps is intentional
|
|
|
|
default: // normal cases
|
|
|
|
SetTexture(sd, side_t::mid, msd->midtexture, missingtex);
|
|
SetTexture(sd, side_t::top, msd->toptexture, missingtex);
|
|
SetTexture(sd, side_t::bottom, msd->bottomtexture, missingtex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// killough 4/4/98: delay using texture names until
|
|
// after linedefs are loaded, to allow overloading.
|
|
// killough 5/3/98: reformatted, cleaned up
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadSideDefs2 (MapData *map, FMissingTextureTracker &missingtex)
|
|
{
|
|
auto msdf = map->Read(ML_SIDEDEFS);
|
|
|
|
for (unsigned i = 0; i < Level->sides.Size(); i++)
|
|
{
|
|
mapsidedef_t *msd = ((mapsidedef_t*)msdf.Data()) + sidetemp[i].a.map;
|
|
side_t *sd = &Level->sides[i];
|
|
sector_t *sec;
|
|
|
|
// [RH] The Doom renderer ignored the patch y locations when
|
|
// drawing mid textures. ZDoom does not, so fix the laser beams in Strife.
|
|
if (gameinfo.gametype == GAME_Strife &&
|
|
strncmp (msd->midtexture, "LASERB01", 8) == 0)
|
|
{
|
|
msd->rowoffset += 102;
|
|
}
|
|
|
|
sd->SetTextureXOffset(LittleShort(msd->textureoffset));
|
|
sd->SetTextureYOffset(LittleShort(msd->rowoffset));
|
|
sd->SetTextureXScale(1.);
|
|
sd->SetTextureYScale(1.);
|
|
sd->linedef = nullptr;
|
|
sd->Flags = 0;
|
|
sd->UDMFIndex = i;
|
|
|
|
// killough 4/4/98: allow sidedef texture names to be overloaded
|
|
// killough 4/11/98: refined to allow colormaps to work as wall
|
|
// textures if invalid as colormaps but valid as textures.
|
|
|
|
if ((unsigned)LittleShort(msd->sector)>=Level->sectors.Size())
|
|
{
|
|
Printf (PRINT_HIGH, "Sidedef %d has a bad sector\n", i);
|
|
sd->sector = sec = nullptr;
|
|
}
|
|
else
|
|
{
|
|
sd->sector = sec = &Level->sectors[LittleShort(msd->sector)];
|
|
}
|
|
|
|
intmapsidedef_t imsd;
|
|
imsd.toptexture.CopyCStrPart(msd->toptexture, 8);
|
|
imsd.midtexture.CopyCStrPart(msd->midtexture, 8);
|
|
imsd.bottomtexture.CopyCStrPart(msd->bottomtexture, 8);
|
|
|
|
ProcessSideTextures(!map->HasBehavior, sd, sec, &imsd,
|
|
sidetemp[i].a.special, sidetemp[i].a.tag, &sidetemp[i].a.alpha, missingtex);
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// [RH] My own blockmap builder, not Killough's or TeamTNT's.
|
|
//
|
|
// Killough's turned out not to be correct enough, and I had
|
|
// written this for ZDBSP before I discovered that, so
|
|
// replacing the one he wrote for MBF seemed like the easiest
|
|
// thing to do. (Doom E3M6, near vertex 0--the one furthest east
|
|
// on the map--had problems.)
|
|
//
|
|
// Using a hash table to get the minimum possible blockmap size
|
|
// seems like overkill, but I wanted to change the code as little
|
|
// as possible from its ZDBSP incarnation.
|
|
//
|
|
//===========================================================================
|
|
|
|
static unsigned int BlockHash (TArray<int> *block)
|
|
{
|
|
int hash = 0;
|
|
int *ar = &(*block)[0];
|
|
for (size_t i = 0; i < block->Size(); ++i)
|
|
{
|
|
hash = hash * 12235 + ar[i];
|
|
}
|
|
return hash & 0x7fffffff;
|
|
}
|
|
|
|
static bool BlockCompare (TArray<int> *block1, TArray<int> *block2)
|
|
{
|
|
size_t size = block1->Size();
|
|
|
|
if (size != block2->Size())
|
|
{
|
|
return false;
|
|
}
|
|
if (size == 0)
|
|
{
|
|
return true;
|
|
}
|
|
int *ar1 = &(*block1)[0];
|
|
int *ar2 = &(*block2)[0];
|
|
for (size_t i = 0; i < size; ++i)
|
|
{
|
|
if (ar1[i] != ar2[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void CreatePackedBlockmap (TArray<int> &BlockMap, TArray<int> *blocks, int bmapwidth, int bmapheight)
|
|
{
|
|
int buckets[4096];
|
|
int hashblock;
|
|
TArray<int> *block;
|
|
int zero = 0;
|
|
int terminator = -1;
|
|
int *array;
|
|
int i, hash;
|
|
int hashed = 0, nothashed = 0;
|
|
|
|
TArray<int> hashes(bmapwidth * bmapheight, true);
|
|
|
|
memset (hashes.Data(), 0xff, sizeof(int)*bmapwidth*bmapheight);
|
|
memset (buckets, 0xff, sizeof(buckets));
|
|
|
|
for (i = 0; i < bmapwidth * bmapheight; ++i)
|
|
{
|
|
block = &blocks[i];
|
|
hash = BlockHash (block) % 4096;
|
|
hashblock = buckets[hash];
|
|
while (hashblock != -1)
|
|
{
|
|
if (BlockCompare (block, &blocks[hashblock]))
|
|
{
|
|
break;
|
|
}
|
|
hashblock = hashes[hashblock];
|
|
}
|
|
if (hashblock != -1)
|
|
{
|
|
BlockMap[4+i] = BlockMap[4+hashblock];
|
|
hashed++;
|
|
}
|
|
else
|
|
{
|
|
hashes[i] = buckets[hash];
|
|
buckets[hash] = i;
|
|
BlockMap[4+i] = BlockMap.Size ();
|
|
BlockMap.Push (zero);
|
|
array = &(*block)[0];
|
|
for (size_t j = 0; j < block->Size(); ++j)
|
|
{
|
|
BlockMap.Push (array[j]);
|
|
}
|
|
BlockMap.Push (terminator);
|
|
nothashed++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MapLoader::CreateBlockMap ()
|
|
{
|
|
enum
|
|
{
|
|
BLOCKBITS = 7,
|
|
BLOCKSIZE = 128
|
|
};
|
|
|
|
TArray<int> *block, *endblock;
|
|
TArray<TArray<int>> BlockLists;
|
|
int adder;
|
|
int bmapwidth, bmapheight;
|
|
double dminx, dmaxx, dminy, dmaxy;
|
|
int minx, maxx, miny, maxy;
|
|
int line;
|
|
|
|
if (Level->vertexes.Size() == 0)
|
|
return;
|
|
|
|
// Find map extents for the blockmap
|
|
dminx = dmaxx = Level->vertexes[0].fX();
|
|
dminy = dmaxy = Level->vertexes[0].fY();
|
|
|
|
for (auto &vert : Level->vertexes)
|
|
{
|
|
if (vert.fX() < dminx) dminx = vert.fX();
|
|
else if (vert.fX() > dmaxx) dmaxx = vert.fX();
|
|
if (vert.fY() < dminy) dminy = vert.fY();
|
|
else if (vert.fY() > dmaxy) dmaxy = vert.fY();
|
|
}
|
|
|
|
minx = int(dminx);
|
|
miny = int(dminy);
|
|
maxx = int(dmaxx);
|
|
maxy = int(dmaxy);
|
|
|
|
bmapwidth = ((maxx - minx) >> BLOCKBITS) + 1;
|
|
bmapheight = ((maxy - miny) >> BLOCKBITS) + 1;
|
|
|
|
TArray<int> BlockMap (bmapwidth * bmapheight * 3 + 4);
|
|
|
|
adder = minx; BlockMap.Push (adder);
|
|
adder = miny; BlockMap.Push (adder);
|
|
adder = bmapwidth; BlockMap.Push (adder);
|
|
adder = bmapheight; BlockMap.Push (adder);
|
|
|
|
BlockLists.Resize(bmapwidth * bmapheight);
|
|
|
|
for (line = 0; line < (int)Level->lines.Size(); ++line)
|
|
{
|
|
int x1 = int(Level->lines[line].v1->fX());
|
|
int y1 = int(Level->lines[line].v1->fY());
|
|
int x2 = int(Level->lines[line].v2->fX());
|
|
int y2 = int(Level->lines[line].v2->fY());
|
|
int dx = x2 - x1;
|
|
int dy = y2 - y1;
|
|
int bx = (x1 - minx) >> BLOCKBITS;
|
|
int by = (y1 - miny) >> BLOCKBITS;
|
|
int bx2 = (x2 - minx) >> BLOCKBITS;
|
|
int by2 = (y2 - miny) >> BLOCKBITS;
|
|
|
|
block = &BlockLists[bx + by * bmapwidth];
|
|
endblock = &BlockLists[bx2 + by2 * bmapwidth];
|
|
|
|
if (block == endblock) // Single block
|
|
{
|
|
block->Push (line);
|
|
}
|
|
else if (by == by2) // Horizontal line
|
|
{
|
|
if (bx > bx2)
|
|
{
|
|
swapvalues (block, endblock);
|
|
}
|
|
do
|
|
{
|
|
block->Push (line);
|
|
block += 1;
|
|
} while (block <= endblock);
|
|
}
|
|
else if (bx == bx2) // Vertical line
|
|
{
|
|
if (by > by2)
|
|
{
|
|
swapvalues (block, endblock);
|
|
}
|
|
do
|
|
{
|
|
block->Push (line);
|
|
block += bmapwidth;
|
|
} while (block <= endblock);
|
|
}
|
|
else // Diagonal line
|
|
{
|
|
int xchange = (dx < 0) ? -1 : 1;
|
|
int ychange = (dy < 0) ? -1 : 1;
|
|
int ymove = ychange * bmapwidth;
|
|
int adx = abs (dx);
|
|
int ady = abs (dy);
|
|
|
|
if (adx == ady) // 45 degrees
|
|
{
|
|
int xb = (x1 - minx) & (BLOCKSIZE-1);
|
|
int yb = (y1 - miny) & (BLOCKSIZE-1);
|
|
if (dx < 0)
|
|
{
|
|
xb = BLOCKSIZE-xb;
|
|
}
|
|
if (dy < 0)
|
|
{
|
|
yb = BLOCKSIZE-yb;
|
|
}
|
|
if (xb < yb)
|
|
adx--;
|
|
}
|
|
if (adx >= ady) // X-major
|
|
{
|
|
int yadd = dy < 0 ? -1 : BLOCKSIZE;
|
|
do
|
|
{
|
|
int stop = (Scale ((by << BLOCKBITS) + yadd - (y1 - miny), dx, dy) + (x1 - minx)) >> BLOCKBITS;
|
|
while (bx != stop)
|
|
{
|
|
block->Push (line);
|
|
block += xchange;
|
|
bx += xchange;
|
|
}
|
|
block->Push (line);
|
|
block += ymove;
|
|
by += ychange;
|
|
} while (by != by2);
|
|
while (block != endblock)
|
|
{
|
|
block->Push (line);
|
|
block += xchange;
|
|
}
|
|
block->Push (line);
|
|
}
|
|
else // Y-major
|
|
{
|
|
int xadd = dx < 0 ? -1 : BLOCKSIZE;
|
|
do
|
|
{
|
|
int stop = (Scale ((bx << BLOCKBITS) + xadd - (x1 - minx), dy, dx) + (y1 - miny)) >> BLOCKBITS;
|
|
while (by != stop)
|
|
{
|
|
block->Push (line);
|
|
block += ymove;
|
|
by += ychange;
|
|
}
|
|
block->Push (line);
|
|
block += xchange;
|
|
bx += xchange;
|
|
} while (bx != bx2);
|
|
while (block != endblock)
|
|
{
|
|
block->Push (line);
|
|
block += ymove;
|
|
}
|
|
block->Push (line);
|
|
}
|
|
}
|
|
}
|
|
|
|
BlockMap.Reserve (bmapwidth * bmapheight);
|
|
CreatePackedBlockmap (BlockMap, BlockLists.Data(), bmapwidth, bmapheight);
|
|
|
|
Level->blockmap.blockmaplump = new int[BlockMap.Size()];
|
|
for (unsigned int ii = 0; ii < BlockMap.Size(); ++ii)
|
|
{
|
|
Level->blockmap.blockmaplump[ii] = BlockMap[ii];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_VerifyBlockMap
|
|
//
|
|
// haleyjd 03/04/10: do verification on validity of blockmap.
|
|
//
|
|
//===========================================================================
|
|
|
|
bool FBlockmap::VerifyBlockMap(int count, unsigned numlines)
|
|
{
|
|
int x, y;
|
|
int *maxoffs = blockmaplump + count;
|
|
|
|
int bmapwidth = blockmaplump[2];
|
|
int bmapheight = blockmaplump[3];
|
|
|
|
for(y = 0; y < bmapheight; y++)
|
|
{
|
|
for(x = 0; x < bmapwidth; x++)
|
|
{
|
|
int offset;
|
|
int *list, *tmplist;
|
|
int *blockoffset;
|
|
|
|
offset = y * bmapwidth + x;
|
|
blockoffset = blockmaplump + offset + 4;
|
|
|
|
|
|
// check that block offset is in bounds
|
|
if(blockoffset >= maxoffs)
|
|
{
|
|
Printf(PRINT_HIGH, "VerifyBlockMap: block offset overflow\n");
|
|
return false;
|
|
}
|
|
|
|
offset = *blockoffset;
|
|
|
|
// check that list offset is in bounds
|
|
if(offset < 4 || offset >= count)
|
|
{
|
|
Printf(PRINT_HIGH, "VerifyBlockMap: list offset overflow\n");
|
|
return false;
|
|
}
|
|
|
|
list = blockmaplump + offset;
|
|
|
|
// scan forward for a -1 terminator before maxoffs
|
|
for(tmplist = list; ; tmplist++)
|
|
{
|
|
// we have overflowed the lump?
|
|
if(tmplist >= maxoffs)
|
|
{
|
|
Printf(PRINT_HIGH, "VerifyBlockMap: open blocklist\n");
|
|
return false;
|
|
}
|
|
if(*tmplist == -1) // found -1
|
|
break;
|
|
}
|
|
|
|
// there's some node builder which carelessly removed the initial 0-entry.
|
|
// Rather than second-guessing the intent, let's just discard such blockmaps entirely
|
|
// to be on the safe side.
|
|
if (*list != 0)
|
|
{
|
|
Printf(PRINT_HIGH, "VerifyBlockMap: first entry is not 0.\n");
|
|
return false;
|
|
}
|
|
|
|
// scan the list for out-of-range linedef indicies in list
|
|
for(tmplist = list; *tmplist != -1; tmplist++)
|
|
{
|
|
if((unsigned)*tmplist >= numlines)
|
|
{
|
|
Printf(PRINT_HIGH, "VerifyBlockMap: index >= numlines\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_LoadBlockMap
|
|
//
|
|
// killough 3/1/98: substantially modified to work
|
|
// towards removing blockmap limit (a wad limitation)
|
|
//
|
|
// killough 3/30/98: Rewritten to remove blockmap limit
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadBlockMap (MapData * map)
|
|
{
|
|
int count = map->Size(ML_BLOCKMAP);
|
|
|
|
if (ForceNodeBuild || genblockmap ||
|
|
count/2 >= 0x10000 || count == 0 ||
|
|
Args->CheckParm("-blockmap")
|
|
)
|
|
{
|
|
DPrintf (DMSG_SPAMMY, "Generating BLOCKMAP\n");
|
|
CreateBlockMap ();
|
|
}
|
|
else
|
|
{
|
|
auto data = map->Read(ML_BLOCKMAP);
|
|
const short *wadblockmaplump = (short *)data.Data();
|
|
int i;
|
|
|
|
count/=2;
|
|
Level->blockmap.blockmaplump = new int[count];
|
|
|
|
// killough 3/1/98: Expand wad blockmap into larger internal one,
|
|
// by treating all offsets except -1 as unsigned and zero-extending
|
|
// them. This potentially doubles the size of blockmaps allowed,
|
|
// because Doom originally considered the offsets as always signed.
|
|
|
|
Level->blockmap.blockmaplump[0] = LittleShort(wadblockmaplump[0]);
|
|
Level->blockmap.blockmaplump[1] = LittleShort(wadblockmaplump[1]);
|
|
Level->blockmap.blockmaplump[2] = (uint32_t)(LittleShort(wadblockmaplump[2])) & 0xffff;
|
|
Level->blockmap.blockmaplump[3] = (uint32_t)(LittleShort(wadblockmaplump[3])) & 0xffff;
|
|
|
|
for (i = 4; i < count; i++)
|
|
{
|
|
short t = LittleShort(wadblockmaplump[i]); // killough 3/1/98
|
|
Level->blockmap.blockmaplump[i] = t == -1 ? (uint32_t)0xffffffff : (uint32_t) t & 0xffff;
|
|
}
|
|
|
|
if (!Level->blockmap.VerifyBlockMap(count, Level->lines.Size()))
|
|
{
|
|
DPrintf (DMSG_SPAMMY, "Generating BLOCKMAP\n");
|
|
CreateBlockMap();
|
|
}
|
|
|
|
}
|
|
|
|
Level->blockmap.bmaporgx = Level->blockmap.blockmaplump[0];
|
|
Level->blockmap.bmaporgy = Level->blockmap.blockmaplump[1];
|
|
Level->blockmap.bmapwidth = Level->blockmap.blockmaplump[2];
|
|
Level->blockmap.bmapheight = Level->blockmap.blockmaplump[3];
|
|
|
|
// clear out mobj chains
|
|
count = Level->blockmap.bmapwidth*Level->blockmap.bmapheight;
|
|
Level->blockmap.blocklinks = new FBlockNode *[count];
|
|
memset (Level->blockmap.blocklinks, 0, count*sizeof(*Level->blockmap.blocklinks));
|
|
Level->blockmap.blockmap = Level->blockmap.blockmaplump+4;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// P_GroupLines
|
|
// Builds sector line lists and subsector sector numbers.
|
|
// Finds block bounding boxes for sectors.
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::GroupLines (bool buildmap)
|
|
{
|
|
int total;
|
|
sector_t* sector;
|
|
FBoundingBox bbox;
|
|
bool flaggedNoFronts = false;
|
|
unsigned int jj;
|
|
|
|
// look up sector number for each subsector
|
|
for (auto &sub : Level->subsectors)
|
|
{
|
|
sub.sector = sub.firstline->sidedef->sector;
|
|
}
|
|
for (auto &sub : Level->subsectors)
|
|
{
|
|
for (jj = 0; jj < sub.numlines; ++jj)
|
|
{
|
|
sub.firstline[jj].Subsector = ⊂
|
|
}
|
|
}
|
|
|
|
// count number of lines in each sector
|
|
total = 0;
|
|
for (unsigned i = 0; i < Level->lines.Size(); i++)
|
|
{
|
|
auto li = &Level->lines[i];
|
|
if (li->frontsector == nullptr)
|
|
{
|
|
if (!flaggedNoFronts)
|
|
{
|
|
flaggedNoFronts = true;
|
|
Printf ("The following lines do not have a front sidedef:\n");
|
|
}
|
|
Printf (" %d\n", i);
|
|
}
|
|
else
|
|
{
|
|
li->frontsector->Lines.Count++;
|
|
total++;
|
|
}
|
|
|
|
if (li->backsector && li->backsector != li->frontsector)
|
|
{
|
|
li->backsector->Lines.Count++;
|
|
total++;
|
|
}
|
|
}
|
|
if (flaggedNoFronts)
|
|
{
|
|
I_Error ("You need to fix these lines to play this map.\n");
|
|
}
|
|
|
|
// build line tables for each sector
|
|
Level->linebuffer.Alloc(total);
|
|
line_t **lineb_p = &Level->linebuffer[0];
|
|
auto numsectors = Level->sectors.Size();
|
|
TArray<unsigned> linesDoneInEachSector(numsectors, true);
|
|
memset (linesDoneInEachSector.Data(), 0, sizeof(int)*numsectors);
|
|
|
|
sector = &Level->sectors[0];
|
|
for (unsigned i = 0; i < numsectors; i++, sector++)
|
|
{
|
|
if (sector->Lines.Count == 0)
|
|
{
|
|
Printf ("Sector %i (tag %i) has no lines\n", i, Level->GetFirstSectorTag(Index(sector)));
|
|
// 0 the sector's tag so that no specials can use it
|
|
Level->tagManager.RemoveSectorTags(i);
|
|
}
|
|
else
|
|
{
|
|
sector->Lines.Array = lineb_p;
|
|
lineb_p += sector->Lines.Count;
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < Level->lines.Size(); i++)
|
|
{
|
|
auto li = &Level->lines[i];
|
|
if (li->frontsector != nullptr)
|
|
{
|
|
li->frontsector->Lines[linesDoneInEachSector[Index(li->frontsector)]++] = li;
|
|
}
|
|
if (li->backsector != nullptr && li->backsector != li->frontsector)
|
|
{
|
|
li->backsector->Lines[linesDoneInEachSector[Index(li->backsector)]++] = li;
|
|
}
|
|
}
|
|
|
|
sector = &Level->sectors[0];
|
|
for (unsigned i = 0; i < numsectors; ++i, ++sector)
|
|
{
|
|
if (linesDoneInEachSector[i] != sector->Lines.Size())
|
|
{
|
|
I_Error("P_GroupLines: miscounted");
|
|
}
|
|
if (sector->Lines.Size() > 3)
|
|
{
|
|
bbox.ClearBox();
|
|
for (auto li : sector->Lines)
|
|
{
|
|
bbox.AddToBox(li->v1->fPos());
|
|
bbox.AddToBox(li->v2->fPos());
|
|
}
|
|
|
|
// set the center to the middle of the bounding box
|
|
sector->centerspot.X = (bbox.Right() + bbox.Left()) / 2;
|
|
sector->centerspot.Y = (bbox.Top() + bbox.Bottom()) / 2;
|
|
}
|
|
else if (sector->Lines.Size() > 0)
|
|
{
|
|
// For triangular sectors the above does not calculate good points unless the longest of the triangle's lines is perfectly horizontal and vertical
|
|
DVector2 pos = { 0,0 };
|
|
for (auto ln : sector->Lines)
|
|
{
|
|
pos += ln->v1->fPos() + ln->v2->fPos();
|
|
}
|
|
sector->centerspot = pos / (2 * sector->Lines.Size());
|
|
}
|
|
}
|
|
|
|
// killough 1/30/98: Create xref tables for tags
|
|
Level->tagManager.HashTags();
|
|
|
|
if (!buildmap)
|
|
{
|
|
SetSlopes ();
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadReject (MapData * map, bool junk)
|
|
{
|
|
const int neededsize = (Level->sectors.Size() * Level->sectors.Size() + 7) >> 3;
|
|
int rejectsize;
|
|
|
|
if (!map->CheckName(ML_REJECT, "REJECT"))
|
|
{
|
|
rejectsize = 0;
|
|
}
|
|
else
|
|
{
|
|
rejectsize = junk ? 0 : map->Size(ML_REJECT);
|
|
}
|
|
|
|
if (rejectsize < neededsize)
|
|
{
|
|
if (rejectsize > 0)
|
|
{
|
|
Printf ("REJECT is %d byte%s too small.\n", neededsize - rejectsize,
|
|
neededsize-rejectsize==1?"":"s");
|
|
}
|
|
Level->rejectmatrix.Reset();
|
|
}
|
|
else
|
|
{
|
|
// Check if the reject has some actual content. If not, free it.
|
|
rejectsize = MIN (rejectsize, neededsize);
|
|
Level->rejectmatrix.Alloc(rejectsize);
|
|
|
|
map->Read (ML_REJECT, &Level->rejectmatrix[0], rejectsize);
|
|
|
|
int qwords = rejectsize / 8;
|
|
int i;
|
|
|
|
if (qwords > 0)
|
|
{
|
|
const uint64_t *qreject = (const uint64_t *)&Level->rejectmatrix[0];
|
|
|
|
i = 0;
|
|
do
|
|
{
|
|
if (qreject[i] != 0)
|
|
return;
|
|
} while (++i < qwords);
|
|
}
|
|
rejectsize &= 7;
|
|
qwords *= 8;
|
|
for (i = 0; i < rejectsize; ++i)
|
|
{
|
|
if (Level->rejectmatrix[qwords + i] != 0)
|
|
return;
|
|
}
|
|
|
|
// Reject has no data, so pretend it isn't there.
|
|
Level->rejectmatrix.Reset();
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::LoadBehavior(MapData * map)
|
|
{
|
|
if (map->Size(ML_BEHAVIOR) > 0)
|
|
{
|
|
Level->Behaviors.LoadModule(-1, &map->Reader(ML_BEHAVIOR), map->Size(ML_BEHAVIOR), map->lumpnum);
|
|
}
|
|
if (!Level->Behaviors.CheckAllGood())
|
|
{
|
|
Printf("ACS scripts unloaded.\n");
|
|
Level->Behaviors.UnloadModules();
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void MapLoader::GetPolySpots (MapData * map, TArray<FNodeBuilder::FPolyStart> &spots, TArray<FNodeBuilder::FPolyStart> &anchors)
|
|
{
|
|
//if (map->HasBehavior)
|
|
{
|
|
for (unsigned int i = 0; i < MapThingsConverted.Size(); ++i)
|
|
{
|
|
FDoomEdEntry *mentry = MapThingsConverted[i].info;
|
|
if (mentry != nullptr && mentry->Type == nullptr && mentry->Special >= SMT_PolyAnchor && mentry->Special <= SMT_PolySpawnHurt)
|
|
{
|
|
FNodeBuilder::FPolyStart newvert;
|
|
newvert.x = FLOAT2FIXED(MapThingsConverted[i].pos.X);
|
|
newvert.y = FLOAT2FIXED(MapThingsConverted[i].pos.Y);
|
|
newvert.polynum = MapThingsConverted[i].angle;
|
|
if (mentry->Special == SMT_PolyAnchor)
|
|
{
|
|
anchors.Push (newvert);
|
|
}
|
|
else
|
|
{
|
|
spots.Push (newvert);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
void MapLoader::CalcIndices()
|
|
{
|
|
// sectornums were already initialized because some init code needs them.
|
|
for (unsigned int i = 0; i < Level->vertexes.Size(); ++i)
|
|
{
|
|
Level->vertexes[i].vertexnum = i;
|
|
}
|
|
for (unsigned int i = 0; i < Level->lines.Size(); ++i)
|
|
{
|
|
Level->lines[i].linenum = i;
|
|
}
|
|
for (unsigned int i = 0; i < Level->sides.Size(); ++i)
|
|
{
|
|
Level->sides[i].sidenum = i;
|
|
}
|
|
for (unsigned int i = 0; i < Level->segs.Size(); ++i)
|
|
{
|
|
Level->segs[i].segnum = i;
|
|
}
|
|
for (unsigned int i = 0; i < Level->subsectors.Size(); ++i)
|
|
{
|
|
Level->subsectors[i].subsectornum = i;
|
|
}
|
|
for (unsigned int i = 0; i < Level->nodes.Size(); ++i)
|
|
{
|
|
Level->nodes[i].nodenum = i;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
void MapLoader::LoadLevel(MapData *map, const char *lumpname, int position)
|
|
{
|
|
const int *oldvertextable = nullptr;
|
|
|
|
// note: most of this ordering is important
|
|
ForceNodeBuild = gennodes;
|
|
|
|
// [RH] Load in the BEHAVIOR lump
|
|
if (map->HasBehavior)
|
|
{
|
|
LoadBehavior(map);
|
|
Level->maptype = MAPTYPE_HEXEN;
|
|
}
|
|
else
|
|
{
|
|
// We need translators only for Doom format maps.
|
|
const char *translator;
|
|
|
|
if (!Level->info->Translator.IsEmpty())
|
|
{
|
|
// The map defines its own translator.
|
|
translator = Level->info->Translator.GetChars();
|
|
}
|
|
else
|
|
{
|
|
// Has the user overridden the game's default translator with a commandline parameter?
|
|
translator = Args->CheckValue("-xlat");
|
|
if (translator == nullptr)
|
|
{
|
|
// Use the game's default.
|
|
translator = gameinfo.translator.GetChars();
|
|
}
|
|
}
|
|
Level->Translator = P_LoadTranslator(translator);
|
|
Level->maptype = MAPTYPE_DOOM;
|
|
}
|
|
if (map->isText)
|
|
{
|
|
Level->maptype = MAPTYPE_UDMF;
|
|
}
|
|
FName checksum = CheckCompatibility(map);
|
|
if (Level->ib_compatflags & BCOMPATF_REBUILDNODES)
|
|
{
|
|
ForceNodeBuild = true;
|
|
}
|
|
T_LoadScripts(Level, map);
|
|
|
|
if (!map->HasBehavior || map->isText)
|
|
{
|
|
// Doom format and UDMF text maps get strict monster activation unless the mapinfo
|
|
// specifies differently.
|
|
if (!(Level->flags2 & LEVEL2_LAXACTIVATIONMAPINFO))
|
|
{
|
|
Level->flags2 &= ~LEVEL2_LAXMONSTERACTIVATION;
|
|
}
|
|
}
|
|
|
|
if (!map->HasBehavior && !map->isText)
|
|
{
|
|
Level->flags2 |= LEVEL2_DUMMYSWITCHES;
|
|
}
|
|
|
|
Level->Behaviors.LoadDefaultModules();
|
|
LoadMapinfoACSLump();
|
|
|
|
|
|
LoadStrifeConversations(map, lumpname);
|
|
|
|
FMissingTextureTracker missingtex;
|
|
|
|
if (!map->isText)
|
|
{
|
|
LoadVertexes(map);
|
|
|
|
// Check for maps without any BSP data at all (e.g. SLIGE)
|
|
LoadSectors(map, missingtex);
|
|
|
|
if (!map->HasBehavior)
|
|
LoadLineDefs(map);
|
|
else
|
|
LoadLineDefs2(map); // [RH] Load Hexen-style linedefs
|
|
|
|
LoadSideDefs2(map, missingtex);
|
|
|
|
FinishLoadingLineDefs();
|
|
|
|
if (!map->HasBehavior)
|
|
LoadThings(map);
|
|
else
|
|
LoadThings2(map); // [RH] Load Hexen-style things
|
|
}
|
|
else
|
|
{
|
|
ParseTextMap(map, missingtex);
|
|
}
|
|
|
|
SetCompatibilityParams(checksum);
|
|
|
|
LoopSidedefs(true);
|
|
|
|
SummarizeMissingTextures(missingtex);
|
|
bool reloop = false;
|
|
|
|
if (!ForceNodeBuild)
|
|
{
|
|
// Check for compressed nodes first, then uncompressed nodes
|
|
FileReader *fr = nullptr;
|
|
uint32_t id = MAKE_ID('X', 'x', 'X', 'x'), idcheck = 0, idcheck2 = 0, idcheck3 = 0, idcheck4 = 0, idcheck5 = 0, idcheck6 = 0;
|
|
|
|
if (map->Size(ML_ZNODES) != 0)
|
|
{
|
|
// Test normal nodes first
|
|
fr = &map->Reader(ML_ZNODES);
|
|
idcheck = MAKE_ID('Z', 'N', 'O', 'D');
|
|
idcheck2 = MAKE_ID('X', 'N', 'O', 'D');
|
|
}
|
|
else if (map->Size(ML_GLZNODES) != 0)
|
|
{
|
|
fr = &map->Reader(ML_GLZNODES);
|
|
idcheck = MAKE_ID('Z', 'G', 'L', 'N');
|
|
idcheck2 = MAKE_ID('Z', 'G', 'L', '2');
|
|
idcheck3 = MAKE_ID('Z', 'G', 'L', '3');
|
|
idcheck4 = MAKE_ID('X', 'G', 'L', 'N');
|
|
idcheck5 = MAKE_ID('X', 'G', 'L', '2');
|
|
idcheck6 = MAKE_ID('X', 'G', 'L', '3');
|
|
}
|
|
|
|
bool NodesLoaded = false;
|
|
if (fr != nullptr && fr->isOpen()) fr->Read(&id, 4);
|
|
if (id != 0 && (id == idcheck || id == idcheck2 || id == idcheck3 || id == idcheck4 || id == idcheck5 || id == idcheck6))
|
|
{
|
|
NodesLoaded = LoadExtendedNodes(*fr, id);
|
|
}
|
|
else if (!map->isText) // regular nodes are not supported for text maps
|
|
{
|
|
// If all 3 node related lumps are empty there's no need to output a message.
|
|
// This just means that the map has no nodes and the engine is supposed to build them.
|
|
if (map->Size(ML_SEGS) != 0 || map->Size(ML_SSECTORS) != 0 || map->Size(ML_NODES) != 0)
|
|
{
|
|
if (!P_CheckV4Nodes(map))
|
|
{
|
|
NodesLoaded = LoadSubsectors<mapsubsector_t, mapseg_t>(map) &&
|
|
LoadNodes<mapnode_t, mapsubsector_t>(map) &&
|
|
LoadSegs<mapseg_t>(map);
|
|
}
|
|
else
|
|
{
|
|
NodesLoaded = LoadSubsectors<mapsubsector4_t, mapseg4_t>(map) &&
|
|
LoadNodes<mapnode4_t, mapsubsector4_t>(map) &&
|
|
LoadSegs<mapseg4_t>(map);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If loading the regular nodes failed try GL nodes before considering a rebuild
|
|
if (!NodesLoaded)
|
|
{
|
|
if (LoadGLNodes(map))
|
|
reloop = true;
|
|
else
|
|
ForceNodeBuild = true;
|
|
}
|
|
}
|
|
else reloop = true;
|
|
|
|
uint64_t startTime = 0, endTime = 0;
|
|
|
|
bool BuildGLNodes;
|
|
|
|
// The node builder needs these indices.
|
|
for (unsigned int i = 0; i < Level->sides.Size(); ++i)
|
|
{
|
|
Level->sides[i].sidenum = i;
|
|
}
|
|
|
|
if (ForceNodeBuild)
|
|
{
|
|
BuildGLNodes = true;
|
|
// In case the compatibility handler made changes to the map's layout
|
|
for(auto &line : Level->lines)
|
|
{
|
|
line.AdjustLine();
|
|
}
|
|
|
|
startTime = I_msTime();
|
|
TArray<FNodeBuilder::FPolyStart> polyspots, anchors;
|
|
GetPolySpots(map, polyspots, anchors);
|
|
FNodeBuilder::FLevel leveldata =
|
|
{
|
|
&Level->vertexes[0], (int)Level->vertexes.Size(),
|
|
&Level->sides[0], (int)Level->sides.Size(),
|
|
&Level->lines[0], (int)Level->lines.Size(),
|
|
0, 0, 0, 0
|
|
};
|
|
leveldata.FindMapBounds();
|
|
|
|
FNodeBuilder builder(leveldata, polyspots, anchors, BuildGLNodes);
|
|
builder.Extract(*Level);
|
|
endTime = I_msTime();
|
|
DPrintf(DMSG_NOTIFY, "BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, Level->segs.Size());
|
|
oldvertextable = builder.GetOldVertexTable();
|
|
reloop = true;
|
|
}
|
|
else
|
|
{
|
|
BuildGLNodes = false;
|
|
// Older ZDBSPs had problems with compressed sidedefs and assigned wrong sides to the segs if both sides were the same sidedef.
|
|
for (auto &seg : Level->segs)
|
|
{
|
|
if (seg.backsector == seg.frontsector && seg.linedef)
|
|
{
|
|
double d1 = (seg.v1->fPos() - seg.linedef->v1->fPos()).LengthSquared();
|
|
double d2 = (seg.v2->fPos() - seg.linedef->v1->fPos()).LengthSquared();
|
|
|
|
if (d2 < d1) // backside
|
|
{
|
|
seg.sidedef = seg.linedef->sidedef[1];
|
|
}
|
|
else // front side
|
|
{
|
|
seg.sidedef = seg.linedef->sidedef[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build GL nodes if we want a textured automap or GL nodes are forced to be built.
|
|
// If the original nodes being loaded are not GL nodes they will be kept around for
|
|
// use in P_PointInSubsector to avoid problems with maps that depend on the specific
|
|
// nodes they were built with (P:AR E1M3 is a good example for a map where this is the case.)
|
|
reloop |= CheckNodes(map, BuildGLNodes, (uint32_t)(endTime - startTime));
|
|
|
|
// set the head node for gameplay purposes. If the separate gamenodes array is not empty, use that, otherwise use the render nodes.
|
|
Level->headgamenode = Level->gamenodes.Size() > 0 ? &Level->gamenodes[Level->gamenodes.Size() - 1] : Level->nodes.Size() ? &Level->nodes[Level->nodes.Size() - 1] : nullptr;
|
|
|
|
LoadBlockMap(map);
|
|
|
|
LoadReject(map, false);
|
|
GroupLines(false);
|
|
FloodZones();
|
|
SetRenderSector();
|
|
FixMinisegReferences();
|
|
FixHoles();
|
|
|
|
// Create the item indices, after the last function which may change the data has run.
|
|
CalcIndices();
|
|
|
|
Level->bodyqueslot = 0;
|
|
// phares 8/10/98: Clear body queue so the corpses from previous games are
|
|
// not assumed to be from this one.
|
|
|
|
for (auto & p : Level->bodyque)
|
|
p = nullptr;
|
|
|
|
CreateSections(Level);
|
|
|
|
// [RH] Spawn slope creating things first.
|
|
SpawnSlopeMakers(&MapThingsConverted[0], &MapThingsConverted[MapThingsConverted.Size()], oldvertextable);
|
|
CopySlopes();
|
|
|
|
// Spawn 3d floors - must be done before spawning things so it can't be done in P_SpawnSpecials
|
|
Spawn3DFloors();
|
|
|
|
SpawnThings(position);
|
|
|
|
for (int i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (Level->PlayerInGame(i) && Level->Players[i]->mo != nullptr)
|
|
Level->Players[i]->health = Level->Players[i]->mo->health;
|
|
}
|
|
if (!map->HasBehavior && !map->isText)
|
|
TranslateTeleportThings(); // [RH] Assign teleport destination TIDs
|
|
|
|
if (oldvertextable != nullptr)
|
|
{
|
|
delete[] oldvertextable;
|
|
}
|
|
|
|
// set up world state
|
|
SpawnSpecials();
|
|
|
|
// disable reflective planes on sloped sectors.
|
|
for (auto &sec : Level->sectors)
|
|
{
|
|
if (sec.floorplane.isSlope()) sec.reflect[sector_t::floor] = 0;
|
|
if (sec.ceilingplane.isSlope()) sec.reflect[sector_t::ceiling] = 0;
|
|
}
|
|
for (auto &node : Level->nodes)
|
|
{
|
|
double fdx = FIXED2DBL(node.dx);
|
|
double fdy = FIXED2DBL(node.dy);
|
|
node.len = (float)g_sqrt(fdx * fdx + fdy * fdy);
|
|
}
|
|
|
|
InitRenderInfo(); // create hardware independent renderer resources for the level. This must be done BEFORE the PolyObj Spawn!!!
|
|
Level->ClearDynamic3DFloorData(); // CreateVBO must be run on the plain 3D floor data.
|
|
screen->mVertexData->CreateVBO(Level->sectors);
|
|
|
|
for (auto &sec : Level->sectors)
|
|
{
|
|
P_Recalculate3DFloors(&sec);
|
|
}
|
|
|
|
SWRenderer->SetColormap(Level); //The SW renderer needs to do some special setup for the level's default colormap.
|
|
InitPortalGroups(Level);
|
|
P_InitHealthGroups(Level);
|
|
|
|
if (reloop) LoopSidedefs(false);
|
|
PO_Init(); // Initialize the polyobjs
|
|
if (!Level->IsReentering())
|
|
Level->FinalizePortals(); // finalize line portals after polyobjects have been initialized. This info is needed for properly flagging them.
|
|
}
|