Пример #1
0
def build_itemclass_dict(prop_block: Property):
    """Load in the item ID database.

    This maps item IDs to their item class, and their embed locations.
    """
    for prop in prop_block.find_children('ItemClasses'):
        try:
            it_class = consts.ItemClass(prop.value)
        except KeyError:
            LOGGER.warning('Unknown item class "{}"', prop.value)
            continue

        ITEMS_WITH_CLASS[it_class].append(prop.name)
        CLASS_FOR_ITEM[prop.name] = it_class

    # Now load in the embed data.
    for prop in prop_block.find_children('ItemEmbeds'):
        if prop.name not in CLASS_FOR_ITEM:
            LOGGER.warning('Unknown item ID with embeds "{}"!', prop.real_name)

        vecs = EMBED_OFFSETS.setdefault(prop.name, [])
        if ':' in prop.value:
            first, last = prop.value.split(':')
            bbox_min, bbox_max = Vec.bbox(Vec.from_str(first),
                                          Vec.from_str(last))
            vecs.extend(Vec.iter_grid(bbox_min, bbox_max))
        else:
            vecs.append(Vec.from_str(prop.value))

    LOGGER.info(
        'Read {} item IDs, with {} embeds!',
        len(ITEMS_WITH_CLASS),
        len(EMBED_OFFSETS),
    )
Пример #2
0
def flag_blockpos_type(inst: Entity, flag: Property):
    """Determine the type of a grid position.

    If the value is single value, that should be the type.
    Otherwise, the value should be a block with 'offset' and 'type' values.
    The offset is in block increments, with 0 0 0 equal to the mounting surface.
    If 'offset2' is also provided, all positions in the bounding box will
    be checked.

    The type should be a space-seperated list of locations:

    * `VOID` (Outside the map)
    * `SOLID` (Full wall cube)
    * `EMBED` (Hollow wall cube)
    * `AIR` (Inside the map, may be occupied by items)
    * `OCCUPIED` (Known to be occupied by items)
    * `PIT` (Bottomless pits, any)
        * `PIT_SINGLE` (one-high)
        * `PIT_TOP`
        * `PIT_MID`
        * `PIT_BOTTOM`
    * `GOO`
        * `GOO_SINGLE` (one-deep goo)
        * `GOO_TOP` (goo surface)
        * `GOO_MID`
        * `GOO_BOTTOM` (floor)
    """
    pos2 = None

    if flag.has_children():
        pos1 = resolve_offset(inst,
                              flag['offset', '0 0 0'],
                              scale=128,
                              zoff=-128)
        types = flag['type'].split()
        if 'offset2' in flag:
            pos2 = resolve_offset(inst, flag.value, scale=128, zoff=-128)
    else:
        types = flag.value.split()
        pos1 = Vec()

    if pos2 is not None:
        bbox = Vec.iter_grid(*Vec.bbox(pos1, pos2), stride=128)
    else:
        bbox = [pos1]

    for pos in bbox:
        block = brushLoc.POS['world':pos]
        for block_type in types:
            try:
                allowed = brushLoc.BLOCK_LOOKUP[block_type.casefold()]
            except KeyError:
                raise ValueError(
                    '"{}" is not a valid block type!'.format(block_type))
            if block in allowed:
                break  # To next position
        else:
            return False  # Didn't match any in this list.
    return True  # Matched all positions.
Пример #3
0
def flag_blockpos_type(inst: Entity, flag: Property):
    """Determine the type of a grid position.

    If the value is single value, that should be the type.
    Otherwise, the value should be a block with 'offset' and 'type' values.
    The offset is in block increments, with 0 0 0 equal to the mounting surface.
    If 'offset2' is also provided, all positions in the bounding box will
    be checked.

    The type should be a space-seperated list of locations:
    * `VOID` (Outside the map)
    * `SOLID` (Full wall cube)
    * `EMBED` (Hollow wall cube)
    * `AIR` (Inside the map, may be occupied by items)
    * `OCCUPIED` (Known to be occupied by items)
    * `PIT` (Bottomless pits, any)
      * `PIT_SINGLE` (one-high)
      * `PIT_TOP`
      * `PIT_MID`
      * `PIT_BOTTOM`
    * `GOO`
      * `GOO_SINGLE` (one-deep goo)
      * `GOO_TOP` (goo surface)
      * `GOO_MID`
      * `GOO_BOTTOM` (floor)
    """
    pos2 = None

    if flag.has_children():
        pos1 = resolve_offset(inst, flag['offset', '0 0 0'], scale=128, zoff=-128)
        types = flag['type'].split()
        if 'offset2' in flag:
            pos2 = resolve_offset(inst, flag.value, scale=128, zoff=-128)
    else:
        types = flag.value.split()
        pos1 = Vec()

    if pos2 is not None:
        bbox = Vec.iter_grid(*Vec.bbox(pos1, pos2), stride=128)
    else:
        bbox = [pos1]

    for pos in bbox:
        block = brushLoc.POS['world': pos]
        for block_type in types:
            try:
                allowed = brushLoc.BLOCK_LOOKUP[block_type.casefold()]
            except KeyError:
                raise ValueError('"{}" is not a valid block type!'.format(block_type))
            if block in allowed:
                break  # To next position
        else:
            return False  # Didn't match any in this list.
    return True  # Matched all positions.
Пример #4
0
def res_set_block(inst: Entity, res: Property) -> None:
    """Set a block to the given value, overwriting the existing value.

    - `type` is the type of block to set:
        * `VOID` (Outside the map)
        * `SOLID` (Full wall cube)
        * `EMBED` (Hollow wall cube)
        * `AIR` (Inside the map, may be occupied by items)
        * `OCCUPIED` (Known to be occupied by items)
        * `PIT_SINGLE` (one-high)
        * `PIT_TOP`
        * `PIT_MID`
        * `PIT_BOTTOM`
        * `GOO_SINGLE` (one-deep goo)
        * `GOO_TOP` (goo surface)
        * `GOO_MID`
        * `GOO_BOTTOM` (floor)
    - `offset` is in block increments, with `0 0 0` equal to the mounting surface.
    - If 'offset2' is also provided, all positions in the bounding box will be set.
    """
    try:
        new_vals = brushLoc.BLOCK_LOOKUP[res['type'].casefold()]
    except KeyError:
        raise ValueError('"{}" is not a valid block type!'.format(res['type']))

    try:
        [new_val] = new_vals
    except ValueError:
        # TODO: This could spread top/mid/bottom through the bbox...
        raise ValueError(
            f'Can\'t use compound block type "{res["type"]}", specify '
            "_SINGLE/TOP/MID/BOTTOM"
        )

    pos1 = resolve_offset(inst, res['offset', '0 0 0'], scale=128, zoff=-128)

    if 'offset2' in res:
        pos2 = resolve_offset(inst, res['offset2', '0 0 0'], scale=128, zoff=-128)
        for pos in Vec.iter_grid(*Vec.bbox(pos1, pos2), stride=128):
            brushLoc.POS['world': pos] = new_val
    else:
        brushLoc.POS['world': pos1] = new_val
Пример #5
0
def load_embeddedvoxel(item: Item, ent: Entity) -> None:
    """Parse embed definitions contained in the VMF."""
    bbox_min, bbox_max = ent.get_bbox()
    bbox_min = round(bbox_min, 0)
    bbox_max = round(bbox_max, 0)

    if bbox_min % 128 != (64.0, 64.0, 64.0) or bbox_max % 128 != (64.0, 64.0,
                                                                  64.0):
        LOGGER.warning(
            'Embedded voxel definition ({}) - ({}) is not aligned to grid!',
            bbox_min,
            bbox_max,
        )
        return

    item.embed_voxels.update(
        map(
            Coord.from_vec,
            Vec.iter_grid(
                (bbox_min + (64, 64, 64)) / 128,
                (bbox_max - (64, 64, 64)) / 128,
            )))
Пример #6
0
# Position -> entity
# We merge ones within 3 blocks of our item.
CHECKPOINT_TRIG = {}  # type: Dict[Tuple[float, float, float], Entity]

# Approximately a 3-distance from
# the center.
#   x
#  xxx
# xx xx
#  xxx
#   x
CHECKPOINT_NEIGHBOURS = list(
    Vec.iter_grid(
        Vec(-128, -128, 0),
        Vec(128, 128, 0),
        stride=128,
    ))
CHECKPOINT_NEIGHBOURS.extend([
    Vec(-256, 0, 0),
    Vec(256, 0, 0),
    Vec(0, -256, 0),
    Vec(0, 256, 0),
])
# Don't include ourself..
CHECKPOINT_NEIGHBOURS.remove(Vec(0, 0, 0))


@make_result('CheckpointTrigger')
def res_checkpoint_trigger(inst: Entity, res: Property):
    """Generate a trigger underneath coop checkpoint items
Пример #7
0
def brush_at_loc(
    inst: Entity,
    props: Property,
) -> Tuple[tiling.TileType, bool, Set[tiling.TileType]]:
    """Common code for posIsSolid and ReadSurfType.

    This returns the average tiletype, if both colors were found,
    and a set of all types found.
    """
    origin = Vec.from_str(inst['origin'])
    angles = Vec.from_str(inst['angles'])

    # Allow using pos1 instead, to match pos2.
    pos = props.vec('pos1' if 'pos1' in props else 'pos')
    pos.z -= 64  # Subtract so origin is the floor-position

    pos.localise(origin, angles)

    norm = props.vec('dir', 0, 0, 1).rotate(*angles)

    if props.bool('gridpos') and norm is not None:
        for axis in 'xyz':
            # Don't realign things in the normal's axis -
            # those are already fine.
            if norm[axis] == 0:
                pos[axis] = pos[axis] // 128 * 128 + 64

    result_var = props['setVar', '']
    # RemoveBrush is the pre-tiling name.
    should_remove = props.bool('RemoveTile', props.bool('RemoveBrush', False))

    tile_types: Set[tiling.TileType] = set()
    both_colors = False

    if 'pos2' in props:
        pos2 = props.vec('pos2')
        pos2.z -= 64  # Subtract so origin is the floor-position
        pos2.localise(origin, angles)

        bbox_min, bbox_max = Vec.bbox(pos, pos2)

        white_count = black_count = 0

        for pos in Vec.iter_grid(bbox_min, bbox_max, 32):
            try:
                tiledef, u, v = tiling.find_tile(pos, norm)
            except KeyError:
                continue

            tile_type = tiledef[u, v]
            tile_types.add(tile_type)
            if should_remove:
                tiledef[u, v] = tiling.TileType.VOID
            if tile_type.is_tile:
                if tile_type.color is tiling.Portalable.WHITE:
                    white_count += 1
                else:
                    black_count += 1

        both_colors = white_count > 0 and black_count > 0

        if white_count == black_count == 0:
            tile_type = tiling.TileType.VOID
            tile_types.add(tiling.TileType.VOID)
        elif white_count > black_count:
            tile_type = tiling.TileType.WHITE
        else:
            tile_type = tiling.TileType.BLACK
    else:
        # Single tile.
        try:
            tiledef, u, v = tiling.find_tile(pos, norm)
        except KeyError:
            tile_type = tiling.TileType.VOID
        else:
            tile_type = tiledef[u, v]
            if should_remove:
                tiledef[u, v] = tiling.TileType.VOID
        tile_types.add(tile_type)

    if result_var:
        if tile_type.is_tile:
            # Don't distinguish between 4x4, goo sides
            inst.fixup[result_var] = tile_type.color.value
        elif tile_type is tiling.TileType.VOID:
            inst.fixup[result_var] = 'none'
        else:
            inst.fixup[result_var] = tile_type.name.casefold()

    return tile_type, both_colors, tile_types
Пример #8
0
def load_occupiedvoxel(item: Item, ent: Entity) -> None:
    """Parse voxel collisions contained in the VMF."""
    bbox_min, bbox_max = ent.get_bbox()
    bbox_min = round(bbox_min, 0)
    bbox_max = round(bbox_max, 0)

    coll_type = parse_colltype(ent['coll_type'])
    if ent['coll_against']:
        coll_against = parse_colltype(ent['coll_against'])
    else:
        coll_against = None

    if bbox_min % 128 == (64.0, 64.0, 64.0) and bbox_max % 128 == (64.0, 64.0,
                                                                   64.0):
        # Full voxels.
        for voxel in Vec.iter_grid(
            (bbox_min + (64, 64, 64)) / 128,
            (bbox_max - (64, 64, 64)) / 128,
        ):
            item.occupy_voxels.add(
                OccupiedVoxel(
                    coll_type,
                    coll_against,
                    Coord.from_vec(voxel),
                ))
        return
    elif bbox_min % 32 == (0.0, 0.0, 0.0) and bbox_max % 32 == (0.0, 0.0, 0.0):
        # Subvoxel sections.
        for subvoxel in Vec.iter_grid(
                bbox_min / 32,
            (bbox_max - (32.0, 32.0, 32.0)) / 32,
        ):
            item.occupy_voxels.add(
                OccupiedVoxel(
                    coll_type,
                    coll_against,
                    Coord.from_vec((subvoxel + (2, 2, 2)) // 4),
                    Coord.from_vec((subvoxel - (2, 2, 2)) % 4),
                ))
        return
    # else, is this a surface definition?
    size = round(bbox_max - bbox_min, 0)
    for axis in ['x', 'y', 'z']:
        if size[axis] < 8:
            u, v = Vec.INV_AXIS[axis]
            # Figure out if we're aligned to the min or max side of the voxel.
            # Compute the normal, then flatten to zero thick.
            if bbox_min[axis] % 32 == 0:
                norm = +1
                plane_dist = bbox_max[axis] = bbox_min[axis]
            elif bbox_max[axis] % 32 == 0:
                norm = -1
                plane_dist = bbox_min[axis] = bbox_max[axis]
            else:
                # Both faces aren't aligned to the grid, skip to error.
                break

            if bbox_min[u] % 128 == bbox_min[v] % 128 == bbox_max[
                    v] % 128 == bbox_max[v] % 128 == 64.0:
                # Full voxel surface definitions.
                for voxel in Vec.iter_grid(
                        Vec.with_axes(u, bbox_min[u] + 64, v, bbox_min[v] + 64,
                                      axis, plane_dist + 64 * norm) / 128,
                        Vec.with_axes(u, bbox_max[u] - 64, v, bbox_max[v] - 64,
                                      axis, plane_dist + 64 * norm) / 128,
                ):
                    item.occupy_voxels.add(
                        OccupiedVoxel(
                            coll_type,
                            coll_against,
                            Coord.from_vec(voxel),
                            normal=Coord.from_vec(Vec.with_axes(axis, norm)),
                        ))
                return
            elif bbox_min[u] % 32 == bbox_min[v] % 32 == bbox_max[
                    v] % 32 == bbox_max[v] % 32 == 0.0:
                # Subvoxel surface definitions.
                return
            else:
                # Not aligned to grid, skip to error.
                break

    LOGGER.warning(
        'Unknown occupied voxel definition: ({}) - ({}), type="{}", against="{}"',
        bbox_min,
        bbox_max,
        ent['coll_type'],
        ent['coll_against'],
    )
Пример #9
0
    )

# Position -> entity
# We merge ones within 3 blocks of our item.
CHECKPOINT_TRIG = {}  # type: Dict[Tuple[float, float, float], Entity]

# Approximately a 3-distance from
# the center.
#   x
#  xxx
# xx xx
#  xxx
#   x
CHECKPOINT_NEIGHBOURS = list(Vec.iter_grid(
    Vec(-128, -128, 0),
    Vec(128, 128, 0),
    stride=128,
))
CHECKPOINT_NEIGHBOURS.extend([
    Vec(-256, 0, 0),
    Vec(256, 0, 0),
    Vec(0, -256, 0),
    Vec(0, 256, 0),
])
# Don't include ourself..
CHECKPOINT_NEIGHBOURS.remove(Vec(0, 0, 0))


@make_result('CheckpointTrigger')
def res_checkpoint_trigger(inst: Entity, res: Property):
    """Generate a trigger underneath coop checkpoint items
Пример #10
0
def improve_item(item: Property) -> None:
    """Improve editoritems formats in various ways.

    This operates inplace.
    """
    # OccupiedVoxels does not allow specifying 'volume' regions like
    # EmbeddedVoxel. Implement that.

    # First for 32^2 cube sections.
    for voxel_part in item.find_all("Exporting", "OccupiedVoxels",
                                    "SurfaceVolume"):
        if 'subpos1' not in voxel_part or 'subpos2' not in voxel_part:
            LOGGER.warning(
                'Item {} has invalid OccupiedVoxels part '
                '(needs SubPos1 and SubPos2)!',
                item['type'],
            )
            continue
        voxel_part.name = "Voxel"
        pos_1 = None
        voxel_subprops = list(voxel_part)
        voxel_part.clear()
        for prop in voxel_subprops:
            if prop.name not in ('subpos', 'subpos1', 'subpos2'):
                voxel_part.append(prop)
                continue
            pos_2 = Vec.from_str(prop.value)
            if pos_1 is None:
                pos_1 = pos_2
                continue

            bbox_min, bbox_max = Vec.bbox(pos_1, pos_2)
            pos_1 = None
            for pos in Vec.iter_grid(bbox_min, bbox_max):
                voxel_part.append(
                    Property("Surface", [
                        Property("Pos", str(pos)),
                    ]))
        if pos_1 is not None:
            LOGGER.warning(
                'Item {} has only half of SubPos bbox!',
                item['type'],
            )

    # Full blocks
    for occu_voxels in item.find_all("Exporting", "OccupiedVoxels"):
        for voxel_part in list(occu_voxels.find_all("Volume")):
            del occu_voxels['Volume']

            if 'pos1' not in voxel_part or 'pos2' not in voxel_part:
                LOGGER.warning(
                    'Item {} has invalid OccupiedVoxels part '
                    '(needs Pos1 and Pos2)!', item['type'])
                continue
            voxel_part.name = "Voxel"
            bbox_min, bbox_max = Vec.bbox(
                voxel_part.vec('pos1'),
                voxel_part.vec('pos2'),
            )
            del voxel_part['pos1']
            del voxel_part['pos2']
            for pos in Vec.iter_grid(bbox_min, bbox_max):
                new_part = voxel_part.copy()
                new_part['Pos'] = str(pos)
                occu_voxels.append(new_part)
Пример #11
0
    def setup(self, global_seed: str, tiles: List['TileDef']):
        """Build the list of clump locations."""
        assert self.portal is not None
        assert self.orient is not None

        # Convert the generator key to a generator-specific seed.
        # That ensures different surfaces don't end up reusing the same
        # texture indexes.
        self.gen_seed = int.from_bytes(
            self.category.name.encode() + self.portal.name.encode() +
            self.orient.name.encode(),
            'big',
        )

        LOGGER.info('Generating texture clumps...')

        clump_length: int = self.options['clump_length']
        clump_width: int = self.options['clump_width']

        # The tiles currently present in the map.
        orient_z = self.orient.z
        remaining_tiles: Set[Tuple[float, float, float]] = {
            (tile.pos + 64 * tile.normal // 128 * 128).as_tuple()
            for tile in tiles if tile.normal.z == orient_z
        }

        # A global RNG for picking clump positions.
        clump_rand = random.Random(global_seed + '_clumping')

        pos_min = Vec()
        pos_max = Vec()

        # For debugging, generate skip brushes with the shape of the clumps.
        debug_visgroup: Optional[VisGroup]
        if self.options['clump_debug']:
            import vbsp
            debug_visgroup = vbsp.VMF.create_visgroup(
                f'{self.category.name}_{self.orient.name}_{self.portal.name}')
        else:
            debug_visgroup = None

        while remaining_tiles:
            # Pick from a random tile.
            tile_pos = next(
                itertools.islice(
                    remaining_tiles,
                    clump_rand.randrange(0, len(remaining_tiles)),
                    len(remaining_tiles),
                ))
            remaining_tiles.remove(tile_pos)

            pos = Vec(tile_pos)

            # Clumps are long strips mainly extended in one direction
            # In the other directions extend by 'width'. It can point any axis.
            direction = clump_rand.choice('xyz')
            for axis in 'xyz':
                if axis == direction:
                    dist = clump_length
                else:
                    dist = clump_width
                pos_min[axis] = pos[axis] - clump_rand.randint(0, dist) * 128
                pos_max[axis] = pos[axis] + clump_rand.randint(0, dist) * 128

            remaining_tiles.difference_update(
                map(Vec.as_tuple, Vec.iter_grid(pos_min, pos_max, 128)))

            self._clump_locs.append(
                Clump(
                    pos_min.x,
                    pos_min.y,
                    pos_min.z,
                    pos_max.x,
                    pos_max.y,
                    pos_max.z,
                    # We use this to reseed an RNG, giving us the same textures
                    # each time for the same clump.
                    clump_rand.getrandbits(32),
                ))
            if debug_visgroup is not None:
                # noinspection PyUnboundLocalVariable
                debug_brush: Solid = vbsp.VMF.make_prism(
                    pos_min - 64,
                    pos_max + 64,
                    'tools/toolsskip',
                ).solid
                debug_brush.visgroup_ids.add(debug_visgroup.id)
                debug_brush.vis_shown = False
                vbsp.VMF.add_brush(debug_brush)

        LOGGER.info(
            '{}.{}.{}: {} Clumps for {} tiles',
            self.category.name,
            self.orient.name,
            self.portal.name,
            len(self._clump_locs),
            len(tiles),
        )
Пример #12
0
def edit_panel(vmf: VMF, inst: Entity, props: Property, create: bool) -> None:
    """Implements SetPanelOptions and CreatePanel."""
    orient = Matrix.from_angle(Angle.from_str(inst['angles']))
    normal: Vec = round(props.vec('normal', 0, 0, 1) @ orient, 6)
    origin = Vec.from_str(inst['origin'])
    uaxis, vaxis = Vec.INV_AXIS[normal.axis()]

    points: Set[Tuple[float, float, float]] = set()

    if 'point' in props:
        for prop in props.find_all('point'):
            points.add(
                conditions.resolve_offset(inst, prop.value,
                                          zoff=-64).as_tuple())
    elif 'pos1' in props and 'pos2' in props:
        pos1, pos2 = Vec.bbox(
            conditions.resolve_offset(inst,
                                      props['pos1', '-48 -48 0'],
                                      zoff=-64),
            conditions.resolve_offset(inst, props['pos2', '48 48 0'],
                                      zoff=-64),
        )
        points.update(map(Vec.as_tuple, Vec.iter_grid(pos1, pos2, 32)))
    else:
        # Default to the full tile.
        points.update({(Vec(u, v, -64.0) @ orient + origin).as_tuple()
                       for u in [-48.0, -16.0, 16.0, 48.0]
                       for v in [-48.0, -16.0, 16.0, 48.0]})

    tiles_to_uv: Dict[tiling.TileDef, Set[Tuple[int, int]]] = defaultdict(set)
    for pos in points:
        try:
            tile, u, v = tiling.find_tile(Vec(pos), normal, force=create)
        except KeyError:
            continue
        tiles_to_uv[tile].add((u, v))

    if not tiles_to_uv:
        LOGGER.warning('"{}": No tiles found for panels!', inst['targetname'])
        return

    # If bevels is provided, parse out the overall world positions.
    bevel_world: Optional[Set[Tuple[int, int]]]
    try:
        bevel_prop = props.find_key('bevel')
    except NoKeyError:
        bevel_world = None
    else:
        bevel_world = set()
        if bevel_prop.has_children():
            # Individually specifying offsets.
            for bevel_str in bevel_prop.as_array():
                bevel_point = Vec.from_str(bevel_str) @ orient + origin
                bevel_world.add(
                    (int(bevel_point[uaxis]), int(bevel_point[vaxis])))
        elif srctools.conv_bool(bevel_prop.value):
            # Fill the bounding box.
            bbox_min, bbox_max = Vec.bbox(map(Vec, points))
            off = Vec.with_axes(uaxis, 32, vaxis, 32)
            bbox_min -= off
            bbox_max += off
            for pos in Vec.iter_grid(bbox_min, bbox_max, 32):
                if pos.as_tuple() not in points:
                    bevel_world.add((pos[uaxis], pos[vaxis]))
        # else: No bevels.
    panels: List[tiling.Panel] = []

    for tile, uvs in tiles_to_uv.items():
        if create:
            panel = tiling.Panel(
                None,
                inst,
                tiling.PanelType.NORMAL,
                thickness=4,
                bevels=(),
            )
            panel.points = uvs
            tile.panels.append(panel)
        else:
            for panel in tile.panels:
                if panel.same_item(inst) and panel.points == uvs:
                    break
            else:
                LOGGER.warning('No panel to modify found for "{}"!',
                               inst['targetname'])
                continue
        panels.append(panel)

        pan_type = '<nothing?>'
        try:
            pan_type = conditions.resolve_value(inst, props['type'])
            panel.pan_type = tiling.PanelType(pan_type.lower())
        except LookupError:
            pass
        except ValueError:
            raise ValueError('Unknown panel type "{}"!'.format(pan_type))

        if 'thickness' in props:
            panel.thickness = srctools.conv_int(
                conditions.resolve_value(inst, props['thickness']))
            if panel.thickness not in (2, 4, 8):
                raise ValueError(
                    '"{}": Invalid panel thickess {}!\n'
                    'Must be 2, 4 or 8.',
                    inst['targetname'],
                    panel.thickness,
                )

        if bevel_world is not None:
            panel.bevels.clear()
            for u, v in bevel_world:
                # Convert from world points to UV positions.
                u = (u - tile.pos[uaxis] + 48) / 32
                v = (v - tile.pos[vaxis] + 48) / 32
                # Cull outside here, we wont't use them.
                if -1 <= u <= 4 and -1 <= v <= 4:
                    panel.bevels.add((u, v))

        if 'offset' in props:
            panel.offset = conditions.resolve_offset(inst, props['offset'])
            panel.offset -= Vec.from_str(inst['origin'])
        if 'template' in props:
            # We only want the template inserted once. So remove it from all but one.
            if len(panels) == 1:
                panel.template = conditions.resolve_value(
                    inst, props['template'])
            else:
                panel.template = ''
        if 'nodraw' in props:
            panel.nodraw = srctools.conv_bool(
                conditions.resolve_value(inst, props['nodraw']))
        if 'seal' in props:
            panel.seal = srctools.conv_bool(
                conditions.resolve_value(inst, props['seal']))
        if 'move_bullseye' in props:
            panel.steals_bullseye = srctools.conv_bool(
                conditions.resolve_value(inst, props['move_bullseye']))
    if 'keys' in props or 'localkeys' in props:
        # First grab the existing ent, so we can edit it.
        # These should all have the same value, unless they were independently
        # edited with mismatching point sets. In that case destroy all those existing ones.
        existing_ents: Set[Optional[Entity]] = {
            panel.brush_ent
            for panel in panels
        }
        try:
            [brush_ent] = existing_ents
        except ValueError:
            LOGGER.warning(
                'Multiple independent panels for "{}" were made, then the '
                'brush entity was edited as a group! Discarding '
                'individual ents...', inst['targetname'])
            for brush_ent in existing_ents:
                if brush_ent is not None and brush_ent in vmf.entities:
                    brush_ent.remove()
            brush_ent = None

        if brush_ent is None:
            brush_ent = vmf.create_ent('')

        old_pos = brush_ent.keys.pop('origin', None)

        conditions.set_ent_keys(brush_ent, inst, props)
        if not brush_ent['classname']:
            if create:  # This doesn't make sense, you could just omit the prop.
                LOGGER.warning(
                    'No classname provided for panel "{}"!',
                    inst['targetname'],
                )
            # Make it a world brush.
            brush_ent.remove()
            brush_ent = None
        else:
            # We want to do some post-processing.
            # Localise any origin value.
            if 'origin' in brush_ent.keys:
                pos = Vec.from_str(brush_ent['origin'])
                pos.localise(
                    Vec.from_str(inst['origin']),
                    Vec.from_str(inst['angles']),
                )
                brush_ent['origin'] = pos
            elif old_pos is not None:
                brush_ent['origin'] = old_pos

            # If it's func_detail, clear out all the keys.
            # Particularly `origin`, but the others are useless too.
            if brush_ent['classname'] == 'func_detail':
                brush_ent.clear_keys()
                brush_ent['classname'] = 'func_detail'
        for panel in panels:
            panel.brush_ent = brush_ent
Пример #13
0
    )

# Position -> entity
# We merge ones within 3 blocks of our item.
CHECKPOINT_TRIG = {}  # type: Dict[Tuple[float, float, float], Entity]

# Approximately a 3-distance from
# the center.
#   x
#  xxx
# xx xx
#  xxx
#   x
CHECKPOINT_NEIGHBOURS = list(Vec.iter_grid(
    Vec(-128, -128, 0),
    Vec(128, 128, 0),
    stride=128,
))
CHECKPOINT_NEIGHBOURS.extend([
    Vec(-256, 0, 0),
    Vec(256, 0, 0),
    Vec(0, -256, 0),
    Vec(0, 256, 0),
])
# Don't include ourself..
CHECKPOINT_NEIGHBOURS.remove(Vec(0, 0, 0))


@make_result('CheckpointTrigger')
def res_checkpoint_trigger(inst: Entity, res: Property):
    """Generate a trigger underneath coop checkpoint items