Esempio n. 1
0
def res_change_outputs_setup(res: Property):
    return [
        (
            Output.parse_name(prop.real_name),
            Output.parse_name(prop.value)
        )
        for prop in
        res
    ]
def res_linked_cube(inst: Entity, res: Property):
    """Marks a cube to link it to a dropper.

    This assumes some things about the item.
    """
    time = 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

    if time in LINKED_CUBES:
        raise Exception(
            'Two cubes have the same '
            '"linkage" value set ({})!'.format(
                time,
            )
        )

    resp_out_name, resp_out = Output.parse_name(res.value)

    LINKED_CUBES[time] = (
        inst,
        inst.fixup['$cube_type'],
        resp_out_name, resp_out,
    )
Esempio n. 3
0
def res_cust_output_setup(res: Property):
    conds = [
        Condition.parse(sub_res) for sub_res in res
        if sub_res.name == 'targcondition'
    ]
    outputs = list(res.find_all('addOut'))
    dec_con_count = srctools.conv_bool(res["decConCount", '0'], False)
    sign_type = IND_PANEL_TYPES.get(res['sign_type', None], None)

    if sign_type is None:
        sign_act = sign_deact = (None, '')
    else:
        # The outputs which trigger the sign.
        sign_act = Output.parse_name(res['sign_activate', ''])
        sign_deact = Output.parse_name(res['sign_deactivate', ''])

    return outputs, dec_con_count, conds, sign_type, sign_act, sign_deact
def res_locking_input(inst: Entity, res: Property):
    """Executed on the input item, and evaluates to True if successful.

    The parameter is an `instance:name;Input` value, which resets the item.
    This must be executed after the `MarkLocking` results have run.
    """
    from vbsp import IND_ITEM_NAMES, IND_PANEL_NAMES, VMF
    in_name, in_inp = Output.parse_name(res.value)

    targets = {
        out.target
        for out in
        inst.outputs
        # Skip toggle or indicator panel items.
        if out.target not in IND_ITEM_NAMES
    }
    # No outputs, or 2+ - we can't convert in that case
    if len(targets) != 1:
        return False

    target, = targets
    try:
        targ_inst, targ_out_name, targ_out, out_relay = LOCKABLE_ITEMS[target]
    except KeyError:
        # Some other item...
        return False

    # Remove the indicator panel instances.
    ind_panels = {
        out.target
        for out in
        inst.outputs
        # Skip toggle or indicator panel items.
        if out.target in IND_PANEL_NAMES
    }
    for pan_inst in VMF.by_class['func_instance']:
        if pan_inst['targetname'] in ind_panels:
            pan_inst.remove()

    # Add an output pointing in the opposite direction.
    if out_relay is None:
        targ_inst.add_out(Output(
            out=targ_out,
            inst_out=targ_out_name,
            targ=inst['targetname'],
            inp=in_inp,
            inst_in=in_name,
        ))
    else:
        from conditions.instances import add_global_input
        add_global_input(
            inst,
            in_name,
            in_inp,
            rl_name=out_relay,
            output=targ_out,
        )
    return True
Esempio n. 5
0
def res_global_input_setup(res: Property):
    target = res['target', ''] or None
    name = res['name', ''] or None
    output = res['output', 'OnTrigger']
    param = res['param', '']
    delay = srctools.conv_float(res['delay', ''])
    inp_name, inp_command = Output.parse_name(res['input'])

    return name, inp_name, inp_command, output, delay, param, target
def res_locking_output(inst: Entity, res: Property):
    """Marks an output item for locked connections.

    The parameter is an `instance:name;Output` value, which is fired when the
    item resets. This must be executed before `LockingIO`.

    This only applies if `$connectioncount` is 1.
    """
    # Items with more than one connection have AND logic in the mix - it makes
    # it unsafe to lock the input item.
    if inst.fixup['$connectioncount'] != '1':
        return

    if res.has_children():
        name, output = Output.parse_name(res['output'])
        relay_name = res['rl_name', None]
    else:
        name, output = Output.parse_name(res.value)
        relay_name = None

    LOCKABLE_ITEMS[inst['targetname']] = inst, name, output, relay_name
Esempio n. 7
0
def res_change_inputs_setup(res: Property):
    vals = {}
    for prop in res:
        out_key = Output.parse_name(prop.real_name)
        if prop.has_children():
            vals[out_key] = (
                prop['inst_in', None],
                prop['input'],
                prop['params', ''],
                srctools.conv_float(prop['delay', 0.0]),
                1 if srctools.conv_bool(prop['only_once', '0']) else -1,
            )
        else:
            vals[out_key] = None
    return vals
Esempio n. 8
0
def res_global_input_setup(res: Property):
    if res.has_children():
        name = res['name', '']
        inp_name, inp_command = Output.parse_name(res['input'])
        return name, Output(
            out=res['output', 'OnTrigger'],
            targ=res['target', ''],
            inp=inp_command,
            inst_in=inp_name,
            delay=srctools.conv_float(res['delay', '']),
            param=res['param', ''],
        )
    else:
        out = Output.parse(res)
        out.output = ''  # Don't need to store GlobalInput...
        return '', out
Esempio n. 9
0
def res_global_input_setup(res: Property):
    if res.has_children():
        name = res['name', '']
        inp_name, inp_command = Output.parse_name(res['input'])
        return name, Output(
            out=res['output', 'OnTrigger'],
            targ=res['target', ''],
            inp=inp_command,
            inst_in=inp_name,
            delay=srctools.conv_float(res['delay', '']),
            param=res['param', ''],
        )
    else:
        out = Output.parse(res)
        out.output = ''  # Don't need to store GlobalInput...
        return '', out
Esempio n. 10
0
def res_global_input_setup(res: Property) -> tuple[str, Output]:
    """Pre-parse the global input."""
    if res.has_children():
        name = res['name', '']
        if not name and res.bool('alsoonload'):
            name = ON_LOAD
        inp_name, inp_command = Output.parse_name(res['input'])
        return name, Output(
            out=res['output', 'OnTrigger'],
            targ=res['target', ''],
            inp=inp_command,
            inst_in=inp_name,
            delay=srctools.conv_float(res['delay', '']),
            param=res['param', ''],
        )
    else:
        out = Output.parse(res)
        out.output = ''  # Don't need to store GlobalInput...
        return '', out
Esempio n. 11
0
def res_resizeable_trigger(vmf: VMF, res: Property):
    """Replace two markers with a trigger brush.  

    This is run once to affect all of an item.  
    Options:
    * `markerInst`: <ITEM_ID:1,2> value referencing the marker instances, or a filename.
    * `markerItem`: The item's ID
    * `previewConf`: A item config which enables/disables the preview overlay.
    * `previewInst`: An instance to place at the marker location in preview mode.
        This should contain checkmarks to display the value when testing.
    * `previewMat`: If set, the material to use for an overlay func_brush.
        The brush will be parented to the trigger, so it vanishes once killed.
        It is also non-solid.
    * `previewScale`: The scale for the func_brush materials.
    * `previewActivate`, `previewDeactivate`: The VMF output to turn the
        previewInst on and off.

    * `triggerActivate, triggerDeactivate`: The `instance:name;Output`
        outputs used when the trigger turns on or off.

    * `coopVar`: The instance variable which enables detecting both Coop players.
        The trigger will be a trigger_playerteam.

    * `coopActivate, coopDeactivate`: The `instance:name;Output` outputs used
        when coopVar is enabled. These should be suitable for a logic_coop_manager.
    * `coopOnce`: If true, kill the manager after it first activates.

    * `keys`: A block of keyvalues for the trigger brush. Origin and targetname
        will be set automatically.
    * `localkeys`: The same as above, except values will be changed to use
        instance-local names.
    """
    marker = instanceLocs.resolve(res['markerInst'])

    marker_names = set()

    for inst in vmf.by_class['func_instance']:
        if inst['file'].casefold() in marker:
            marker_names.add(inst['targetname'])
            # Unconditionally delete from the map, so it doesn't
            # appear even if placed wrongly.
            inst.remove()

    if not marker_names:  # No markers in the map - abort
        return RES_EXHAUSTED

    item_id = res['markerItem']

    # Synthesise the item type used for the final trigger.
    item_type_sp = connections.ItemType(
        id=item_id + ':TRIGGER',
        output_act=Output.parse_name(res['triggerActivate',
                                         'OnStartTouchAll']),
        output_deact=Output.parse_name(res['triggerDeactivate',
                                           'OnEndTouchAll']),
    )

    # For Coop, we add a logic_coop_manager in the mix so both players can
    # be handled.
    try:
        coop_var = res['coopVar']
    except LookupError:
        coop_var = item_type_coop = None
        coop_only_once = False
    else:
        coop_only_once = res.bool('coopOnce')
        item_type_coop = connections.ItemType(
            id=item_id + ':TRIGGER_COOP',
            output_act=Output.parse_name(res['coopActivate',
                                             'OnChangeToAllTrue']),
            output_deact=Output.parse_name(res['coopDeactivate',
                                               'OnChangeToAnyFalse']),
        )

    # Display preview overlays if it's preview mode, and the config is true
    pre_act = pre_deact = None
    if vbsp.IS_PREVIEW and vbsp_options.get_itemconf(res['previewConf', ''],
                                                     False):
        preview_mat = res['previewMat', '']
        preview_inst_file = res['previewInst', '']
        preview_scale = res.float('previewScale', 0.25)
        # None if not found.
        with suppress(LookupError):
            pre_act = Output.parse(res.find_key('previewActivate'))
        with suppress(LookupError):
            pre_deact = Output.parse(res.find_key('previewDeactivate'))
    else:
        # Deactivate the preview_ options when publishing.
        preview_mat = preview_inst_file = ''
        preview_scale = 0.25

    # Now go through each brush.
    # We do while + pop to allow removing both names each loop through.
    todo_names = set(marker_names)
    while todo_names:
        targ = todo_names.pop()

        mark1 = connections.ITEMS.pop(targ)
        for conn in mark1.outputs:
            if conn.to_item.name in marker_names:
                mark2 = conn.to_item
                conn.remove()  # Delete this connection.
                todo_names.discard(mark2.name)
                del connections.ITEMS[mark2.name]
                break
        else:
            if not mark1.inputs:
                # If the item doesn't have any connections, 'connect'
                # it to itself so we'll generate a 1-block trigger.
                mark2 = mark1
            else:
                # It's a marker with an input, the other in the pair
                # will handle everything.
                # But reinstate it in ITEMS.
                connections.ITEMS[targ] = mark1
                continue

        inst1 = mark1.inst
        inst2 = mark2.inst

        is_coop = coop_var is not None and vbsp.GAME_MODE == 'COOP' and (
            inst1.fixup.bool(coop_var) or inst2.fixup.bool(coop_var))

        bbox_min, bbox_max = Vec.bbox(Vec.from_str(inst1['origin']),
                                      Vec.from_str(inst2['origin']))
        origin = (bbox_max + bbox_min) / 2

        # Extend to the edge of the blocks.
        bbox_min -= 64
        bbox_max += 64

        out_ent = trig_ent = vmf.create_ent(
            classname='trigger_multiple',  # Default
            targetname=targ,
            origin=origin,
            angles='0 0 0',
        )
        trig_ent.solids = [
            vmf.make_prism(
                bbox_min,
                bbox_max,
                mat=const.Tools.TRIGGER,
            ).solid,
        ]

        # Use 'keys' and 'localkeys' blocks to set all the other keyvalues.
        conditions.set_ent_keys(trig_ent, inst, res)

        if is_coop:
            trig_ent['spawnflags'] = '1'  # Clients
            trig_ent['classname'] = 'trigger_playerteam'

            out_ent = manager = vmf.create_ent(
                classname='logic_coop_manager',
                targetname=conditions.local_name(inst, 'man'),
                origin=origin,
            )

            item = connections.Item(
                out_ent,
                item_type_coop,
                mark1.ant_floor_style,
                mark1.ant_wall_style,
            )

            if coop_only_once:
                # Kill all the ents when both players are present.
                manager.add_out(
                    Output('OnChangeToAllTrue', manager, 'Kill'),
                    Output('OnChangeToAllTrue', targ, 'Kill'),
                )
            trig_ent.add_out(
                Output('OnStartTouchBluePlayer', manager, 'SetStateATrue'),
                Output('OnStartTouchOrangePlayer', manager, 'SetStateBTrue'),
                Output('OnEndTouchBluePlayer', manager, 'SetStateAFalse'),
                Output('OnEndTouchOrangePlayer', manager, 'SetStateBFalse'),
            )
        else:
            item = connections.Item(
                trig_ent,
                item_type_sp,
                mark1.ant_floor_style,
                mark1.ant_wall_style,
            )

        # Register, and copy over all the antlines.
        connections.ITEMS[item.name] = item
        item.ind_panels = mark1.ind_panels | mark2.ind_panels
        item.antlines = mark1.antlines | mark2.antlines
        item.shape_signs = mark1.shape_signs + mark2.shape_signs

        if preview_mat:
            preview_brush = vmf.create_ent(
                classname='func_brush',
                parentname=targ,
                origin=origin,
                Solidity='1',  # Not solid
                drawinfastreflection='1',  # Draw in goo..

                # Disable shadows and lighting..
                disableflashlight='1',
                disablereceiveshadows='1',
                disableshadowdepth='1',
                disableshadows='1',
            )
            preview_brush.solids = [
                # Make it slightly smaller, so it doesn't z-fight with surfaces.
                vmf.make_prism(
                    bbox_min + 0.5,
                    bbox_max - 0.5,
                    mat=preview_mat,
                ).solid,
            ]
            for face in preview_brush.sides():
                face.scale = preview_scale

        if preview_inst_file:
            pre_inst = vmf.create_ent(
                classname='func_instance',
                targetname=targ + '_preview',
                file=preview_inst_file,
                # Put it at the second marker, since that's usually
                # closest to antlines if present.
                origin=inst2['origin'],
            )

            if pre_act is not None:
                out = pre_act.copy()
                out.inst_out, out.output = item.output_act()
                out.target = conditions.local_name(pre_inst, out.target)
                out_ent.add_out(out)
            if pre_deact is not None:
                out = pre_deact.copy()
                out.inst_out, out.output = item.output_deact()
                out.target = conditions.local_name(pre_inst, out.target)
                out_ent.add_out(out)

        for conn in mark1.outputs | mark2.outputs:
            conn.from_item = item

    return RES_EXHAUSTED
def res_resizeable_trigger(res: Property):
    """Replace two markers with a trigger brush.

    This is run once to affect all of an item.
    Options:
    'markerInst': <ITEM_ID:1,2> value referencing the marker instances, or a filename.
    'markerItem': The item's ID
    'previewVar': A stylevar which enables/disables the preview overlay.
    'previewinst': An instance to place at the marker location in preview mode.
        This should contain checkmarks to display the value when testing.
    'previewMat': If set, the material to use for an overlay func_brush.
        The brush will be parented to the trigger, so it vanishes once killed.
        It is also non-solid.
    'previewScale': The scale for the func_brush materials.
    'previewActivate', 'previewDeactivate': The 'instance:name;Input' value
        to turn the previewInst on and off.

    'triggerActivate, triggerDeactivate': The outputs used when the trigger
        turns on or off.

    'coopVar': The instance variable which enables detecting both Coop players.
        The trigger will be a trigger_playerteam.

    'coopActivate, coopDeactivate': The outputs used when coopVar is enabled.
        These should be suitable for a logic_coop_manager.
    'coopOnce': If true, kill the manager after it first activates.

    'keys': A block of keyvalues for the trigger brush. Origin and targetname
        will be set automatically.
    'localkeys': The same as above, except values will be changed to use
        instance-local names.
    """
    marker = resolve_inst(res['markerInst'])

    markers = {}
    for inst in vbsp.VMF.by_class['func_instance']:
        if inst['file'].casefold() in marker:
            markers[inst['targetname']] = inst

    if not markers:  # No markers in the map - abort
        return RES_EXHAUSTED

    trig_act = res['triggerActivate', 'OnStartTouchAll']
    trig_deact = res['triggerDeactivate','OnEndTouchAll']

    coop_var = res['coopVar', None]
    coop_act = res['coopActivate', 'OnChangeToAllTrue']
    coop_deact = res['coopDeactivate', 'OnChangeToAnyFalse']
    coop_only_once = res.bool('coopOnce')

    marker_connection = conditions.CONNECTIONS[res['markerItem'].casefold()]
    mark_act_name, mark_act_out = marker_connection.out_act
    mark_deact_name, mark_deact_out = marker_connection.out_deact
    del marker_connection

    preview_var = res['previewVar', ''].casefold()

    # Display preview overlays if it's preview mode, and the style var is true
    # or does not exist
    if vbsp.IS_PREVIEW and (not preview_var or vbsp.settings['style_vars'][preview_var]):
        preview_mat = res['previewMat', '']
        preview_inst_file = res['previewInst', '']
        pre_act_name, pre_act_inp = Output.parse_name(
            res['previewActivate', ''])
        pre_deact_name, pre_deact_inp = Output.parse_name(
            res['previewDeactivate', ''])
        preview_scale = srctools.conv_float(res['previewScale', '0.25'], 0.25)
    else:
        # Deactivate the preview_ options when publishing.
        preview_mat = preview_inst_file = ''
        pre_act_name = pre_deact_name = None
        pre_act_inp = pre_deact_inp = ''
        preview_scale = 0.25

    # Now convert each brush
    # Use list() to freeze it, allowing us to delete from the dict
    for targ, inst in list(markers.items()):  # type: str, VLib.Entity
        for out in inst.output_targets():
            if out in markers:
                other = markers[out]  # type: Entity
                del markers[out]  # Don't let it get repeated
                break
        else:
            if inst.fixup['$connectioncount'] == '0':
                # If the item doesn't have any connections, 'connect'
                # it to itself so we'll generate a 1-block trigger.
                other = inst
            else:
                continue  # It's a marker with an input, the other in the pair
                # will handle everything.

        for ent in {inst, other}:
            # Only do once if inst == other
            ent.remove()

        is_coop = vbsp.GAME_MODE == 'COOP' and (
            inst.fixup.bool(coop_var) or
            other.fixup.bool(coop_var)
        )

        bbox_min, bbox_max = Vec.bbox(
            Vec.from_str(inst['origin']),
            Vec.from_str(other['origin'])
        )

        # Extend to the edge of the blocks.
        bbox_min -= 64
        bbox_max += 64

        out_ent = trig_ent = vbsp.VMF.create_ent(
            classname='trigger_multiple',  # Default
            # Use the 1st instance's name - that way other inputs control the
            # trigger itself.
            targetname=targ,
            origin=inst['origin'],
            angles='0 0 0',
        )
        trig_ent.solids = [
            vbsp.VMF.make_prism(
                bbox_min,
                bbox_max,
                mat=const.Tools.TRIGGER,
            ).solid,
        ]

        # Use 'keys' and 'localkeys' blocks to set all the other keyvalues.
        conditions.set_ent_keys(trig_ent, inst, res)

        if is_coop:
            trig_ent['spawnflags'] = '1'  # Clients
            trig_ent['classname'] = 'trigger_playerteam'

            out_ent_name = conditions.local_name(inst, 'man')
            out_ent = vbsp.VMF.create_ent(
                classname='logic_coop_manager',
                targetname=out_ent_name,
                origin=inst['origin']
            )
            if coop_only_once:
                # Kill all the ents when both players are present.
                out_ent.add_out(
                    Output('OnChangeToAllTrue', out_ent_name, 'Kill'),
                    Output('OnChangeToAllTrue', targ, 'Kill'),
                )
            trig_ent.add_out(
                Output('OnStartTouchBluePlayer', out_ent_name, 'SetStateATrue'),
                Output('OnStartTouchOrangePlayer', out_ent_name, 'SetStateBTrue'),
                Output('OnEndTouchBluePlayer', out_ent_name, 'SetStateAFalse'),
                Output('OnEndTouchOrangePlayer', out_ent_name, 'SetStateBFalse'),
            )
            act_out = coop_act
            deact_out = coop_deact
        else:
            act_out = trig_act
            deact_out = trig_deact

        if preview_mat:
            preview_brush = vbsp.VMF.create_ent(
                classname='func_brush',
                parentname=targ,
                origin=inst['origin'],

                Solidity='1',  # Not solid
                drawinfastreflection='1',  # Draw in goo..

                # Disable shadows and lighting..
                disableflashlight='1',
                disablereceiveshadows='1',
                disableshadowdepth='1',
                disableshadows='1',
            )
            preview_brush.solids = [
                # Make it slightly smaller, so it doesn't z-fight with surfaces.
                vbsp.VMF.make_prism(
                    bbox_min + 0.5,
                    bbox_max - 0.5,
                    mat=preview_mat,
                ).solid,
            ]
            for face in preview_brush.sides():
                face.scale = preview_scale

        if preview_inst_file:
            vbsp.VMF.create_ent(
                classname='func_instance',
                targetname=targ + '_preview',
                file=preview_inst_file,
                # Put it at the second marker, since that's usually
                # closest to antlines if present.
                origin=other['origin'],
            )

            if pre_act_name and trig_act:
                out_ent.add_out(Output(
                    trig_act,
                    targ + '_preview',
                    inst_in=pre_act_name,
                    inp=pre_act_inp,
                ))
            if pre_deact_name and trig_deact:
                out_ent.add_out(Output(
                    trig_deact,
                    targ + '_preview',
                    inst_in=pre_deact_name,
                    inp=pre_deact_inp,
                ))

        # Now copy over the outputs from the markers, making it work.
        for out in inst.outputs + other.outputs:
            # Skip the output joining the two markers together.
            if out.target == other['targetname']:
                continue

            if out.inst_out == mark_act_name and out.output == mark_act_out:
                ent_out = act_out
            elif out.inst_out == mark_deact_name and out.output == mark_deact_out:
                ent_out = deact_out
            else:
                continue  # Skip this output - it's somehow invalid for this item.

            if not ent_out:
                continue  # Allow setting the output to "" to skip

            out_ent.add_out(Output(
                ent_out,
                out.target,
                inst_in=out.inst_in,
                inp=out.input,
                param=out.params,
                delay=out.delay,
                times=out.times,
            ))

    return RES_EXHAUSTED
Esempio n. 13
0
 def get_input(prop_name: str):
     """Parse an input command."""
     try:
         return Output.parse_name(conf[prop_name])
     except IndexError:
         return None
Esempio n. 14
0
    def parse(item_id: str, conf: Property):
        """Read the item type info from the given config."""

        def get_outputs(prop_name):
            """Parse all the outputs with this name."""
            return [
                Output.parse(prop)
                for prop in
                conf.find_all(prop_name)
                # Allow blank to indicate no output.
                if prop.value != ''
            ]

        enable_cmd = get_outputs('enable_cmd')
        disable_cmd = get_outputs('disable_cmd')
        lock_cmd = get_outputs('lock_cmd')
        unlock_cmd = get_outputs('unlock_cmd')

        inf_lock_only = conf.bool('inf_lock_only')

        timer_done_cmd = get_outputs('timer_done_cmd')
        if 'timer_sound_pos' in conf:
            timer_sound_pos = conf.vec('timer_sound_pos')
            force_timer_sound = conf.bool('force_timer_sound')
        else:
            timer_sound_pos = None
            force_timer_sound = False

        try:
            input_type = InputType(
                conf['Type', 'default'].casefold()
            )
        except ValueError:
            raise ValueError('Invalid input type "{}": {}'.format(
                item_id, conf['type'],
            )) from None

        invert_var = conf['invertVar', '0']

        try:
            spawn_fire = FeatureMode(conf['spawnfire', 'never'].casefold())
        except ValueError:
            # Older config option - it was a bool for always/never.
            spawn_fire_bool = conf.bool('spawnfire', None)
            if spawn_fire_bool is None:
                raise  # Nope, not a bool.

            spawn_fire = FeatureMode.ALWAYS if spawn_fire_bool else FeatureMode.NEVER

        try:
            sec_spawn_fire = FeatureMode(conf['sec_spawnfire', 'never'].casefold())
        except ValueError:  # Default to primary value.
            sec_spawn_fire = FeatureMode.NEVER

        if input_type is InputType.DUAL:
            sec_enable_cmd = get_outputs('sec_enable_cmd')
            sec_disable_cmd = get_outputs('sec_disable_cmd')

            try:
                default_dual = CONN_TYPE_NAMES[
                    conf['Default_Dual', 'primary'].casefold()
                ]
            except KeyError:
                raise ValueError('Invalid default type for "{}": {}'.format(
                    item_id, conf['Default_Dual'],
                )) from None

            # We need an affinity to use when nothing else specifies it.
            if default_dual is ConnType.DEFAULT:
                raise ValueError('Must specify a default type for "{}"!'.format(
                    item_id,
                )) from None

            sec_invert_var = conf['sec_invertVar', '0']
        else:
            # No dual type, set to dummy values.
            sec_enable_cmd = []
            sec_disable_cmd = []
            default_dual = ConnType.DEFAULT
            sec_invert_var = ''

        try:
            output_type = CONN_TYPE_NAMES[
                conf['DualType', 'default'].casefold()
            ]
        except KeyError:
            raise ValueError('Invalid output affinity for "{}": {}'.format(
                item_id, conf['DualType'],
            )) from None

        def get_input(prop_name: str):
            """Parse an input command."""
            try:
                return Output.parse_name(conf[prop_name])
            except IndexError:
                return None

        out_act = get_input('out_activate')
        out_deact = get_input('out_deactivate')
        out_lock = get_input('out_lock')
        out_unlock = get_input('out_unlock')

        timer_start = timer_stop = None
        if 'out_timer_start' in conf:
            timer_start = [
                Output.parse_name(prop.value)
                for prop in conf.find_all('out_timer_start')
                if prop.value
            ]
        if 'out_timer_stop' in conf:
            timer_stop = [
                Output.parse_name(prop.value)
                for prop in conf.find_all('out_timer_stop')
                if prop.value
            ]

        return Config(
            item_id, default_dual, input_type,
            spawn_fire, invert_var, enable_cmd, disable_cmd,
            sec_spawn_fire, sec_invert_var, sec_enable_cmd, sec_disable_cmd,
            output_type, out_act, out_deact,
            lock_cmd, unlock_cmd, out_lock, out_unlock, inf_lock_only,
            timer_sound_pos, timer_done_cmd, force_timer_sound,
            timer_start, timer_stop,
        )
Esempio n. 15
0
def res_resizeable_trigger(vmf: VMF, res: Property):
    """Replace two markers with a trigger brush.  

    This is run once to affect all of an item.  
    Options:
    * `markerInst`: <ITEM_ID:1,2> value referencing the marker instances, or a filename.
    * `markerItem`: The item's ID
    * `previewConf`: A item config which enables/disables the preview overlay.
    * `previewInst`: An instance to place at the marker location in preview mode.
        This should contain checkmarks to display the value when testing.
    * `previewMat`: If set, the material to use for an overlay func_brush.
        The brush will be parented to the trigger, so it vanishes once killed.
        It is also non-solid.
    * `previewScale`: The scale for the func_brush materials.
    * `previewActivate`, `previewDeactivate`: The VMF output to turn the
        previewInst on and off.

    * `triggerActivate, triggerDeactivate`: The `instance:name;Output`
        outputs used when the trigger turns on or off.

    * `coopVar`: The instance variable which enables detecting both Coop players.
        The trigger will be a trigger_playerteam.

    * `coopActivate, coopDeactivate`: The `instance:name;Output` outputs used
        when coopVar is enabled. These should be suitable for a logic_coop_manager.
    * `coopOnce`: If true, kill the manager after it first activates.

    * `keys`: A block of keyvalues for the trigger brush. Origin and targetname
        will be set automatically.
    * `localkeys`: The same as above, except values will be changed to use
        instance-local names.
    """
    marker = instanceLocs.resolve(res['markerInst'])

    marker_names = set()

    for inst in vmf.by_class['func_instance']:
        if inst['file'].casefold() in marker:
            marker_names.add(inst['targetname'])
            # Unconditionally delete from the map, so it doesn't
            # appear even if placed wrongly.
            inst.remove()

    if not marker_names:  # No markers in the map - abort
        return RES_EXHAUSTED

    item_id = res['markerItem']

    # Synthesise the item type used for the final trigger.
    item_type_sp = connections.ItemType(
        id=item_id + ':TRIGGER',
        output_act=Output.parse_name(res['triggerActivate', 'OnStartTouchAll']),
        output_deact=Output.parse_name(res['triggerDeactivate', 'OnEndTouchAll']),
    )

    # For Coop, we add a logic_coop_manager in the mix so both players can
    # be handled.
    try:
        coop_var = res['coopVar']
    except LookupError:
        coop_var = item_type_coop = None
        coop_only_once = False
    else:
        coop_only_once = res.bool('coopOnce')
        item_type_coop = connections.ItemType(
            id=item_id + ':TRIGGER_COOP',
            output_act=Output.parse_name(
                res['coopActivate', 'OnChangeToAllTrue']
            ),
            output_deact=Output.parse_name(
                res['coopDeactivate', 'OnChangeToAnyFalse']
            ),
        )

    # Display preview overlays if it's preview mode, and the config is true
    pre_act = pre_deact = None
    if vbsp.IS_PREVIEW and vbsp_options.get_itemconf(res['previewConf', ''], False):
        preview_mat = res['previewMat', '']
        preview_inst_file = res['previewInst', '']
        preview_scale = res.float('previewScale', 0.25)
        # None if not found.
        with suppress(LookupError):
            pre_act = Output.parse(res.find_key('previewActivate'))
        with suppress(LookupError):
            pre_deact = Output.parse(res.find_key('previewDeactivate'))
    else:
        # Deactivate the preview_ options when publishing.
        preview_mat = preview_inst_file = ''
        preview_scale = 0.25

    # Now go through each brush.
    # We do while + pop to allow removing both names each loop through.
    todo_names = set(marker_names)
    while todo_names:
        targ = todo_names.pop()

        mark1 = connections.ITEMS.pop(targ)
        for conn in mark1.outputs:
            if conn.to_item.name in marker_names:
                mark2 = conn.to_item
                conn.remove()  # Delete this connection.
                todo_names.discard(mark2.name)
                del connections.ITEMS[mark2.name]
                break
        else:
            if not mark1.inputs:
                # If the item doesn't have any connections, 'connect'
                # it to itself so we'll generate a 1-block trigger.
                mark2 = mark1
            else:
                # It's a marker with an input, the other in the pair
                # will handle everything.
                # But reinstate it in ITEMS.
                connections.ITEMS[targ] = mark1
                continue

        inst1 = mark1.inst
        inst2 = mark2.inst

        is_coop = coop_var is not None and vbsp.GAME_MODE == 'COOP' and (
            inst1.fixup.bool(coop_var) or
            inst2.fixup.bool(coop_var)
        )

        bbox_min, bbox_max = Vec.bbox(
            Vec.from_str(inst1['origin']),
            Vec.from_str(inst2['origin'])
        )
        origin = (bbox_max + bbox_min) / 2

        # Extend to the edge of the blocks.
        bbox_min -= 64
        bbox_max += 64

        out_ent = trig_ent = vmf.create_ent(
            classname='trigger_multiple',  # Default
            targetname=targ,
            origin=origin,
            angles='0 0 0',
        )
        trig_ent.solids = [
            vmf.make_prism(
                bbox_min,
                bbox_max,
                mat=const.Tools.TRIGGER,
            ).solid,
        ]

        # Use 'keys' and 'localkeys' blocks to set all the other keyvalues.
        conditions.set_ent_keys(trig_ent, inst, res)

        if is_coop:
            trig_ent['spawnflags'] = '1'  # Clients
            trig_ent['classname'] = 'trigger_playerteam'

            out_ent = manager = vmf.create_ent(
                classname='logic_coop_manager',
                targetname=conditions.local_name(inst, 'man'),
                origin=origin,
            )

            item = connections.Item(
                out_ent,
                item_type_coop,
                mark1.ant_floor_style,
                mark1.ant_wall_style,
            )

            if coop_only_once:
                # Kill all the ents when both players are present.
                manager.add_out(
                    Output('OnChangeToAllTrue', manager, 'Kill'),
                    Output('OnChangeToAllTrue', targ, 'Kill'),
                )
            trig_ent.add_out(
                Output('OnStartTouchBluePlayer', manager, 'SetStateATrue'),
                Output('OnStartTouchOrangePlayer', manager, 'SetStateBTrue'),
                Output('OnEndTouchBluePlayer', manager, 'SetStateAFalse'),
                Output('OnEndTouchOrangePlayer', manager, 'SetStateBFalse'),
            )
        else:
            item = connections.Item(
                trig_ent,
                item_type_sp,
                mark1.ant_floor_style,
                mark1.ant_wall_style,
            )

        # Register, and copy over all the antlines.
        connections.ITEMS[item.name] = item
        item.ind_panels = mark1.ind_panels | mark2.ind_panels
        item.antlines = mark1.antlines | mark2.antlines
        item.shape_signs = mark1.shape_signs + mark2.shape_signs

        if preview_mat:
            preview_brush = vmf.create_ent(
                classname='func_brush',
                parentname=targ,
                origin=origin,

                Solidity='1',  # Not solid
                drawinfastreflection='1',  # Draw in goo..

                # Disable shadows and lighting..
                disableflashlight='1',
                disablereceiveshadows='1',
                disableshadowdepth='1',
                disableshadows='1',
            )
            preview_brush.solids = [
                # Make it slightly smaller, so it doesn't z-fight with surfaces.
                vmf.make_prism(
                    bbox_min + 0.5,
                    bbox_max - 0.5,
                    mat=preview_mat,
                ).solid,
            ]
            for face in preview_brush.sides():
                face.scale = preview_scale

        if preview_inst_file:
            pre_inst = vmf.create_ent(
                classname='func_instance',
                targetname=targ + '_preview',
                file=preview_inst_file,
                # Put it at the second marker, since that's usually
                # closest to antlines if present.
                origin=inst2['origin'],
            )

            if pre_act is not None:
                out = pre_act.copy()
                out.inst_out, out.output = item.output_act()
                out.target = conditions.local_name(pre_inst, out.target)
                out_ent.add_out(out)
            if pre_deact is not None:
                out = pre_deact.copy()
                out.inst_out, out.output = item.output_deact()
                out.target = conditions.local_name(pre_inst, out.target)
                out_ent.add_out(out)

        for conn in mark1.outputs | mark2.outputs:
            conn.from_item = item

    return RES_EXHAUSTED
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,
        ))
Esempio n. 17
0
    def parse(cls, conf: Property):
        """Read in a fizzler from a config."""
        fizz_id = conf['id']
        item_ids = [prop.value.casefold() for prop in conf.find_all('item_id')]

        try:
            model_name_type = ModelName(conf['NameType', 'same'].casefold())
        except ValueError:
            LOGGER.warning('Bad model name type: "{}"', conf['NameType'])
            model_name_type = ModelName.SAME

        model_local_name = conf['ModelName', '']
        if not model_local_name:
            # We can't rename without a local name.
            model_name_type = ModelName.SAME

        inst = {}
        for inst_type, is_static in itertools.product(FizzInst, (False, True)):
            inst_type_name = inst_type.value + ('_static' if is_static else '')
            inst[inst_type, is_static] = instances = [
                file for prop in conf.find_all(inst_type_name)
                for file in instanceLocs.resolve(prop.value)
            ]
            # Allow specifying weights to bias model locations
            weights = conf[inst_type_name + '_weight', '']
            if weights:
                # Produce the weights, then process through the original
                # list to build a new one with repeated elements.
                inst[inst_type, is_static] = instances = [
                    instances[i] for i in conditions.weighted_random(
                        len(instances), weights)
                ]
            # If static versions aren't given, reuse non-static ones.
            # We do False, True so it's already been calculated.
            if not instances and is_static:
                inst[inst_type, True] = inst[inst_type, False]

        if not inst[FizzInst.BASE, False]:
            LOGGER.warning('No base instance set! for "{}"!', fizz_id)

        voice_attrs = []
        for prop in conf.find_all('Has'):
            if prop.has_children():
                for child in prop:
                    voice_attrs.append(child.name.casefold())
            else:
                voice_attrs.append(prop.value.casefold())

        out_activate = conf['OutActivate', None]
        if out_activate is not None:
            out_activate = Output.parse_name(out_activate)

        out_deactivate = conf['OutDeactivate', None]
        if out_deactivate is not None:
            out_deactivate = Output.parse_name(out_deactivate)

        pack_lists = {prop.value for prop in conf.find_all('Pack')}
        pack_lists_static = {
            prop.value
            for prop in conf.find_all('PackStatic')
        }

        brushes = [FizzlerBrush.parse(prop) for prop in conf.find_all('Brush')]

        beams = []  # type: List[FizzBeam]
        for beam_prop in conf.find_all('Beam'):
            offsets = [
                Vec.from_str(off.value) for off in beam_prop.find_all('pos')
            ]
            keys = Property('', [
                beam_prop.find_key('Keys', []),
                beam_prop.find_key('LocalKeys', [])
            ])
            beams.append(
                FizzBeam(
                    offsets,
                    keys,
                    beam_prop.int('RandSpeedMin', 0),
                    beam_prop.int('RandSpeedMax', 0),
                ))

        try:
            temp_conf = conf.find_key('TemplateBrush')
        except NoKeyError:
            temp_brush_keys = temp_min = temp_max = temp_single = None
        else:
            temp_brush_keys = Property('--', [
                temp_conf.find_key('Keys'),
                temp_conf.find_key('LocalKeys', []),
            ])

            # Find and load the templates.
            temp_min = temp_conf['Left', None]
            temp_max = temp_conf['Right', None]
            temp_single = temp_conf['Single', None]

        return FizzlerType(
            fizz_id,
            item_ids,
            voice_attrs,
            pack_lists,
            pack_lists_static,
            model_local_name,
            model_name_type,
            out_activate,
            out_deactivate,
            brushes,
            beams,
            inst,
            temp_brush_keys,
            temp_min,
            temp_max,
            temp_single,
        )
Esempio n. 18
0
 def parse(item, key):
     """Parse the output value, handling values that aren't present."""
     val = item[key, '']
     if not val:
         return None, ''
     return Output.parse_name(val)