Reading a map file to find the highest and lowest points [Code provded]

Thought I would share this with anyone that wants it, and i wasn’t sure if Addon Releases or Developer Discussion was best for it.
Last night i was looking at the Source BSP file format and realised it would be pretty easy to read map files using Lua. Why would you want to do this? Well my main reason was to find the ‘bounding box’ of the map, so I can generate random vectors and have less chance of them being outside of the map.

The valve wiki page is pretty easy to follow along, basically the header of the file (first 1036 bytes) contains the info needed to be able to read the rest of the file. I read the header, get the info for where the Vertexes are stored in the map, then read the Vertex info. Then loop through those vertexes to find the minimum and maximum values for xyz.

Hopefully someone finds this useful, let me know if there’s anything wrong with it.

Code:[lua]–Whiterabbit

local function safe(s) --Helper function to print strings that contain nullbytes. Nullbytes [char(0)] show as _
return string.gsub(s, “%z”, “_”)
end
local function safeprint(s)
print(safe(s))
end
local function BytesToString(bytes) --Helper function to turn an array of bytes (numbers from 0 to 255) into a string of letters
return string.char(unpack(bytes))
end

–File reading functions

local function ReadBytes(f,numbytes) --Read a number of bytes from the file and return them in an array
local bytes = {}
for i=1,numbytes do
bytes[#bytes+1] = f:ReadByte()
end
return bytes
end

local function ReadLumpT(f) --Read a lump header from the file
local t = {}
t.Offset = f:ReadLong() --Read 4 bytes that make up a number that tells us where in the file this lump will be found
t.Length = f:ReadLong() --Read 4 bytes that make up a number that tells us how many bytes this lump uses
t.Version = f:ReadLong() --Read 4 bytes that make up a number that tells us the version of this lump (usually 0, occasionally 1 or 2)
t.IdentCode = f:ReadLong() --usually 0 (four zero bytes)
return t
end

local function ReadLumpArray(f) --Read every possible lump header from the file. The lumps are stored in this exact order (usually? all the time? if they aren’t in this order, how do we know what order they ARE in?)
local t = {}
t.LUMP_ENTITIES = ReadLumpT(f)
t.LUMP_PLANES = ReadLumpT(f)
t.LUMP_TEXDATA = ReadLumpT(f)
t.LUMP_VERTEXES = ReadLumpT(f)
t.LUMP_VISIBILITY = ReadLumpT(f)
t.LUMP_NODES = ReadLumpT(f)
t.LUMP_TEXINFO = ReadLumpT(f)
t.LUMP_FACES = ReadLumpT(f)
t.LUMP_LIGHTING = ReadLumpT(f)
t.LUMP_OCCLUSION = ReadLumpT(f)
t.LUMP_LEAFS = ReadLumpT(f)
t.LUMP_FACEIDS = ReadLumpT(f)
t.LUMP_EDGES = ReadLumpT(f)
t.LUMP_SURFEDGES = ReadLumpT(f)
t.LUMP_MODELS = ReadLumpT(f)
t.LUMP_WORLDLIGHTS = ReadLumpT(f)
t.LUMP_LEAFFACES = ReadLumpT(f)
t.LUMP_LEAFBRUSHES = ReadLumpT(f)
t.LUMP_BRUSHES = ReadLumpT(f)
t.LUMP_BRUSHSIDES = ReadLumpT(f)
t.LUMP_AREAS = ReadLumpT(f)
t.LUMP_AREAPORTALS = ReadLumpT(f)
t.LUMP_PORTALS = ReadLumpT(f)
t.LUMP_UNUSED0 = ReadLumpT(f)
t.LUMP_PROPCOLLISION = ReadLumpT(f)
t.LUMP_CLUSTERS = ReadLumpT(f)
t.LUMP_UNUSED1 = ReadLumpT(f)
t.LUMP_PROPHULLS = ReadLumpT(f)
t.LUMP_PORTALVERTS = ReadLumpT(f)
t.LUMP_UNUSED2 = ReadLumpT(f)
t.LUMP_PROPHULLVERTS = ReadLumpT(f)
t.LUMP_CLUSTERPORTALS = ReadLumpT(f)
t.LUMP_UNUSED3 = ReadLumpT(f)
t.LUMP_PROPTRIS = ReadLumpT(f)
t.LUMP_DISPINFO = ReadLumpT(f)
t.LUMP_ORIGINALFACES = ReadLumpT(f)
t.LUMP_PHYSDISP = ReadLumpT(f)
t.LUMP_PHYSCOLLIDE = ReadLumpT(f)
t.LUMP_VERTNORMALS = ReadLumpT(f)
t.LUMP_VERTNORMALINDICES = ReadLumpT(f)
t.LUMP_DISP_LIGHTMAP_ALPHAS = ReadLumpT(f)
t.LUMP_DISP_VERTS = ReadLumpT(f)
t.LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS = ReadLumpT(f)
t.LUMP_GAME_LUMP = ReadLumpT(f)
t.LUMP_LEAFWATERDATA = ReadLumpT(f)
t.LUMP_PRIMITIVES = ReadLumpT(f)
t.LUMP_PRIMVERTS = ReadLumpT(f)
t.LUMP_PRIMINDICES = ReadLumpT(f)
t.LUMP_PAKFILE = ReadLumpT(f)
t.LUMP_CLIPPORTALVERTS = ReadLumpT(f)
t.LUMP_CUBEMAPS = ReadLumpT(f)
t.LUMP_TEXDATA_STRING_DATA = ReadLumpT(f)
t.LUMP_TEXDATA_STRING_TABLE = ReadLumpT(f)
t.LUMP_OVERLAYS = ReadLumpT(f)
t.LUMP_LEAFMINDISTTOWATER = ReadLumpT(f)
t.LUMP_FACE_MACRO_TEXTURE_INFO = ReadLumpT(f)
t.LUMP_DISP_TRIS = ReadLumpT(f)
t.LUMP_PHYSCOLLIDESURFACE = ReadLumpT(f)
t.LUMP_PROP_BLOB = ReadLumpT(f)
t.LUMP_WATEROVERLAYS = ReadLumpT(f)
t.LUMP_LIGHTMAPPAGES = ReadLumpT(f)
t.LUMP_LEAF_AMBIENT_INDEX_HDR = ReadLumpT(f)
t.LUMP_LIGHTMAPPAGEINFOS = ReadLumpT(f)
t.LUMP_LEAF_AMBIENT_INDEX = ReadLumpT(f)
t.LUMP_LIGHTING_HDR = ReadLumpT(f)
t.LUMP_WORLDLIGHTS_HDR = ReadLumpT(f)
t.LUMP_LEAF_AMBIENT_LIGHTING_HDR = ReadLumpT(f)
t.LUMP_LEAF_AMBIENT_LIGHTING = ReadLumpT(f)
t.LUMP_XZIPPAKFILE = ReadLumpT(f)
t.LUMP_FACES_HDR = ReadLumpT(f)
t.LUMP_MAP_FLAGS = ReadLumpT(f)
t.LUMP_OVERLAY_FADES = ReadLumpT(f)
t.LUMP_OVERLAY_SYSTEM_LEVELS = ReadLumpT(f)
t.LUMP_PHYSLEVEL = ReadLumpT(f)
t.LUMP_DISP_MULTIBLEND = ReadLumpT(f)
return t
end

local function ReadVertex(f, i) --Read 3 floats and return them as a table
local x,y,z = f:ReadFloat(),f:ReadFloat(),f:ReadFloat()
–print(“Read vertex [”…i…"] “…tostring(x)…”,"…tostring(y)…","…tostring(z))
return {x=x,y=y,z=z}
end
local function ReadVertexTable(f,info) --Read the whole vertex lump using the info from the lump header
local t = {}
local numv = info.Length/12 --each vertex is 12 bytes (3 floats, 4 bytes each), so this should be correct number of vertexes to read
f:Seek(info.Offset)
for i=1,numv do
t* = ReadVertex(f, i) --Read them vertexes
end
return t
end

–This is where stuff actually happens

–Get current map name
local mapname = game.GetMap()
–Open map file
local fileobj = file.Open(‘maps/’…mapname…’.bsp’, ‘r’, “GAME”)

–Read header start
local ident = BytesToString(ReadBytes(fileobj, 4))
local version = fileobj:ReadLong()
–Raed header lump array
local lump_array = ReadLumpArray(fileobj)

print(" vvv MAP INFO AND LUMP LOCATIONS vvv “)
PrintTable({ident,version,lump_array})
print(” ^^^ MAP INFO AND LUMP LOCATIONS ^^^ ")

–Get the vertex-lump header from the array
local Vt = lump_array.LUMP_VERTEXES
if not Vt.Offset then error(“NO VERTEXES LUMP?”) end

–Read in the actual vertex array
local MapVertexes = ReadVertexTable(fileobj, Vt)
–print("Estimated number of vertexes = " … Vt.Length/12)
–print("Actual number = " … #MapVertexes)
–PrintTable(MapVertexes)

–Loop and find the min and max points
local minz,maxz,minx,maxx,miny,maxy
local firstVert = MapVertexes[1]
minx,maxx = firstVert.x,firstVert.x
miny,maxy = firstVert.y,firstVert.y
minz,maxz = firstVert.z,firstVert.z
for i=2,#MapVertexes do
local v = MapVertexes*
if v then
–print(“Compare”,i,v)
if v.x and (v.x < minx) then minx = v.x end
if v.x and (v.x > maxx) then maxx = v.x end
if v.y and (v.y < miny) then miny = v.y end
if v.y and (v.y > maxy) then maxy = v.y end
if v.z and (v.z < minz) then minz = v.z end
if v.z and (v.z > maxz) then maxz = v.z end
end
end

local minv = Vector(minx,miny,minz)
local maxv = Vector(maxx,maxy,maxz)

print("MinV = ",minv)
print("MaxV = ",maxv)[/lua]

Output from gm_flatgrass:[lua]lua_openscript testmapread.lua
Running script testmapread.lua…
vvv MAP INFO AND LUMP LOCATIONS vvv
1 = VBSP
2 = 20
3:
LUMP_UNUSED0:
LUMP_FACES:
Offset = 1397932
Length = 163632
IdentCode = 0
Version = 1
LUMP_WORLDLIGHTS:
LUMP_PROPTRIS:
LUMP_PHYSCOLLIDE:
LUMP_EDGES:
LUMP_ENTITIES:
Offset = 4784852
Length = 24727
IdentCode = 0
Version = 0
LUMP_DISP_TRIS:
LUMP_AREAPORTALS:
LUMP_PHYSCOLLIDESURFACE:
LUMP_FACEIDS:
Offset = 1725196
LUMP_TEXDATA:
Offset = 1267244
Length = 576
IdentCode = 0
Version = 0
LUMP_MODELS:
LUMP_CLUSTERS:
LUMP_LEAF_AMBIENT_INDEX:
LUMP_PHYSLEVEL:
LUMP_SURFEDGES:
LUMP_PROPHULLVERTS:
LUMP_DISPINFO:
LUMP_WATEROVERLAYS:
LUMP_UNUSED1:
LUMP_LEAF_AMBIENT_LIGHTING:
LUMP_PROP_BLOB:
LUMP_VERTNORMALINDICES:
LUMP_PHYSDISP:
LUMP_LIGHTMAPPAGEINFOS:
LUMP_PRIMVERTS:
LUMP_DISP_LIGHTMAP_ALPHAS:
LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS:
LUMP_PROPCOLLISION:
LUMP_VERTEXES:
Offset = 1104340
Length = 44856
IdentCode = 0
Version = 0
LUMP_WORLDLIGHTS_HDR:
LUMP_LEAFWATERDATA:
LUMP_FACE_MACRO_TEXTURE_INFO:
LUMP_AREAS:
LUMP_BRUSHSIDES:
LUMP_DISP_MULTIBLEND:
LUMP_OCCLUSION:
Offset = 4810108
Length = 12
IdentCode = 0
Version = 2
LUMP_VERTNORMALS:
LUMP_OVERLAY_SYSTEM_LEVELS:
LUMP_GAME_LUMP:
LUMP_TEXDATA_STRING_DATA:
LUMP_OVERLAY_FADES:
LUMP_MAP_FLAGS:
LUMP_BRUSHES:
LUMP_XZIPPAKFILE:
LUMP_PAKFILE:
LUMP_LEAF_AMBIENT_LIGHTING_HDR:
LUMP_LEAFFACES:
LUMP_LIGHTING_HDR:
LUMP_OVERLAYS:
LUMP_LEAFS:
Offset = 26756
Length = 115520
IdentCode = 0
Version = 1
LUMP_NODES:
Offset = 1149196
Length = 115456
IdentCode = 0
Version = 0
LUMP_UNUSED2:
LUMP_TEXINFO:
Offset = 1264652
Length = 2592
IdentCode = 0
Version = 0
LUMP_LEAFMINDISTTOWATER:
LUMP_FACES_HDR:
LUMP_PROPHULLS:
LUMP_LEAFBRUSHES:
LUMP_ORIGINALFACES:
LUMP_LEAF_AMBIENT_INDEX_HDR:
LUMP_PLANES:
Offset = 1036
Length = 25720
IdentCode = 0
Version = 0
LUMP_PORTALVERTS:
LUMP_PORTALS:
LUMP_LIGHTING:
Offset = 1858920
Length = 1312924
IdentCode = 0
Version = 1
LUMP_CLIPPORTALVERTS:
LUMP_UNUSED3:
LUMP_LIGHTMAPPAGES:
LUMP_CLUSTERPORTALS:
LUMP_VISIBILITY:
Offset = 4484768
Length = 300081
IdentCode = 0
Version = 0
LUMP_DISP_VERTS:
LUMP_TEXDATA_STRING_TABLE:
LUMP_PRIMITIVES:
LUMP_CUBEMAPS:
LUMP_PRIMINDICES:
^^^ MAP INFO AND LUMP LOCATIONS ^^^
MinV = 0.000000 0.000000 -15872.000000
MaxV = 15360.000000 15360.000000 15360.000000
[/lua]

[editline]14th September 2013[/editline]

Why am I seeing =“entity label”> in the output I pasted?