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.

save(self, filename: str | None = None) None

Write the BSP back into the given file.

Parameters:

filename – If specified, overrides the originally read filename.

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 VERSIONS enum 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.

BSP.map_revision: int

A counter for the map revision. Incremented whenever Hammer saves the VMF.

class srctools.bsp.VERSIONS

Bases: Enum

The 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
class srctools.bsp.GameVersion

Bases: Enum

Identifies specific games which we need to detect and specially handle.

NORMAL = 'normal'
L4D2 = 'l4d2'
VITAMINSOURCE = 'vitaminsource'

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.lumps

Maps a lump ID to the stored lump.

Type:

dict[BSP_LUMPS, Lump]

BSP.game_lumps

Maps a lump ID to the stored game lump. The key should be 4 characters long.

Type:

dict[bytes, Lump]

BSP.get_lump(lump: BSP_LUMPS) bytes

Return the contents of the given lump.

BSP.get_game_lump(lump_id: bytes) bytes

Get a given game-lump, given the 4-character byte ID.

class srctools.bsp.BSP_LUMPS

Bases: Enum

All 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(
type: BSP_LUMPS,
version: int,
data: bytes = b'',
is_compressed: bool = False,
)

Represents a lump header in a BSP file.

type: BSP_LUMPS

Type of lump.

version: int

The version value is stored in the BSP. Not totally reliable, Valve sometimes modifies lumps without updating the ID.

data: bytes

If not parsed, contains the raw data for the lump. When the associated property is accessed, this is automatically cleared.

is_compressed: bool

If true, this is LZMA compressed.

class srctools.bsp.GameLump(id: bytes, flags: int, version: int, data: bytes = b'')

Represents a game lump.

These are designed to be game-specific.

id: bytes

ID for the lump. Should be 4 bytes long.

flags: int

Arbitary bitflags, aside from 0x1 which represents compression.

version: int

Version number for this lump. Not totally reliable, since different games have used the same version number for different layouts.

data: bytes

If not parsed, contains the raw data for the lump. When the associated property is accessed, this is automatically cleared.

property is_compressed: bool

This flag indicates if the lump was compressed.

Entity Lump

BSP.ents: VMF

The entity lump stores all entities and their keyvalues. When parsed, this is exposed as a srctools.vmf.VMF object, since the entities function identically. However, all brushes are stored elsewhere, so solids attributes will all be blank and are ignored. See the BSP.bmodels attribute 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 its model keyvalue overwritten to point to the specified model.

class srctools.bsp.BModel
mins: Vec
maxes: Vec

Axial bounding box surrounding the brush model.

origin: Vec

Original position of the brushes.

node: VisTree

References the root node for the visleaf tree for this brush model.

faces: list[Face]

All faces in this brush model, unsorted.

phys_keyvalues: Keyvalues | None

If this brush model is solid, this contains the VPhysics data. It is very similar to that in .phy model files. For each brush, this stores metadata like mass, surfaceprop, etc.

clear_physics() None

Delete the physics data for this brush model, and set the visleafs to non-solid.

This is useful to optimise if the entity is known to not be solid to physics objects.

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.BytesIO object, 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.

BSP.textures: list[str]

The raw list of materials used. Automatically appended to by texinfo.

BSP.texinfo: list[TexInfo]

Materials along with their positioning information.

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,
) TexInfo
BSP.create_texinfo(
mat: str,
*,
copy_from: TexInfo,
reflectivity: AnyVec,
width: int,
height: int,
) 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,
) 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,
*,
reflectivity: AnyVec,
width: int,
height: int,
) TexInfo

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_from can 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.0 respectively.

TexInfo structures reference an additional TexData struct, containing texture size and reflectivity information. This is managed automatically.

s_off: Vec
s_shift: float
t_off: Vec
t_shift: float
lightmap_s_off: Vec
lightmap_s_shift: float
lightmap_t_off: Vec
lightmap_t_shift: float
flags: SurfFlags

Bitflags calculated from the material, like translucency, whether lighting calculation is required and portalability.

property mat: str

The material used for this texinfo.

property reflectivity: Vec

The reflectivity of the texture.

property tex_size: tuple[int, int]

The size of the texture.

set(
bsp: BSP,
mat: str,
*,
fsys: FileSystem,
) None
set(
bsp: BSP,
mat: str,
reflectivity: Vec,
width: int,
height: int,
) None

Set the material used for this texinfo.

If it is not already used in the BSP, some additional info is required. This can either be parsed from the VMT and VTF, or provided directly.

Static Props

BSP.props: list[StaticProp]

prop_static entities are specially handled by VBSP, and stored in their own game lump (with ID sprp). 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 props is 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,
*,
color_var: str | None = None,
)

Represents a prop_static in the BSP.

Different features were added in different versions.

  • v5+ allows fade_scale.

  • v6 and v7 allow 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.

model: str
origin: Vec

Position of the prop.

angles: Angle
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.

solidity: int
flags: StaticPropFlags
skin: int
min_fade: float
max_fade: float
lighting: Vec

This is the location used for non-vertex lighting, in world space. It defaults to the prop origin (or $illumposition value), but can be changed by info_lighting entities. Since this is always set to a valid value, there is no direct way to tell if this is custom or not.

fade_scale: float
min_dx_level: int
max_dx_level: int
min_cpu_level: int
max_cpu_level: int
min_gpu_level: int
max_gpu_level: int
tint: Vec

Also known as “Render Colour”.

renderfx: int

Also known as “Render FX”, the alpha value used for the prop.

color_var: str | None

Strata Source addition. If set, this overrides the tint with a game-specified value. Mutually exclusive with the regular tint.

disable_on_xbox: bool
lightmap_x: int
lightmap_y: int
class srctools.bsp.StaticPropFlags

Bases: Flag

Bitflags 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_DRAW

This 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_LIGHTMAP

Disable affecting projected texture lighting. In games supporting lightmapped props (TF2), this instead disables per-luxel lighting.

BOUNCED_LIGHTING = 0x400

Bounce lighting off the prop.

STRATA_COLORVAR_TINT = 0x800

Strata Source addition, uses a ‘color var’ for the tint. This is automatically set/unset depending on the StaticProp.color_var attribute during export.

property value_prim: int

Return the data for the original flag byte.

property value_sec: int

Return the data for the secondary flag byte.

class srctools.bsp.StaticPropVersion

Bases: Enum

The 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: DEFAULT

Adds 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 7 but is identical. Based on v6, adds lightmapped props. Despite the version number, this is more like v6.

V_LIGHTMAP_v10 = (10, 72)
V_LIGHTMAP_MESA = (11, 80, 'Mesa')

Adds rendercolor to V10

V_STRATA_V12 = (12, 80)

Alias: V_CHAOS_V12

Changes the leaf list from uint16 to uint32.

V_STRATA_V13 = (13, 88)

Alias: V_CHAOS_V13

Changes scale from one float to three for non-uniform scaling support.

UNKNOWN = (0, 0, 'unknown')

Indicates the lump has not been parsed.

property is_lightmap: bool

Check if this has lightmaps version.

property is_sdk_2013: bool

Check if this is either Source 2013 version, not including Mesa’s modified one.

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: Enum

The 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.

origin: Vec

World position of the prop.

angles: Angle
orientation: DetailPropOrientation

Determines if and how the prop is affected by the camera direction.

leaf: int
lighting: tuple[int, int, int, int]
sway_amount: int

The amount that the prop can sway by. 255 is max sway, 0 is no movement.

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.

model: str
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.

sprite_scale: float
dims_upper_left: tuple[float, float]
dims_lower_right: tuple[float, float]
texcoord_upper_left: tuple[float, float]

(U, V) coordinates for the upper-left corner of the sprite, in the detail material.

texcoord_lower_right: tuple[float, float]

(U, V) coordinates for the lower-right corner of the sprite, in the detail material.

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.

is_cross: bool

If true, the prop is composed of two planes in a X shape. If false, it is composed of three planes, in a triangle that slightly overlaps.

shape_angle: int

For triangle shapes, the number of degrees to bend outwards. (0-255)

shape_size: int

For triangle shapes, how far each plane should be offset from the center. 0 is no offset, 255 is the same as the overall width.

Cubemaps

BSP.cubemaps: list[Cubemap]

env_cubemap entities are parsed out of the VMF and stored in their own lump.

class srctools.bsp.Cubemap(origin: Vec, size: int = 0)

A env_cubemap positioned in the map.

The position is integral, and the size can be zero for the default or a positive number for different powers of 2.

origin: Vec

Always integer coordinates

size: int

Resolution to use for the cubemap textures.

property resolution: int

Return the actual image size.

Overlays

BSP.overlays: list[Overlay]

info_overlay s are stored in this special lump.

class srctools.bsp.Overlay(
*,
id: int,
origin: Vec,
normal: Vec,
basis_u: FrozenVec,
basis_v_flipped: bool = False,
texture: TexInfo,
faces: list[int] = ...,
render_order: int = 0,
tint: Color = Color(r=255, g=255, b=255, a=255),
color_var: str | None = None,
u_min: float = 0.0,
u_max: float = 1.0,
v_min: float = 0.0,
v_max: float = 1.0,
uv1: FrozenVec = FrozenVec(-16, -16, 0),
uv2: FrozenVec = FrozenVec(-16, 16, 0),
uv3: FrozenVec = FrozenVec(16, 16, 0),
uv4: FrozenVec = FrozenVec(16, -16, 0),
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.

id: int
origin: Vec
normal: Vec
basis_u: FrozenVec
basis_v_flipped: bool

The basis V value is calculated from cross(normal, basis_u). This indicates if it should then be inverted.

texture: TexInfo
faces: list[int]
render_order: int
tint: Color

Strata Source addition, a tint for the overlay.

color_var: str | None

Strata Source addition. If set, this overrides the tint with a game-specified value. Mutually exclusive with the regular tint.

u_min: float
u_max: float
v_min: float
v_max: float
uv1: FrozenVec

Four corner handles of the overlay. Z is unused - the basis U/V values are stored here.

uv2: FrozenVec
uv3: FrozenVec
uv4: FrozenVec
fade_min_sq: float
fade_max_sq: float
min_cpu: int

If system exceeds these limits, the overlay is skipped. Each is a single byte.

max_cpu: int
min_gpu: int
max_gpu: int
property basis_v: FrozenVec

The basis V value is calculated from the normal and basis u.

property fade_min: float

Non-squared version of the fade_min value.

property fade_max: float

Non-squared version of the fade_max value.

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.

potentially_visible: list[bytearray]

For each cluster, an array of bits indicating which leaf can see each other.

potentially_audible: list[bytearray]

For each cluster, an array of bits indicating which leaf can hear each other.

BSP.is_potentially_visible(
leaf1: VisLeaf,
leaf2: VisLeaf,
) tuple[bool, bool]

Check if the first leaf can potentially see and hear the second, in that order.

Always returns True if visibility data has not been computed (self.visibility is None).

BSP.set_potentially_visible(
leaf1: VisLeaf,
leaf2: VisLeaf,
visible: bool | None = None,
audible: bool | None = None,
) None

Override whether the first leaf can see/hear the second.

If either parameter is None that value is left unaltered. Does nothing if BSP.visibility is None.

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.

BSP.visleafs: list[VisLeaf]

The array of all visleafs.

BSP.nodes: list[VisTree]

The array of nodes, each splitting the parent into two children.

BSP.vis_tree() VisTree

Parse the visleaf data, and return the root node.

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.

plane: Plane

The plane the tree is cut on.

mins: Vec
maxes: Vec

The bounding box for the tree.

child_neg: VisTree | VisLeaf

The child on the negative side of the plane.

child_pos: VisTree | VisLeaf

The child on the positive side of the plane.

area_ind: int

Index of the ‘area’ this tree is located in. Each area is entirely separated by areaportals and solid geometry.

faces: list[Face]

All brush faces present in this tree node.

test_point(point: Vec) VisLeaf | None

Test the given point against us, returning the hit leaf or None.

for visleaf in iter_leafs() Iterator[VisLeaf]

Iterate over all child leafs, recursively.

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
cluster_id: int
area: int

Each ‘area’ is an isolated section of map, separated by world geometry or areaportals.

flags: VisLeafFlags
mins: Vec
maxes: Vec
faces: list[Face]
brushes: list[Brush]
water_id: int
min_water_dist: int
test_point(point: Vec) VisLeaf | None

Test the given point against us, returning ourselves or None.

This is used by the corresponding method in VisTree to find hits.

class srctools.bsp.VisLeafFlags

Bases: Flag

Visleaf 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.

class srctools.bsp.LeafWaterInfo(surface_z: float, min_z: float, surface_texinfo: TexInfo)

Additional data about water volumes.

surface_z: float

The height of the water surface.

min_z: float

The lowest bottom in the water volume.

surface_texinfo: TexInfo

The material used for the water surface.

Planes

BSP.planes: list[Plane]
class srctools.bsp.Plane(normal: Vec, dist)

A plane.

normal: Vec
dist: float
property type: PlaneType

Return the plane type, calculating if necessary.

copy() Plane

Return a copy of this plane.

intersect_line(
a: VecT,
b: Vec | FrozenVec,
) VecT | None

Given a line segment, calculate the point where it intersects the plane.

class srctools.bsp.PlaneType

Bases: Enum

The orientation of a plane.

classmethod from_normal(normal: Vec) PlaneType

Compute the correct orientation for a normal.

X = 0

Exactly in the X axis.

Y = 1

Exactly in the Y axis.

Z = 2

Exactly in the Z axis.

ANY_X = 3

Pointing mostly in the X axis

ANY_Y = 4

Pointing mostly in the Y axis.

ANY_Z = 5

Pointing mostly in the Z axis.

Brushes

The brushes lump contains a copy of all the original brushes in the map. This is mainly used for collision.

BSP.brushes: list[Brush]
class srctools.bsp.Brush(contents: BSPContents, sides: list[BrushSide])

A brush definition.

contents: BSPContents
sides: list[BrushSide]
class srctools.bsp.BrushSide(
plane: Plane,
texinfo: TexInfo,
dispinfo: int,
is_bevel_plane: bool,
unknown_bevel_bits: int = 0,
)

A side of the original brush geometry which the map is constructed from.

This matches the original VMF.

plane: Plane
texinfo: TexInfo
is_bevel_plane: bool

If true, this is an artificial axial face, inserted so that all sides of the bounding box surrounding the brush exists. This is necessary to allow for ‘inflating’ the brush for efficient collision detection.

Faces

BSP.faces: list[Face]
BSP.orig_faces: list[Face]

These faces more closely match those in Hammer.

BSP.hdr_faces: list[Face]

Face data used in HDR mode.

BSP.primitives

Primitive surfaces, also known as ‘t-junctions’ or ‘waterverts’ are generated to stitch together T-junction faces. This fixes potential seams.

BSP.surfedges: list[Edge]
BSP.vertexes: list[Vec]
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.

plane: Plane
same_dir_as_plane: bool
on_node: bool
edges: list[Edge]
texinfo: TexInfo | None
surf_fog_volume_id: int
light_styles: bytes
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

The original ID of the Hammer face.

vitamin_flags: int

VitaminSource-specific flags.

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.

is_tristrip: bool
indexed_verts: list[int]
verts: list[Vec]
class srctools.bsp.Edge(a: Vec, b: Vec)

A pair of vertexes defining an edge of a face.

The face on the other side of the edge has a RevEdge instead, which shares these vectors.

opposite: Edge
property a: Vec
property b: Vec
key() tuple[object, ...]

A key to match the edge with.

class srctools.bsp.RevEdge(ed: Edge)

The edge on the opposite side from the original.

This is implicitly created when an Edge is.

property a: Vec

This is a proxy for our opposite’s B vec.

property b: Vec

This is a proxy for our opposite’s A vec.

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.const.BSPContents

This enum specifies collision types for brushes. It is reimported in the BSP module for convenience.