Beispiel #1
0
    def add_output(inst: Entity) -> None:
        """Add the output."""
        if out_type in ('activate', 'deactivate'):
            try:
                item_type = connections.ITEM_TYPES[out_id.casefold()]
            except KeyError:
                LOGGER.warning('"{}" has no connections!', out_id)
                return
            if out_type[0] == 'a':
                if item_type.output_act is None:
                    return

                inst_out, output = item_type.output_act
            else:
                if item_type.output_deact is None:
                    return
                inst_out, output = item_type.output_deact
        else:
            output = out_id
            inst_out = conf_inst_out

        inst.add_out(
            Output(
                inst.fixup.substitute(output),
                conditions.local_name(inst, inst.fixup.substitute(targ))
                or inst['targetname'],
                inst.fixup.substitute(input_name),
                inst.fixup.substitute(parm),
                srctools.conv_float(inst.fixup.substitute(delay)),
                times=times,
                inst_out=inst.fixup.substitute(inst_out) or None,
                inst_in=inst.fixup.substitute(inst_in) or None,
            ))
Beispiel #2
0
def add_output(inst: Entity, prop: Property, target: str) -> None:
    """Add a customisable output to an instance."""
    inst.add_out(Output(
        prop['output', ''],
        target,
        prop['input', ''],
        inst_in=prop['targ_in', ''],
        inst_out=prop['targ_out', ''],
        ))
Beispiel #3
0
def add_output(inst: Entity, prop: Property, target: str) -> None:
    """Add a customisable output to an instance."""
    inst.add_out(Output(
        prop['output', ''],
        target,
        prop['input', ''],
        inst_in=prop['targ_in', ''],
        inst_out=prop['targ_out', ''],
        ))
Beispiel #4
0
def res_add_output(inst: Entity, res: Property):
    """Add an output from an instance to a global or local name.

    Values:

    - `output`: The output name. Can be `<ITEM_ID:activate>` or `<ITEM_ID:deactivate>`
      to lookup that item type.
    - `target`: The name of the target entity
    - `input`: The input to give
    - `parm`: Parameters for the input
    - `delay`: Delay for the output
    - `only_once`: True to make the input last only once (overrides times)
    - `times`: The number of times to trigger the input
    """
    (
        out_type,
        out_id,
        targ,
        input_name,
        parm,
        delay,
        times,
        inst_in,
        inst_out,
    ) = res.value

    if out_type in ('activate', 'deactivate'):
        try:
            item_type = connections.ITEM_TYPES[out_id.casefold()]
        except KeyError:
            LOGGER.warning('"{}" has no connections!', out_id)
            return
        if out_type[0] == 'a':
            if item_type.output_act is None:
                return

            inst_out, output = item_type.output_act
        else:
            if item_type.output_deact is None:
                return
            inst_out, output = item_type.output_deact
    else:
        output = resolve_value(inst, out_id)
        inst_out = resolve_value(inst, inst_out)

    inst.add_out(
        Output(
            resolve_value(inst, output),
            local_name(inst, resolve_value(inst, targ)) or inst['targetname'],
            resolve_value(inst, input_name),
            resolve_value(inst, parm),
            srctools.conv_float(resolve_value(inst, delay)),
            times=times,
            inst_out=resolve_value(inst, inst_out) or None,
            inst_in=resolve_value(inst, inst_in) or None,
        ))
Beispiel #5
0
def res_add_output(inst: Entity, res: Property):
    """Add an output from an instance to a global or local name.

    Values:
    - output: The output name.Can be <ITEM_ID:activate> or <ITEM_ID:deactivate>
      to lookup that item type.
    - target: The name of the target entity
    - input: The input to give
    - parm: Parameters for the input
    - delay: Delay for the output
    - only_once: True to make the input last only once (overrides times)
    - times: The number of times to trigger the input
    """
    (
        out_type,
        out_id,
        targ,
        input_name,
        parm,
        delay,
        times,
        inst_in,
        inst_out,
    ) = res.value

    LOGGER.info('Conn: {}', res.value)

    if out_type in ('activate', 'deactivate'):
        try:
            connection = CONNECTIONS[out_id]
        except KeyError:
            LOGGER.warning('"{}" has no connections!', out_id)
            return
        if out_type[0] == 'a':
            inst_out, output = connection.out_act
        else:
            inst_out, output = connection.out_deact
    else:
        output = resolve_value(inst, out_id)
        inst_out = resolve_value(inst, inst_out)

    inst.add_out(
        Output(
            resolve_value(inst, output),
            local_name(inst, resolve_value(inst, targ)),
            resolve_value(inst, input_name),
            resolve_value(inst, parm),
            srctools.conv_float(resolve_value(inst, delay)),
            times=times,
            inst_out=resolve_value(inst, inst_out) or None,
            inst_in=resolve_value(inst, inst_in) or None,
        ))
Beispiel #6
0
def res_add_output(inst: Entity, res: Property):
    """Add an output from an instance to a global or local name.

    Values:
    - output: The output name.Can be <ITEM_ID:activate> or <ITEM_ID:deactivate>
      to lookup that item type.
    - target: The name of the target entity
    - input: The input to give
    - parm: Parameters for the input
    - delay: Delay for the output
    - only_once: True to make the input last only once (overrides times)
    - times: The number of times to trigger the input
    """
    (
        out_type,
        out_id,
        targ,
        input_name,
        parm,
        delay,
        times,
        inst_in,
        inst_out,
    ) = res.value

    LOGGER.info('Conn: {}', res.value)

    if out_type in ('activate', 'deactivate'):
        try:
            connection = CONNECTIONS[out_id]
        except KeyError:
            LOGGER.warning('"{}" has no connections!', out_id)
            return
        if out_type[0] == 'a':
            inst_out, output = connection.out_act
        else:
            inst_out, output = connection.out_deact
    else:
        output = resolve_value(inst, out_id)
        inst_out = resolve_value(inst, inst_out)

    inst.add_out(Output(
        resolve_value(inst, output),
        local_name(inst, resolve_value(inst, targ)),
        resolve_value(inst, input_name),
        resolve_value(inst, parm),
        srctools.conv_float(resolve_value(inst, delay)),
        times=times,
        inst_out=resolve_value(inst, inst_out) or None,
        inst_in=resolve_value(inst, inst_in) or None,
    ))
Beispiel #7
0
    def __init__(self, ent: Entity):
        """Convert the entity to have the right logic."""
        self.scanner = None
        self.persist_tv = conv_bool(ent.keys.pop('persist_tv', False))

        pos = Vec.from_str(ent['origin'])
        for prop in ent.map.by_class['prop_dynamic']:
            if (Vec.from_str(prop['origin']) - pos).mag_sq() > 64**2:
                continue

            model = prop['model'].casefold().replace('\\', '/')
            # Allow spelling this correctly, if you're not Valve.
            if 'vacum_scanner_tv' in model or 'vacuum_scanner_tv' in model:
                self.scanner = prop
                prop.make_unique('_vac_scanner')
            elif 'vacum_scanner_motion' in model or 'vacuum_scanner_motion' in model:
                prop.make_unique('_vac_scanner')
                ent.add_out(Output(self.pass_out_name, prop, "SetAnimation", "scan01"))

        super(Straight, self).__init__(ent)
def res_linked_cube_dropper(drp_inst: Entity, res: Property):
    """Link a cube and dropper together, to preplace the cube at a location."""
    time = drp_inst.fixup.int('$timer_delay')
    # Portal 2 bug - when loading existing maps, timers are set to 3...
    if not (3 < time <= 30):
        # Infinite or 3-second - this behaviour is disabled..
        return

    try:

        cube_inst, cube_type, resp_out_name, resp_out = LINKED_CUBES[time]
    except KeyError:
        raise Exception('Unknown cube "linkage" value ({}) in dropper!'.format(
            time,
        ))

    # Force the dropper to match the cube..
    #  = cube_type

    # Set auto-drop to False (so there isn't two cubes),
    # and auto-respawn to True (so it actually functions).
    drp_inst.fixup['$disable_autodrop'] = '1'
    drp_inst.fixup['$disable_autorespawn'] = '0'

    fizz_out_name, fizz_out = Output.parse_name(res['FizzleOut'])

    # Output to destroy the cube when the dropper is triggered externally.
    drp_inst.add_out(Output(
        inst_out=fizz_out_name,
        out=fizz_out,
        targ=local_name(cube_inst, 'cube'),
        inp='Dissolve',
        only_once=True,
    ))

    # Cube items don't have proxies, so we need to use AddOutput
    # after it's created (@relay_spawn_3's time).
    try:
        relay_spawn_3 = GLOBAL_INPUT_ENTS['@relay_spawn_3']
    except KeyError:
        relay_spawn_3 = GLOBAL_INPUT_ENTS['@relay_spawn_3'] = cube_inst.map.create_ent(
            classname='logic_relay',
            targetname='@relay_spawn_3',
            origin=cube_inst['origin'],
        )

    respawn_inp = list(res.find_all('RespawnIn'))
    # There's some voice-logic specific to companion cubes.
    respawn_inp.extend(res.find_all(
        'RespawnCcube' if
        drp_inst.fixup['$cube_type'] == '1'
        else 'RespawnCube'
    ))

    for inp in respawn_inp:
        resp_in_name, resp_in = inp.value.split(':', 1)

        out = Output(
            out='OnFizzled',
            targ=drp_inst,
            inst_in=resp_in_name,
            inp=resp_in,
            only_once=True,
        )

        relay_spawn_3.add_out(Output(
            out='OnTrigger',
            targ=local_name(cube_inst, 'cube'),
            inp='AddOutput',
            param=out.gen_addoutput(),
            only_once=True,
            delay=0.01,
        ))
Beispiel #9
0
    def parse(cls, vmf: VMF, ent: Entity, radius: float) -> 'Dropper':
        """Scan the map applying dropper tweaks, then create the Dropper object."""
        filter_name = ent['filtername']
        template_name = ent['template']

        for cube_filter in vmf.search(filter_name):
            break
        else:
            raise ValueError(
                f'No filter "{filter_name}" for dropper at {ent["origin"]}!')

        for template in vmf.search(template_name):
            break
        else:
            raise ValueError(
                f'No template "{template_name}" for dropper at {ent["origin"]}!'
            )

        best_cube = None
        best_dist = math.inf
        radius **= 2
        ref_pos = Vec.from_str(cube_filter['origin'])
        for cube in vmf.by_class['prop_weighted_cube'] | vmf.by_class[
                'prop_monster_box']:
            dist = (Vec.from_str(cube['origin']) - ref_pos).mag_sq()
            if dist > radius or dist > best_dist:
                continue
            best_dist = dist
            best_cube = cube
        if best_cube is None:
            LOGGER.warning(
                'Cube dropper at {} has no cube. Generating standard one...',
                ref_pos)
            best_cube = vmf.create_ent(
                'prop_weighted_cube',
                angles='0 0 0',
                newskins='1',
                skintype='0',
                cubetype='0',
                skin='0',
                paintpower='4',
                model=CUBE_MODEL,
            )

        # Now adjust the cube for dropper use.
        best_cube.make_unique('dropper_cube')
        best_cube['origin'] = ent['origin']
        # Only regular cubes can disable funnelling, but frankenturrets
        # require being in box form.
        if best_cube['classname'] == 'prop_monster_box':
            best_cube['startasbox'] = '1'
        else:
            best_cube['allowfunnel'] = '0'

        # Copy the cube name to filter and dropper.
        cube_filter['filtername'] = best_cube['targetname']

        for i in range(1, 10):
            if not template[f'Template{i:02}']:
                template[f'Template{i:02}'] = best_cube['targetname']
            break
        else:
            raise ValueError(f'No spare slots for template "{template_name}"!')

        # Add fizzle outputs if enabled.
        if srctools.conv_bool(ent['autorespawn']):
            best_cube.outputs += [
                out for out in ent.outputs
                if out.output.casefold() == 'onfizzled'
            ]
        ent.add_out(Output(Dropper.pass_out_name, template, 'ForceSpawn'))
        return Dropper(ent, template, best_cube)
Beispiel #10
0
def res_cust_output(inst: Entity, res: Property):
    """Add an additional output to the instance with any values.

    Always points to the targeted item.

    If DecConCount is 1, connections
    """
    (
        outputs,
        dec_con_count,
        targ_conditions,
        force_sign_type,
        (sign_act_name, sign_act_out),
        (sign_deact_name, sign_deact_out),
    ) = res.value

    over_name = '@' + inst['targetname'] + '_indicator'
    for toggle in vbsp.VMF.by_class['func_instance']:
        if toggle.fixup['indicator_name', ''] == over_name:
            toggle_name = toggle['targetname']
            break
    else:
        toggle_name = ''  # we want to ignore the toggle instance, if it exists

    # Build a mapping from names to targets.
    # This is also the set of all output items, plus indicators.
    targets = defaultdict(list)
    for out in inst.outputs:
        if out.target != toggle_name:
            targets[out.target].append(out)

    pan_files = instanceLocs.resolve('[indPan]')

    # These all require us to search through the instances.
    if force_sign_type or dec_con_count or targ_conditions:
        for con_inst in vbsp.VMF.by_class['func_instance']:  # type: Entity
            if con_inst['targetname'] not in targets:
                # Not our instance
                continue

            # Is it an indicator panel, and should we be modding it?
            if force_sign_type is not None and con_inst['file'].casefold(
            ) in pan_files:
                # Remove the panel
                if force_sign_type == '':
                    con_inst.remove()
                    continue

                # Overwrite the signage instance, and then add the
                # appropriate outputs to control it.
                sign_id, sign_file_id = force_sign_type
                con_inst['file'] = instanceLocs.resolve_one(sign_file_id,
                                                            error=True)

                # First delete the original outputs:
                for out in targets[con_inst['targetname']]:
                    inst.outputs.remove(out)

                inputs = CONNECTIONS[sign_id]
                act_name, act_inp = inputs.in_act
                deact_name, deact_inp = inputs.in_deact

                LOGGER.info('outputs: a="{}" d="{}"\n'
                            'inputs: a="{}" d="{}"'.format(
                                (sign_act_name, sign_act_out),
                                (sign_deact_name, sign_deact_out),
                                inputs.in_act, inputs.in_deact))

                if act_inp and sign_act_out:
                    inst.add_out(
                        Output(
                            inst_out=sign_act_name,
                            out=sign_act_out,
                            inst_in=act_name,
                            inp=act_inp,
                            targ=con_inst['targetname'],
                        ))

                if deact_inp and sign_deact_out:
                    inst.add_out(
                        Output(
                            inst_out=sign_deact_name,
                            out=sign_deact_out,
                            inst_in=deact_name,
                            inp=deact_inp,
                            targ=con_inst['targetname'],
                        ))
            if dec_con_count and 'connectioncount' in con_inst.fixup:
                # decrease ConnectionCount on the ents,
                # so they can still process normal inputs
                try:
                    val = int(con_inst.fixup['connectioncount'])
                    con_inst.fixup['connectioncount'] = str(val - 1)
                except ValueError:
                    # skip if it's invalid
                    LOGGER.warning(con_inst['targetname'] +
                                   ' has invalid ConnectionCount!')

            if targ_conditions:
                for cond in targ_conditions:  # type: Condition
                    cond.test(con_inst)

    if outputs:
        for targ in targets:
            for out in outputs:
                conditions.add_output(inst, out, targ)
Beispiel #11
0
def generate_fizzlers(vmf: VMF):
    """Generates fizzler models and the brushes according to their set types.

    After this is done, fizzler-related conditions will not function correctly.
    However the model instances are now available for modification.
    """
    from vbsp import MAP_RAND_SEED, TO_PACK, PACK_FILES

    for fizz in FIZZLERS.values():
        if fizz.base_inst not in vmf.entities:
            continue  # The fizzler was removed from the map.

        fizz_name = fizz.base_inst['targetname']
        fizz_type = fizz.fizz_type

        # Static versions are only used for fizzlers which start on.
        # Permanently-off fizzlers are kinda useless, so we don't need
        # to bother optimising for it.
        is_static = bool(
            fizz.base_inst.fixup.int('$connectioncount', 0) == 0
            and fizz.base_inst.fixup.bool('$start_enabled', 1))

        if is_static:
            TO_PACK |= fizz.fizz_type.pack_lists_static
        TO_PACK |= fizz.fizz_type.pack_lists

        if fizz_type.inst[FizzInst.BASE, is_static]:
            random.seed('{}_fizz_base_{}'.format(MAP_RAND_SEED, fizz_name))
            fizz.base_inst['file'] = random.choice(
                fizz_type.inst[FizzInst.BASE, is_static])

        if not fizz.emitters:
            LOGGER.warning('No emitters for fizzler "{}"!', fizz_name)
            continue

        # Brush index -> entity for ones that need to merge.
        # template_brush is used for the templated one.
        single_brushes = {}  # type: Dict[FizzlerBrush, Entity]

        if fizz_type.temp_max or fizz_type.temp_min:
            template_brush_ent = vmf.create_ent(
                classname='func_brush',
                origin=fizz.base_inst['origin'],
            )
            conditions.set_ent_keys(
                template_brush_ent,
                fizz.base_inst,
                fizz_type.temp_brush_keys,
            )
        else:
            template_brush_ent = None

        up_dir = fizz.up_axis
        forward = (fizz.emitters[0][1] - fizz.emitters[0][0]).norm()

        min_angles = FIZZ_ANGLES[forward.as_tuple(), up_dir.as_tuple()]
        max_angles = FIZZ_ANGLES[(-forward).as_tuple(), up_dir.as_tuple()]

        model_min = (fizz_type.inst[FizzInst.PAIR_MIN, is_static]
                     or fizz_type.inst[FizzInst.ALL, is_static])
        model_max = (fizz_type.inst[FizzInst.PAIR_MAX, is_static]
                     or fizz_type.inst[FizzInst.ALL, is_static])

        if not model_min or not model_max:
            raise ValueError(
                'No model specified for one side of "{}"'
                ' fizzlers'.format(fizz_type.id), )

        # Define a function to do the model names.
        model_index = 0
        if fizz_type.model_naming is ModelName.SAME:

            def get_model_name(ind):
                """Give every emitter the base's name."""
                return fizz_name
        elif fizz_type.model_naming is ModelName.LOCAL:

            def get_model_name(ind):
                """Give every emitter a name local to the base."""
                return fizz_name + '-' + fizz_type.model_name
        elif fizz_type.model_naming is ModelName.PAIRED:

            def get_model_name(ind):
                """Give each pair of emitters the same unique name."""
                return '{}-{}{:02}'.format(
                    fizz_name,
                    fizz_type.model_name,
                    ind,
                )
        elif fizz_type.model_naming is ModelName.UNIQUE:

            def get_model_name(ind):
                """Give every model a unique name."""
                nonlocal model_index
                model_index += 1
                return '{}-{}{:02}'.format(
                    fizz_name,
                    fizz_type.model_name,
                    model_index,
                )
        else:
            raise ValueError('Bad ModelName?')

        # Generate env_beam pairs.
        for beam in fizz_type.beams:
            beam_template = Entity(vmf)
            conditions.set_ent_keys(beam_template, fizz.base_inst, beam.keys)
            beam_template['classname'] = 'env_beam'
            del beam_template[
                'LightningEnd']  # Don't allow users to set end pos.
            name = beam_template['targetname'] + '_'

            counter = 1
            for seg_min, seg_max in fizz.emitters:
                for offset in beam.offset:  # type: Vec
                    min_off = offset.copy()
                    max_off = offset.copy()
                    min_off.localise(seg_min, min_angles)
                    max_off.localise(seg_max, max_angles)
                    beam_ent = beam_template.copy()
                    vmf.add_ent(beam_ent)

                    # Allow randomising speed and direction.
                    if 0 < beam.speed_min < beam.speed_max:
                        random.seed('{}{}{}'.format(MAP_RAND_SEED, min_off,
                                                    max_off))
                        beam_ent['TextureScroll'] = random.randint(
                            beam.speed_min, beam.speed_max)
                        if random.choice((False, True)):
                            # Flip to reverse direction.
                            min_off, max_off = max_off, min_off

                    beam_ent['origin'] = min_off
                    beam_ent['LightningStart'] = beam_ent['targetname'] = (
                        name + str(counter))
                    counter += 1
                    beam_ent['targetpoint'] = max_off

        mat_mod_tex = {}  # type: Dict[FizzlerBrush, Set[str]]
        for brush_type in fizz_type.brushes:
            if brush_type.mat_mod_var is not None:
                mat_mod_tex[brush_type] = set()

        trigger_hurt_name = ''

        for seg_ind, (seg_min, seg_max) in enumerate(fizz.emitters, start=1):
            length = (seg_max - seg_min).mag()
            random.seed('{}_fizz_{}'.format(MAP_RAND_SEED, seg_min))
            if length == 128 and fizz_type.inst[FizzInst.PAIR_SINGLE,
                                                is_static]:
                min_inst = vmf.create_ent(
                    targetname=get_model_name(seg_ind),
                    classname='func_instance',
                    file=random.choice(fizz_type.inst[FizzInst.PAIR_SINGLE,
                                                      is_static]),
                    origin=(seg_min + seg_max) / 2,
                    angles=min_angles,
                )
            else:
                # Both side models.
                min_inst = vmf.create_ent(
                    targetname=get_model_name(seg_ind),
                    classname='func_instance',
                    file=random.choice(model_min),
                    origin=seg_min,
                    angles=min_angles,
                )
                random.seed('{}_fizz_{}'.format(MAP_RAND_SEED, seg_max))
                max_inst = vmf.create_ent(
                    targetname=get_model_name(seg_ind),
                    classname='func_instance',
                    file=random.choice(model_max),
                    origin=seg_max,
                    angles=max_angles,
                )
                max_inst.fixup.update(fizz.base_inst.fixup)
            min_inst.fixup.update(fizz.base_inst.fixup)

            if fizz_type.inst[FizzInst.GRID, is_static]:
                # Generate one instance for each position.

                # Go 64 from each side, and always have at least 1 section
                # A 128 gap will have length = 0
                for ind, dist in enumerate(range(64, round(length) - 63, 128)):
                    mid_pos = seg_min + forward * dist
                    random.seed('{}_fizz_mid_{}'.format(
                        MAP_RAND_SEED, mid_pos))
                    mid_inst = vmf.create_ent(
                        classname='func_instance',
                        targetname=fizz_name,
                        angles=min_angles,
                        file=random.choice(fizz_type.inst[FizzInst.GRID,
                                                          is_static]),
                        origin=mid_pos,
                    )
                    mid_inst.fixup.update(fizz.base_inst.fixup)

            if template_brush_ent is not None:
                if length == 128 and fizz_type.temp_single:
                    temp = template_brush.import_template(
                        fizz_type.temp_single,
                        (seg_min + seg_max) / 2,
                        min_angles,
                        force_type=template_brush.TEMP_TYPES.world,
                        add_to_map=False,
                    )
                    template_brush_ent.solids.extend(temp.world)
                else:
                    if fizz_type.temp_min:
                        temp = template_brush.import_template(
                            fizz_type.temp_min,
                            seg_min,
                            min_angles,
                            force_type=template_brush.TEMP_TYPES.world,
                            add_to_map=False,
                        )
                        template_brush_ent.solids.extend(temp.world)
                    if fizz_type.temp_max:
                        temp = template_brush.import_template(
                            fizz_type.temp_max,
                            seg_max,
                            max_angles,
                            force_type=template_brush.TEMP_TYPES.world,
                            add_to_map=False,
                        )
                        template_brush_ent.solids.extend(temp.world)

            # Generate the brushes.
            for brush_type in fizz_type.brushes:
                brush_ent = None
                # If singular, we reuse the same brush ent for all the segments.
                if brush_type.singular:
                    brush_ent = single_brushes.get(brush_type, None)

                # Non-singular or not generated yet - make the entity.
                if brush_ent is None:
                    brush_ent = Entity(vmf, keys=brush_type.keys)
                    vmf.add_ent(brush_ent)

                    for key_name, key_value in brush_type.local_keys.items():
                        brush_ent[key_name] = conditions.local_name(
                            fizz.base_inst, key_value)
                    brush_ent['targetname'] = conditions.local_name(
                        fizz.base_inst,
                        brush_type.name,
                    )
                    # Set this to the center, to make sure it's not going to leak.
                    brush_ent['origin'] = (seg_min + seg_max) / 2

                    # For fizzlers flat on the floor/ceiling, scanlines look
                    # useless. Turn them off.
                    if 'usescanline' in brush_ent and fizz.normal().z:
                        brush_ent['UseScanline'] = 0

                    if brush_ent['classname'] == 'trigger_hurt':
                        trigger_hurt_name = brush_ent['targetname']

                    if brush_type.set_axis_var:
                        axis_script = 'BEE2/fizzler_axis_{}.nut'.format(
                            fizz.normal().axis())
                        scripts = brush_ent['vscripts'].split()
                        scripts.insert(0, axis_script)
                        brush_ent['vscripts'] = ' '.join(scripts)
                        PACK_FILES.add('scripts/vscripts/' + axis_script)

                    for out in brush_type.outputs:
                        new_out = out.copy()
                        new_out.target = conditions.local_name(
                            fizz.base_inst,
                            new_out.target,
                        )
                        brush_ent.add_out(new_out)

                    if brush_type.singular:
                        # Record for the next iteration.
                        single_brushes[brush_type] = brush_ent

                # If we have a material_modify_control to generate,
                # we need to parent it to ourselves to restrict it to us
                # only. We also need one for each material, so provide a
                # function to the generator which adds to a set.
                if brush_type.mat_mod_var is not None:
                    used_tex_func = mat_mod_tex[brush_type].add
                else:

                    def used_tex_func(val):
                        """If not, ignore those calls."""
                        return None

                # Generate the brushes and texture them.
                brush_ent.solids.extend(
                    brush_type.generate(
                        vmf,
                        fizz,
                        seg_min,
                        seg_max,
                        used_tex_func,
                    ))

        if trigger_hurt_name:
            fizz.gen_flinch_trigs(vmf, trigger_hurt_name)

        # If we have the config, but no templates used anywhere...
        if template_brush_ent is not None and not template_brush_ent.solids:
            template_brush_ent.remove()

        for brush_type, used_tex in mat_mod_tex.items():
            brush_name = conditions.local_name(fizz.base_inst, brush_type.name)
            mat_mod_name = conditions.local_name(fizz.base_inst,
                                                 brush_type.mat_mod_name)
            for off, tex in zip(MATMOD_OFFSETS, sorted(used_tex)):
                pos = off.copy().rotate(*min_angles)
                pos += Vec.from_str(fizz.base_inst['origin'])
                vmf.create_ent(
                    classname='material_modify_control',
                    origin=pos,
                    targetname=mat_mod_name,
                    materialName='materials/' + tex + '.vmt',
                    materialVar=brush_type.mat_mod_var,
                    parentname=brush_name,
                )