srctools.bsp
The BSP module allows reading and writing compiled maps. Note that the file format is composed of a large number of individual “lumps”, each independent from one another. Since there is a large amount of data, parsing occurs lazily - the first time an attribute is accessed, it will be parsed into proper data structures along with any dependent lumps. When saved, parsed lumps will be reconstructed, while unparsed lumps will be resaved unchanged.
In Team Fortress 2, support was added for compressed BSP files. Support for this is somewhat incomplete.
The data structures closely match the underlying file format, though some details are automatically handled. For full details, consult the VDC article on the format.
General Functionality
To load a BSP, simply construct BSP, passing the filename as a parameter. This will
load the full file into memory, but only parse the main headers. Due to the complexity of BSP files,
currently it is not possible to create one from scratch. Call :BSP.save() to save.
- class srctools.bsp.BSP
- __init__(
- filename: str | os.PathLike[str],
- version: VERSIONS | GameVersion | None = None,
Create and load a BSP.
- Parameters:
filename – The filename to read.
version – Specify the expected file version, causing an error if the BSP does not match.
- read(self, expected_version: VERSIONS | GameVersion | None = None) None
Reload the BSP file from disk.
- Parameters:
expected_version – Specify the expected file version, causing an error if the BSP does not match.
Versions
A number of attributes are available describing the versions. Known versions are stored as enums.
- BSP.version: VERSIONS | int
The version ID in the file. Will be a
VERSIONSenum if known, otherwise an integer.
- BSP.game_ver: GameVersion
A srctools-specific version to identify some games with unique handling. Inferred from the version ID if specified there.
- class srctools.bsp.VERSIONS
Bases:
EnumThe BSP version numbers for various games.
- VER_17 = 17
- VER_18 = 18
- VER_19 = 19
Aliases:
HL2,CS_SOURCE,DOF_SOURCE
- VER_20 = 20
Aliases:
HL2_EP1,HL2_EP2,HL2_LC,GARYS_MOD,TF2,PORTAL,L4D,ZENO_CLASH,DARK_MESSIAH,VINDICTUS,THE_SHIP,BLACK_MESA,BLOODY_GOOD_TIME
- VER_21 = 21
Aliases:
L4D2,ALIEN_SWARM,PORTAL_2,CS_GO,DEAR_ESTHER,STANLEY_PARABLE
- VER_22 = 22
Aliases:
INFRA,DOTA2
- VER_29 = 29
- CONTAGION = 23
- STRATA_SOURCE = 25
Alias:
CHAOSSOURCE
- DESOLATION_OLD = 42
- VITAMINSOURCE = 43
Lumps
Each BSP lump is exposed via a number of properties. When first accessed, the data is parsed, and when saving, parsed data is re-exported. There are two types of lumps - the regular ones are always present and defined by their order, while “game lumps” are optional and use an associated 4-byte ID.
Many lumps contain arrays of data, which are indexed into by other lumps. This is mostly handled automatically - when parsing, the indexes are resolved into the referenced object. When saving, the object is added to the array automatically if not present, so there is no need to update the original lump. However, it is possible to clear that to remove unused values, if all lumps using the data have been parsed so they can refill the array.
In addition to using parsed objects, raw data for both lump types can be accessed with the following methods:
- BSP.game_lumps
Maps a lump ID to the stored game lump. The key should be 4 characters long.
- class srctools.bsp.BSP_LUMPS
Bases:
EnumAll the lumps in a BSP file.
The values represent the order lumps appear in the index. Some indexes were reused, so they have aliases.
- ENTITIES = 0
self.ents
- PLANES = 1
self.planes
- TEXDATA = 2
- VERTEXES = 3
self.vertexes
- VISIBILITY = 4
- NODES = 5
self.nodes
- TEXINFO = 6
self.texinfo
- FACES = 7
self.faces
- LIGHTING = 8
- OCCLUSION = 9
- LEAFS = 10
self.leafs
- FACEIDS = 11
- EDGES = 12
- SURFEDGES = 13
self.surfedges
- MODELS = 14
self.bmodels
- WORLDLIGHTS = 15
- LEAFFACES = 16
- LEAFBRUSHES = 17
- BRUSHES = 18
self.brushes
- BRUSHSIDES = 19
- AREAS = 20
- AREAPORTALS = 21
- PORTALS = 22
Aliases:
UNUSED0,PROPCOLLISION
- CLUSTERS = 23
Aliases:
UNUSED1,PROPHULLS
- PORTALVERTS = 24
Aliases:
UNUSED2,PROPHULLVERTS
- CLUSTERPORTALS = 25
Aliases:
UNUSED3,PROPTRIS
- DISPINFO = 26
- ORIGINALFACES = 27
self.orig_faces
- PHYSDISP = 28
- PHYSCOLLIDE = 29
- VERTNORMALS = 30
- VERTNORMALINDICES = 31
- DISP_LIGHTMAP_ALPHAS = 32
- DISP_VERTS = 33
- DISP_LIGHTMAP_SAMPLE_POSITIONS = 34
- GAME_LUMP = 35
- LEAFWATERDATA = 36
self.water_leaf_info
- PRIMITIVES = 37
self.primitives
- PRIMVERTS = 38
- PRIMINDICES = 39
- PAKFILE = 40
self.pakfile
- CLIPPORTALVERTS = 41
- CUBEMAPS = 42
self.cubemaps
- TEXDATA_STRING_DATA = 43
- TEXDATA_STRING_TABLE = 44
self.textures
- OVERLAYS = 45
self.overlays
- LEAFMINDISTTOWATER = 46
- FACE_MACRO_TEXTURE_INFO = 47
- DISP_TRIS = 48
- PROP_BLOB = 49
Alias:
PHYSCOLLIDESURFACE
- WATEROVERLAYS = 50
- LEAF_AMBIENT_INDEX_HDR = 51
Alias:
LIGHTMAPPAGES
- LEAF_AMBIENT_INDEX = 52
Alias:
LIGHTMAPPAGEINFOS
- LIGHTING_HDR = 53
- WORLDLIGHTS_HDR = 54
- LEAF_AMBIENT_LIGHTING_HDR = 55
- LEAF_AMBIENT_LIGHTING = 56
- XZIPPAKFILE = 57
- FACES_HDR = 58
self.hdr_faces
- MAP_FLAGS = 59
- OVERLAY_FADES = 60
- OVERLAY_SYSTEM_LEVELS = 61
- PHYSLEVEL = 62
- DISP_MULTIBLEND = 63
- class srctools.bsp.Lump( )
Represents a lump header in a BSP file.
- version: int
The version value is stored in the BSP. Not totally reliable, Valve sometimes modifies lumps without updating the ID.
- class srctools.bsp.GameLump(id: bytes, flags: int, version: int, data: bytes = b'')
Represents a game lump.
These are designed to be game-specific.
- version: int
Version number for this lump. Not totally reliable, since different games have used the same version number for different layouts.
Entity Lump
- BSP.ents: VMF
The entity lump stores all entities and their keyvalues. When parsed, this is exposed as a
srctools.vmf.VMFobject, since the entities function identically. However, all brushes are stored elsewhere, sosolidsattributes will all be blank and are ignored. See theBSP.bmodelsattribute to locate the compiled brush data for an entity.Aside from the entities, the rest of the VMF object is ignored.
- BSP.bmodels: WeakKeyDictionary[Entity, BModel]
For each brush entity (including worldspawn) this defines the compiled brush data. Since this is a weak dictionary, removing entities will automatically clear their brush data. In engine, entities are linked to their brushes with a “model name” like
*42. Any entity in this dictionary will have itsmodelkeyvalue overwritten to point to the specified model.
Pakfile
- BSP.pakfile: zipfile.ZipFile
The pakfile is an internal archive containing resources packed into the BSP for the level to use. By default, cubemaps are included here as well as patched brush materials VBSP generates for various purposes. Any file can be stored here, but only the
zipfile.ZIP_STORED(no compression) format is allowed.The returned zipfile wraps an
io.BytesIOobject, and will be closed/finalised automatically. Do not close it yourself.
Textures
Textures (really materials, the name is a GoldSource leftover) are stored as TexInfo objects, which contain the material as well
as S/T positioning, lightmap and other data. This allows brushes with matching data to be shared. Overlays also
use texinfo, but ignore the S/T positioning. VBSP stores some data derived from the
material’s $basetexture in the BSP - its size and reflectivity. This means that creating TexInfo
requires either providing this extra data manually, or supplying a
FileSystem to automatically read this data from the material/texture files.
To allow reusing existing values, the following method should be used to create each entry:
- BSP.create_texinfo(
- mat: str,
- *,
- copy_from: TexInfo,
- fsys: FileSystem,
- BSP.create_texinfo( ) TexInfo
- BSP.create_texinfo(
- mat: str,
- s_off: AnyVec = FrozenVec(),
- s_shift: float = -99999.0,
- t_off: AnyVec = FrozenVec(),
- t_shift: float = -99999.0,
- lightmap_s_off: AnyVec = FrozenVec(),
- lightmap_s_shift: float = -99999.0,
- lightmap_t_off: AnyVec = FrozenVec(),
- lightmap_t_shift: float = -99999.0,
- flags: SurfFlags = SurfFlags.NONE,
- *,
- fsys: FileSystem,
- BSP.create_texinfo(
- mat: str,
- s_off: AnyVec = FrozenVec(),
- s_shift: float = -99999.0,
- t_off: AnyVec = FrozenVec(),
- t_shift: float = -99999.0,
- lightmap_s_off: AnyVec = FrozenVec(),
- lightmap_s_shift: float = -99999.0,
- lightmap_t_off: AnyVec = FrozenVec(),
- lightmap_t_shift: float = -99999.0,
- flags: SurfFlags = SurfFlags.NONE,
- *,
- reflectivity: AnyVec,
- width: int,
- height: int,
Create or find a texinfo entry with the specified values.
The s/t offset and shift values control the texture positioning. The defaults are those used for overlays, but for brushes all must be specified. Alternatively
copy_fromcan be provided an existing texinfo to copy from, if a texture is being swapped out.In the BSP each material also stores its texture size and reflectivity. If the material has not been used yet, these must either be specified manually or a filesystem provided for the VMT and VTFs to be read from.
- class srctools.bsp.TexInfo
Represents texture positioning / scaling info.
Overlays don’t use the offset/shifts, setting them to
(0, 0, 0)and-99999.0respectively.TexInfo structures reference an additional
TexDatastruct, containing texture size and reflectivity information. This is managed automatically.- flags: SurfFlags
Bitflags calculated from the material, like translucency, whether lighting calculation is required and portalability.
Static Props
- BSP.props: list[StaticProp]
prop_staticentities are specially handled by VBSP, and stored in their own game lump (with IDsprp). This lump has undergone significant changes in various games, so many attributes may do nothing.
- for model_name in BSP.static_prop_models() Iterator[str]
Yield all model filenames used in static props.
If props haven’t been parsed yet, this can avoid parsing all the props themselves. If they have, this will iterate those.
- BSP.static_prop_version: StaticPropVersion = StaticPropVersion.UNKNOWN
The version number for static props is unreliable, with incompatible games using overlapping versions. The byte size of each prop structure is used to determine the precise version used. This stores the version detected to allow saving correctly. This can also be set manually before
propsis accessed to override the automatic detection.
- class srctools.bsp.StaticProp(
- model: str,
- origin: ~srctools.math.Vec,
- angles: ~srctools.math.Angle =...,
- scaling: ~srctools.math.Vec | float =...,
- visleafs: set[~srctools.bsp.VisLeaf] =...,
- solidity: int = 6,
- flags: ~srctools.bsp.StaticPropFlags = <StaticPropFlags.NONE: 0>,
- skin: int = 0,
- min_fade: float = 0.0,
- max_fade: float = 0.0,
- lighting: ~srctools.math.Vec =...,
- fade_scale: float = -1.0,
- min_dx_level: int = 0,
- max_dx_level: int = 0,
- min_cpu_level: int = 0,
- max_cpu_level: int = 0,
- min_gpu_level: int = 0,
- max_gpu_level: int = 0,
- tint: ~srctools.math.Vec =...,
- renderfx: int = 255,
- disable_on_xbox: bool = False,
- lightmap_x: int = 32,
- lightmap_y: int = 32,
Represents a
prop_staticin the BSP.Different features were added in different versions.
v5+allows fade_scale.v6andv7allow min/max DXLevel.v8+allows min/max GPU and CPU levels.v7+allows model tinting, and renderfx.v9+allows disabling on XBox 360.v10+adds 4 unknown bytes (float?), and an expanded flags section.v11+adds uniform scaling.
- scaling: Vec | float
Scaling factor for the prop, only available in CSGO and derived mods. Strata Source additionally adds the ability to scale independently in each axis. Either type can be set - only the X axis is used if only uniform scaling is supported.
- visleafs: set[VisLeaf]
When compiled, VBSP uses the model geometry to statically determine the visleafs the model occupies and stores them here.
- flags: StaticPropFlags
- class srctools.bsp.StaticPropFlags
Bases:
FlagBitflags specified for static props.
These are actually split over two flag fields, but are merged here for simplicity.
- NONE = 0x0
- DOES_FADE = 0x1
- HAS_LIGHTING_ORIGIN = 0x2
- NO_FLASHLIGHT = 0x4
Alias:
DISABLE_DRAWThis was nodraw in earlier versions, but it now prevents projected textures from affecting the prop.
- IGNORE_NORMALS = 0x8
- NO_SHADOW = 0x10
- SCREEN_SPACE_FADE = 0x20
Use screen space fading. Obsolete since at least ASW.
- NO_PER_VERTEX_LIGHTING = 0x40
- NO_SELF_SHADOWING = 0x80
- NO_SHADOW_DEPTH = 0x100
Alias:
NO_LIGHTMAPDisable affecting projected texture lighting. In games supporting lightmapped props (TF2), this instead disables per-luxel lighting.
- BOUNCED_LIGHTING = 0x400
Bounce lighting off the prop.
- class srctools.bsp.StaticPropVersion
Bases:
EnumThe detected version for static props.
Despite the format having version numbers, several engine branches use the same number but have various changes. Thanks to BSPSource for this information. We record the version in the file, and the size of the structure.
Name is set to prevent aliasing special variants that can’t lookup.
- V4 = (4, 56)
V4 and V5 are used in original HL2 maps.
- V5 = (5, 60)
Alias:
DEFAULTAdds forcedFadeScale.
- V6 = (6, 64)
Some TF2 maps, adds min/max DX level.
- V7 = (7, 68)
Old L4D maps, adds rendercolor.
- V8 = (8, 68)
Main L4D, removes min/max DX, adds min/max GPU and CPU.
- V9 = (9, 72)
L4D2, adds disableX360.
- V10 = (10, 76)
Old CSGO, adds new flags integer.
- V11 = (11, 80)
New CSGO, with uniform prop scaling.
- V_LIGHTMAP_v7 = (7, 72)
Source 2013, also appears with version
7but is identical. Based onv6, adds lightmapped props. Despite the version number, this is more likev6.
- V_LIGHTMAP_v10 = (10, 72)
- V_LIGHTMAP_MESA = (11, 80, 'Mesa')
Adds rendercolor to V10
- V_STRATA_V12 = (12, 80)
Alias:
V_CHAOS_V12Changes the leaf list from
uint16touint32.
- V_STRATA_V13 = (13, 88)
Alias:
V_CHAOS_V13Changes scale from one float to three for non-uniform scaling support.
- UNKNOWN = (0, 0, 'unknown')
Indicates the lump has not been parsed.
Detail Props
- BSP.detail_props: list[DetailProp]
When compiling, VBSP places detail props onto all brushes, and stores them in their own game lump (with ID
dprp). There are three types of prop - model, simple sprite, and ‘shape’ (formed of multiple sprites in a particular pattern).
- class srctools.bsp.DetailPropOrientation
Bases:
EnumThe kind of orientation for detail props.
- NORMAL = 0
Does not rotate.
- SCREEN_ALIGNED = 1
Rotates to face directly into the screen.
- SCREEN_ALIGNED_VERTICAL = 2
- class srctools.bsp.DetailProp
A detail prop, automatically placed on surfaces.
This is a base class, use one of the subclasses only.
- orientation: DetailPropOrientation
Determines if and how the prop is affected by the camera direction.
- class srctools.bsp.DetailPropModel(
- origin: Vec,
- angles: Angle,
- orientation: DetailPropOrientation,
- leaf: int,
- lighting: tuple[int, int, int, int],
- light_styles: tuple[int, int],
- sway_amount: int,
- model: str,
A MDL detail prop.
- class srctools.bsp.DetailPropSprite(
- origin: Vec,
- angles: Angle,
- orientation: DetailPropOrientation,
- leaf: int,
- lighting: tuple[int, int, int, int],
- light_styles: tuple[int, int],
- sway_amount: int,
- sprite_scale: float,
- dims_upper_left: tuple[float, float],
- dims_lower_right: tuple[float, float],
- texcoord_upper_left: tuple[float, float],
- texcoord_lower_right: tuple[float, float],
A sprite-type detail prop.
- class srctools.bsp.DetailPropShape(
- origin: Vec,
- angles: Angle,
- orientation: DetailPropOrientation,
- leaf: int,
- lighting: tuple[int, int, int, int],
- light_styles: tuple[int, int],
- sway_amount: int,
- sprite_scale: float,
- dims_upper_left: tuple[float, float],
- dims_lower_right: tuple[float, float],
- texcoord_upper_left: tuple[float, float],
- texcoord_lower_right: tuple[float, float],
- is_cross: bool,
- shape_angle: int,
- shape_size: int,
A shape-type detail prop, rendered as a triangle or cross shape.
Cubemaps
- BSP.cubemaps: list[Cubemap]
env_cubemapentities are parsed out of the VMF and stored in their own lump.
Overlays
- class srctools.bsp.Overlay(
- id: int,
- origin: Vec,
- normal: Vec,
- texture: TexInfo,
- face_count: int,
- faces: list[int] = ...,
- render_order: int = 0,
- u_min: float = 0.0,
- u_max: float = 1.0,
- v_min: float = 0.0,
- v_max: float = 1.0,
- uv1: Vec = ...,
- uv2: Vec = ...,
- uv3: Vec = ...,
- uv4: Vec = ...,
- fade_min_sq: float = -1.0,
- fade_max_sq: float = 0.0,
- min_cpu: int = 0,
- max_cpu: int = 0,
- min_gpu: int = 0,
- max_gpu: int = 0,
An overlay embedded in the map.
Visibility
- BSP.visibility: Visibility | None
If VVIS has run, this contains the computed visibility calculations between all leaves.
- class srctools.bsp.Visibility(potentially_visible: list[bytearray], potentially_audible: list[bytearray])
The visibility data produced by VVIS.
Visleafs each have a “cluster” ID. For every pair of cluster IDs, this indicates if the first can see the second, and whether they can hear each other.
Visleafs
The visleaf structure is composed of a tree either of nodes or vis-leafs. Note these are always calculated, even without VVIS - the tree is fundamental to the BSP structure.
- class srctools.bsp.VisTree(
- plane: Plane,
- mins: Vec,
- maxes: Vec,
- faces: list[Face],
- area_ind: int,
- child_neg: VisTree | VisLeaf = None,
- child_pos: VisTree | VisLeaf = None,
A tree node in the visleaf/BSP data.
Each of these is a plane splitting the map in two, which then has a child tree or visleaf on either side.
- area_ind: int
Index of the ‘area’ this tree is located in. Each area is entirely separated by areaportals and solid geometry.
- class srctools.bsp.VisLeaf(
- contents: BSPContents,
- cluster_id: int,
- area: int,
- flags: VisLeafFlags,
- mins: Vec,
- maxes: Vec,
- faces: list[Face],
- brushes: list[Brush],
- water_id: int,
- ambient: bytes = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- min_water_dist: int = 65535,
A leaf in the visleaf/BSP data.
The bounds are defined implicitly by the parent node planes. The ambient light data is currently not parsed.
- contents: BSPContents
- flags: VisLeafFlags
- class srctools.bsp.VisLeafFlags
Bases:
FlagVisleaf flags.
- NONE = 0x0
- SKY_3D = 0x1
The 3D skybox is visible from here.
- SKY_2D = 0x4
The 2D skybox is visible from here.
- RADIAL = 0x2
Has culled portals, due to far-z fog limits.
- HAS_DETAIL_OBJECTS = 0x8
Contains detail props - ingame only, not set in BSP.
- BSP.water_leaf_info
For each water brush in the map, this structure stores some additional information.
Planes
Brushes
The brushes lump contains a copy of all the original brushes in the map. This is mainly used for collision.
- class srctools.bsp.Brush(contents: BSPContents, sides: list[BrushSide])
A brush definition.
- contents: BSPContents
- class srctools.bsp.BrushSide( )
A side of the original brush geometry which the map is constructed from.
This matches the original VMF.
Faces
- BSP.primitives
Primitive surfaces, also known as ‘t-junctions’ or ‘waterverts’ are generated to stitch together T-junction faces. This fixes potential seams.
- class srctools.bsp.Face(
- plane: Plane,
- same_dir_as_plane: bool,
- on_node: bool,
- edges: list[Edge],
- texinfo: TexInfo | None,
- dispinfo_ind: int,
- surf_fog_volume_id: int,
- light_styles: bytes,
- lightmap_off: int,
- area: float,
- lightmap_mins: tuple[int, int],
- lightmap_size: tuple[int, int],
- orig_face: Face | None,
- primitives: list[Primitive],
- dynamic_shadows: bool,
- smoothing_groups: int,
- hammer_id: int | None,
- vitamin_flags: int,
A brush face definition.
- class srctools.bsp.Primitive(is_tristrip: bool, indexed_verts: list[int], verts: list[Vec])
A ‘primitive’ surface (AKA t-junction, waterverts).
These are generated to stitch together T-junction faces.
Miscellaneous
- BSP.is_cordoned_heuristic() bool
Guess to see if the map uses cordons.
There’s no definite flag, but we can guess based on the shape of the geometry. Cordoning causes a brush almost the map size units to be created, then the cordon regions carved out of it. So the overall map size will be very close to the max range.
This isn’t certain, since users could manually create brushes this large, but that’s not too likely.
- type srctools.bsp.BrushContents = srctools.consts.BSPContents
This enum specifies collision types for brushes. It is reimported in the BSP module for convenience.