snake-biscuits / bsp_tool

Python library for analysing .bsp files
GNU General Public License v3.0
102 stars 8 forks source link

Usage of ContentsMask in source.py #146

Closed DukeVen closed 1 year ago

DukeVen commented 1 year ago

I want compare the content of a brush with ContentsMask - PLAYER_SOLID , but I cant seem to figure out how exactly to do that. https://github.com/snake-biscuits/bsp_tool/blob/master/bsp_tool/branches/valve/source.py#L240

snake-biscuits commented 1 year ago

In theory == should be enough

>>> import bsp_tool
>>> from bsp_tool.branches.valve import source
>>> source.Contents(source.ContentsMask.PLAYER_SOLID)
<Contents.SOLID|WINDOW|GRATE|MOVEABLE|PLAYER_CLIP|MONSTER: 33636363>
>>> _ == source.ContentsMask.PLAYER_SOLID  # exact match
True

But in practice it isn't that simple

>>> bsp = bsp_tool.load_bsp("D:/SteamLibrary/steamapps/common/Team Fortress 2/tf/maps/pl_upward.bsp")
>>> any([b.contents == source.ContentsMask.PLAYER_SOLID for b in bsp.BRUSHES])
False

You have to check if any of the bits inside the ContentsMask are set:

>>> # brush must have at least 1 of these flags:
>>> [c.name for c in source.Contents(source.ContentsMask.PLAYER_SOLID)]
['SOLID', 'WINDOW', 'GRATE', 'MOVEABLE', 'PLAYER_CLIP', 'MONSTER']
>>> [i for i, b in enumerate(bsp.BRUSHES) if b.contents & source.ContentsMask.PLAYER_SOLID != 0][:16]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
>>> {bsp.BRUSHES[i].contents for i in _}
{<Contents.SOLID: 1>}

This line is where the check happens:

>>> [i for i, b in enumerate(bsp.BRUSHES) if b.contents & source.ContentsMask.PLAYER_SOLID != 0][:16]

Breaking it down into 1 line per step:

for i, brush in enumerate(bsp.BRUSHES):
    solid_flags = brush.contents & ContentsMask.PLAYER_SOLID  # apply mask to Contents
    if solid_flags != 0:  # at least 1 flag is set
        print(i)  # this brush is solid to the player
snake-biscuits commented 1 year ago

Iterating over the contents of an enum.IntFlag (like source.Contents) is new in Python 3.11 You could use that do to something like:

PLAYER_SOLID = source.Contents(source.ContentsMask.PLAYER_SOLID)
any([flag in PLAYER_SOLID for flag in brush.contents])

which is a bit more human-readable, tho probably slower performance-wise

DukeVen commented 1 year ago

Thank you! Very helpful