Exemple #1
0
def set_ent_keys(
    ent: Entity,
    inst: Entity,
    prop_block: Property,
    block_name: str='Keys',
) -> None:
    """Copy the given key prop block to an entity.

    This uses the keys and 'localkeys' properties on the prop_block.
    Values with $fixup variables will be treated appropriately.
    LocalKeys keys will be changed to use instance-local names, where needed.
    block_name lets you change the 'keys' suffix on the prop_block name.
    ent can be any mapping.
    """
    for prop in prop_block.find_block(block_name, or_blank=True):
        ent[prop.real_name] = resolve_value(inst, prop.value)
    for prop in prop_block.find_block('Local' + block_name, or_blank=True):
        if prop.value.startswith('$'):
            val = inst.fixup[prop.value]
        else:
            val = prop.value
        if val.startswith('@'):
            ent[prop.real_name] = val
        else:
            ent[prop.real_name] = local_name(inst, val)
Exemple #2
0
def load_handler(props: Property) -> None:
    """Load compiler options from the palette."""
    chosen_thumb.set(props['sshot_type', chosen_thumb.get()])
    cleanup_screenshot.set(
        props.bool('sshot_cleanup', cleanup_screenshot.get()))

    if 'sshot_data' in props:
        screenshot_parts = b'\n'.join([
            prop.value.encode('ascii')
            for prop in props.find_children('sshot_data')
        ])
        screenshot_data = base64.decodebytes(screenshot_parts)
        with atomic_write(SCREENSHOT_LOC, mode='wb', overwrite=True) as f:
            f.write(screenshot_data)

    # Refresh these.
    set_screen_type()
    set_screenshot()

    start_in_elev.set(props.bool('spawn_elev', start_in_elev.get()))

    try:
        player_mdl = props['player_model']
    except LookupError:
        pass
    else:
        player_model_var.set(PLAYER_MODELS[player_mdl])
        COMPILE_CFG['General']['player_model'] = player_mdl

    VOICE_PRIORITY_VAR.set(
        props.bool('voiceline_priority', VOICE_PRIORITY_VAR.get()))

    corr_prop = props.find_block('corridor', or_blank=True)
    for group, win in CORRIDOR.items():
        try:
            sel_id = corr_prop[group]
        except LookupError:
            "No config option, ok."
        else:
            win.sel_item_id(sel_id)
            COMPILE_CFG['Corridor'][
                group] = '0' if sel_id == '<NONE>' else sel_id

    COMPILE_CFG.save_check()
    return None
Exemple #3
0
def load_itemvar(prop: Property) -> None:
    """Load item variables into the palette."""
    for group in CONFIG_ORDER:
        conf = prop.find_block(group.id, or_blank=True)
        for widget in group.widgets:
            if widget.has_values:
                try:
                    widget.values.set(conf[widget.id])
                except LookupError:
                    pass

        for widget in group.multi_widgets:
            time_conf = conf.find_block(widget.id, or_blank=True)
            for tim_val, var in widget.values:
                try:
                    var.set(time_conf[str(tim_val)])
                except LookupError:
                    pass
    return None
Exemple #4
0
def res_import_template(vmf: VMF, coll: Collisions, res: Property):
    """Import a template VMF file, retexturing it to match orientation.

    It will be placed overlapping the given instance. If no block is used, only
    ID can be specified.
    Options:

    - `ID`: The ID of the template to be inserted. Add visgroups to additionally
            add after a colon, comma-seperated (`temp_id:vis1,vis2`).
            Either section, or the whole value can be a `$fixup`.
    - `angles`: Override the instance rotation, so it is always rotated this much.
    - `rotation`: Apply the specified rotation before the instance's rotation.
    - `offset`: Offset the template from the instance's position.
    - `force`: a space-seperated list of overrides. If 'white' or 'black' is
             present, the colour of tiles will be overridden. If `invert` is
            added, white/black tiles will be swapped. If a tile size
            (`2x2`, `4x4`, `wall`, `special`) is included, all tiles will
            be switched to that size (if not a floor/ceiling). If 'world' or
            'detail' is present, the brush will be forced to that type.
    - `replace`: A block of template material -> replacement textures.
            This is case insensitive - any texture here will not be altered
            otherwise. If the material starts with a `#`, it is instead a
            list of face IDs separated by spaces. If the result evaluates
            to "", no change occurs. Both can be $fixups (parsed first).
    - `bindOverlay`: Bind overlays in this template to the given surface, and
            bind overlays on a surface to surfaces in this template.
            The value specifies the offset to the surface, where 0 0 0 is the
            floor position. It can also be a block of multiple positions.
    - `alignBindOverlay`: If set, align the bindOverlay offsets to the grid.
    - `keys`/`localkeys`: If set, a brush entity will instead be generated with
            these values. This overrides force world/detail.
            Specially-handled keys:
            - `"origin"`, offset automatically.
            - `"movedir"` on func_movelinear - set a normal surrounded by `<>`,
              this gets replaced with angles.
    - `colorVar`: If this fixup var is set
            to `white` or `black`, that colour will be forced.
            If the value is `<editor>`, the colour will be chosen based on
            the color of the surface for ItemButtonFloor, funnels or
            entry/exit frames.
    - `invertVar`: If this fixup value is true, tile colour will be
            swapped to the opposite of the current force option. This applies
            after colorVar.
    - `visgroup`: Sets how visgrouped parts are handled. Several values are possible:
            - A property block: Each name should match a visgroup, and the
              value should be a block of flags that if true enables that group.
            - 'none' (default): All extra groups are ignored.
            - 'choose': One group is chosen randomly.
            - a number: The percentage chance for each visgroup to be added.
    - `visgroup_force_var`: If set and True, visgroup is ignored and all groups
            are added.
    - `pickerVars`:
            If this is set, the results of colorpickers can be read
            out of the template. The key is the name of the picker, the value
            is the fixup name to write to. The output is either 'white',
            'black' or ''.
    - `outputs`: Add outputs to the brush ent. Syntax is like VMFs, and all names
            are local to the instance.
    - `senseOffset`: If set, colorpickers and tilesetters will be treated
            as being offset by this amount.
    """
    if res.has_children():
        orig_temp_id = res['id']
    else:
        orig_temp_id = res.value
        res = Property('TemplateBrush', [])

    force = res['force', ''].casefold().split()
    if 'white' in force:
        conf_force_colour = texturing.Portalable.white
    elif 'black' in force:
        conf_force_colour = texturing.Portalable.black
    elif 'invert' in force:
        conf_force_colour = 'INVERT'
    else:
        conf_force_colour = None

    if 'world' in force:
        force_type = template_brush.TEMP_TYPES.world
    elif 'detail' in force:
        force_type = template_brush.TEMP_TYPES.detail
    else:
        force_type = template_brush.TEMP_TYPES.default

    force_grid: texturing.TileSize | None
    size: texturing.TileSize
    for size in texturing.TileSize:
        if size in force:
            force_grid = size
            break
    else:
        force_grid = None

    if 'bullseye' in force:
        surf_cat = texturing.GenCat.BULLSEYE
    elif 'special' in force or 'panel' in force:
        surf_cat = texturing.GenCat.PANEL
    else:
        surf_cat = texturing.GenCat.NORMAL

    replace_tex: dict[str, list[str]] = {}
    for prop in res.find_block('replace', or_blank=True):
        replace_tex.setdefault(prop.name, []).append(prop.value)

    if 'replaceBrush' in res:
        LOGGER.warning(
            'replaceBrush command used for template "{}", which is no '
            'longer used.',
            orig_temp_id,
        )
    bind_tile_pos = [
        # So it's the floor block location.
        Vec.from_str(value) - (0, 0, 128)
        for value in res.find_key('BindOverlay', or_blank=True).as_array()
    ]
    align_bind_overlay = res.bool('alignBindOverlay')

    key_values = res.find_block("Keys", or_blank=True)
    if key_values:
        key_block = Property("", [
            key_values,
            res.find_block("LocalKeys", or_blank=True),
        ])
        # Ensure we have a 'origin' keyvalue - we automatically offset that.
        if 'origin' not in key_values:
            key_values['origin'] = '0 0 0'

        # Spawn everything as detail, so they get put into a brush
        # entity.
        force_type = template_brush.TEMP_TYPES.detail
        outputs = [Output.parse(prop) for prop in res.find_children('Outputs')]
    else:
        key_block = None
        outputs = []

    # None = don't add any more.
    visgroup_func: Callable[[Random, list[str]], Iterable[str]] | None = None

    try:  # allow both spellings.
        visgroup_prop = res.find_key('visgroups')
    except NoKeyError:
        visgroup_prop = res.find_key('visgroup', 'none')
    if visgroup_prop.has_children():
        visgroup_instvars = list(visgroup_prop)
    else:
        visgroup_instvars = []
        visgroup_mode = res['visgroup', 'none'].casefold()
        # Generate the function which picks which visgroups to add to the map.
        if visgroup_mode == 'none':
            pass
        elif visgroup_mode == 'choose':

            def visgroup_func(rng: Random, groups: list[str]) -> Iterable[str]:
                """choose = add one random group."""
                return [rng.choice(groups)]
        else:
            percent = srctools.conv_float(visgroup_mode.rstrip('%'), 0.00)
            if percent > 0.0:

                def visgroup_func(rng: Random,
                                  groups: list[str]) -> Iterable[str]:
                    """Number = percent chance for each to be added"""
                    for group in sorted(groups):
                        if rng.uniform(0, 100) <= percent:
                            yield group

    picker_vars = [(prop.real_name, prop.value)
                   for prop in res.find_children('pickerVars')]
    try:
        ang_override = to_matrix(Angle.from_str(res['angles']))
    except LookupError:
        ang_override = None
    try:
        rotation = to_matrix(Angle.from_str(res['rotation']))
    except LookupError:
        rotation = Matrix()

    offset = res['offset', '0 0 0']
    invert_var = res['invertVar', '']
    color_var = res['colorVar', '']
    if color_var.casefold() == '<editor>':
        color_var = '<editor>'

    # If true, force visgroups to all be used.
    visgroup_force_var = res['forceVisVar', '']

    sense_offset = res.vec('senseOffset')

    def place_template(inst: Entity) -> None:
        """Place a template."""
        temp_id = inst.fixup.substitute(orig_temp_id)

        # Special case - if blank, just do nothing silently.
        if not temp_id:
            return

        temp_name, visgroups = template_brush.parse_temp_name(temp_id)
        try:
            template = template_brush.get_template(temp_name)
        except template_brush.InvalidTemplateName:
            # If we did lookup, display both forms.
            if temp_id != orig_temp_id:
                LOGGER.warning('{} -> "{}" is not a valid template!',
                               orig_temp_id, temp_name)
            else:
                LOGGER.warning('"{}" is not a valid template!', temp_name)
            # We don't want an error, just quit.
            return

        for vis_flag_block in visgroup_instvars:
            if all(
                    conditions.check_flag(flag, coll, inst)
                    for flag in vis_flag_block):
                visgroups.add(vis_flag_block.real_name)

        force_colour = conf_force_colour
        if color_var == '<editor>':
            # Check traits for the colour it should be.
            traits = instance_traits.get(inst)
            if 'white' in traits:
                force_colour = texturing.Portalable.white
            elif 'black' in traits:
                force_colour = texturing.Portalable.black
            else:
                LOGGER.warning(
                    '"{}": Instance "{}" '
                    "isn't one with inherent color!",
                    temp_id,
                    inst['file'],
                )
        elif color_var:
            color_val = conditions.resolve_value(inst, color_var).casefold()

            if color_val == 'white':
                force_colour = texturing.Portalable.white
            elif color_val == 'black':
                force_colour = texturing.Portalable.black
        # else: no color var

        if srctools.conv_bool(conditions.resolve_value(inst, invert_var)):
            force_colour = template_brush.TEMP_COLOUR_INVERT[conf_force_colour]
        # else: False value, no invert.

        if ang_override is not None:
            orient = ang_override
        else:
            orient = rotation @ Angle.from_str(inst['angles', '0 0 0'])
        origin = conditions.resolve_offset(inst, offset)

        # If this var is set, it forces all to be included.
        if srctools.conv_bool(
                conditions.resolve_value(inst, visgroup_force_var)):
            visgroups.update(template.visgroups)
        elif visgroup_func is not None:
            visgroups.update(
                visgroup_func(
                    rand.seed(b'temp', template.id, origin, orient),
                    list(template.visgroups),
                ))

        LOGGER.debug('Placing template "{}" at {} with visgroups {}',
                     template.id, origin, visgroups)

        temp_data = template_brush.import_template(
            vmf,
            template,
            origin,
            orient,
            targetname=inst['targetname'],
            force_type=force_type,
            add_to_map=True,
            coll=coll,
            additional_visgroups=visgroups,
            bind_tile_pos=bind_tile_pos,
            align_bind=align_bind_overlay,
        )

        if key_block is not None:
            conditions.set_ent_keys(temp_data.detail, inst, key_block)
            br_origin = Vec.from_str(key_block.find_key('keys')['origin'])
            br_origin.localise(origin, orient)
            temp_data.detail['origin'] = br_origin

            move_dir = temp_data.detail['movedir', '']
            if move_dir.startswith('<') and move_dir.endswith('>'):
                move_dir = Vec.from_str(move_dir) @ orient
                temp_data.detail['movedir'] = move_dir.to_angle()

            for out in outputs:
                out = out.copy()
                out.target = conditions.local_name(inst, out.target)
                temp_data.detail.add_out(out)

        template_brush.retexture_template(
            temp_data,
            origin,
            inst.fixup,
            replace_tex,
            force_colour,
            force_grid,
            surf_cat,
            sense_offset,
        )

        for picker_name, picker_var in picker_vars:
            picker_val = temp_data.picker_results.get(picker_name, None)
            if picker_val is not None:
                inst.fixup[picker_var] = picker_val.value
            else:
                inst.fixup[picker_var] = ''

    return place_template
Exemple #5
0
    def parse(cls, info: Property, pack_id: str) -> SelitemData:
        """Parse from a property block."""
        auth = sep_values(info['authors', ''])
        short_name = info['shortName', None]
        name = info['name']
        group = info['group', '']
        sort_key = info['sort_key', '']
        desc = desc_parse(info, info['id'], pack_id)
        if not group:
            group = None
        if not short_name:
            short_name = name

        try:
            icon = img.Handle.parse(
                info.find_key('icon'),
                pack_id,
                consts.SEL_ICON_SIZE,
                consts.SEL_ICON_SIZE,
            )
        except LookupError:
            icon = None
        try:
            large_key = info.find_key('iconLarge')
        except LookupError:
            large_icon = large_key = None
        else:
            large_icon = img.Handle.parse(
                large_key,
                pack_id,
                *consts.SEL_ICON_SIZE_LRG,
            )
        try:
            preview_block = info.find_block('previews')
        except LookupError:
            # Use the large icon, if present.
            if large_key is not None:
                previews = [img.Handle.parse(
                    large_key,
                    pack_id,
                    0,
                    0,
                )]
            else:
                previews = []
        else:
            previews = [
                img.Handle.parse(
                    prop,
                    pack_id,
                    0,
                    0,
                ) for prop in preview_block
            ]

        return cls(
            name,
            short_name,
            auth,
            icon,
            large_icon,
            previews,
            desc,
            group,
            sort_key,
            frozenset({pack_id}),
        )