コード例 #1
0
ファイル: glass.py プロジェクト: BenVlodgi/BEE2.4
def glass_item_setup(conf: dict, item_id, config_dict):
    """Build the config dictionary for a custom glass item."""
    [base_inst] = resolve_inst('<{}:0>'.format(item_id))

    conf.update({
        'frame_' + name: resolve_inst('<{}:bee2_frame_{}>'.format(item_id, name))[0]
        for name in ['edge', 'single', 'ubend', 'corner']
    })
    config_dict[base_inst.casefold()] = conf
コード例 #2
0
def glass_item_setup(conf: dict, item_id, config_dict):
    """Build the config dictionary for a custom glass item."""
    [base_inst] = resolve_inst('<{}:0>'.format(item_id))

    conf.update({
        'frame_' + name:
        resolve_inst('<{}:bee2_frame_{}>'.format(item_id, name))[0]
        for name in ['edge', 'single', 'ubend', 'corner']
    })
    config_dict[base_inst.casefold()] = conf
コード例 #3
0
def res_camera_setup(res: Property):
    return {
        'cam_off': Vec.from_str(res['CamOff', '']),
        'yaw_off': Vec.from_str(res['YawOff', '']),
        'pitch_off': Vec.from_str(res['PitchOff', '']),
        'io_inst': resolve_inst(res['IO_inst'])[0],
        'yaw_inst': resolve_inst(res['yawInst', ''])[0],
        'pitch_inst': resolve_inst(res['pitchInst', ''])[0],
        'yaw_range': srctools.conv_int(res['YawRange', ''], 90),
        'pitch_range': srctools.conv_int(res['YawRange', ''], 90),
    }
コード例 #4
0
ファイル: conditions.py プロジェクト: NotFirestorm348/BEE2.4
def res_add_global_inst(_, res):
    """Add one instance in a location.

    Once this is executed, it will be ignored thereafter.
    """
    if res.value is not None:
        if (res['file'] not in GLOBAL_INSTANCES or
                utils.conv_bool(res['allow_multiple', '0'], True)):
            # By default we will skip adding the instance
            # if was already added - this is helpful for
            # items that add to original items, or to avoid
            # bugs.
            new_inst = VLib.Entity(VMF, keys={
                "classname": "func_instance",
                "targetname": res['name', ''],
                "file": resolve_inst(res['file'])[0],
                "angles": res['angles', '0 0 0'],
                "origin": res['position', '0 0 -10000'],
                "fixup_style": res['fixup_style', '0'],
                })
            GLOBAL_INSTANCES.append(res['file'])
            if new_inst['targetname'] == '':
                new_inst['targetname'] = "inst_"
                new_inst.make_unique()
            VMF.add_ent(new_inst)
            res.value = None  # Disable this
コード例 #5
0
ファイル: conditions.py プロジェクト: xDadiKx/BEE2.4
def res_add_global_inst(_, res):
    """Add one instance in a location.

    Once this is executed, it will be ignored thereafter.
    """
    if res.value is not None:
        if utils.conv_bool(res["allow_multiple", "0"]) or res["file"] not in GLOBAL_INSTANCES:
            # By default we will skip adding the instance
            # if was already added - this is helpful for
            # items that add to original items, or to avoid
            # bugs.
            new_inst = VLib.Entity(
                VMF,
                keys={
                    "classname": "func_instance",
                    "targetname": res["name", ""],
                    "file": resolve_inst(res["file"])[0],
                    "angles": res["angles", "0 0 0"],
                    "origin": res["position", "0 0 -10000"],
                    "fixup_style": res["fixup_style", "0"],
                },
            )
            GLOBAL_INSTANCES.add(res["file"])
            if new_inst["targetname"] == "":
                new_inst["targetname"] = "inst_"
                new_inst.make_unique()
            VMF.add_ent(new_inst)
    return True  # Remove this result
コード例 #6
0
ファイル: instances.py プロジェクト: goodDOS/BEE2.4
def flag_has_inst(_, flag):
    """Checks if the given instance is present anywhere in the map."""
    flags = resolve_inst(flag.value)
    return any(
        inst.casefold() in flags
        for inst in
        ALL_INST
    )
コード例 #7
0
ファイル: conditions.py プロジェクト: NotFirestorm348/BEE2.4
def flag_has_inst(_, flag):
    """Return true if the filename is present anywhere in the map."""
    flags = resolve_inst(flag.value)
    return any(
        inst in flags
        for inst in
        ALL_INST
    )
コード例 #8
0
def res_vactube_setup(res: Property):
    group = res['group', 'DEFAULT_GROUP']

    if group not in VAC_CONFIGS:
        # Store our values in the CONFIGS dictionary
        config, inst_configs = VAC_CONFIGS[group] = {}, {}
    else:
        # Grab the already-filled values, and add to them
        config, inst_configs = VAC_CONFIGS[group]

    for block in res.find_all("Instance"):
        # Configuration info for each instance set..
        conf = {
            # The three sizes of corner instance
            ('corner', 1):
            block['corner_small_inst', ''],
            ('corner', 2):
            block['corner_medium_inst', ''],
            ('corner', 3):
            block['corner_large_inst', ''],
            ('corner_temp', 1):
            block['temp_corner_small', ''],
            ('corner_temp', 2):
            block['temp_corner_medium', ''],
            ('corner_temp', 3):
            block['temp_corner_large', ''],

            # Straight instances connected to the next part
            'straight':
            block['straight_inst', ''],

            # Supports attach to the 4 sides of the straight part,
            # if there's a brush there.
            'support':
            block['support_inst', ''],
            'is_tsection':
            srctools.conv_bool(block['is_tsection', '0']),
            ('entry', 'wall'):
            block['entry_inst'],
            ('entry', 'floor'):
            block['entry_floor_inst'],
            ('entry', 'ceiling'):
            block['entry_ceil_inst'],
            'exit':
            block['exit_inst'],
        }

        for prop in block.find_all("File"):
            try:
                size, file = prop.value.split(":", 1)
            except ValueError:
                size = 1
                file = prop.value

            inst_configs[resolve_inst(file)[0]] = conf, srctools.conv_int(
                size, 1)

    return group
コード例 #9
0
def res_find_potential_tag_fizzlers(vmf: VMF, inst: Entity):
    """We need to know which items are 'real' fizzlers.

    This is used for Aperture Tag paint fizzlers.
    """
    if vbsp_options.get(str, 'game_id') != utils.STEAM_IDS['TAG']:
        return RES_EXHAUSTED

    if inst['file'].casefold() not in resolve_inst('<ITEM_BARRIER_HAZARD:0>'):
        return

    # The key list in the dict will be a set of all fizzler items!
    tag_fizzlers[inst['targetname']] = inst

    if tag_fizzler_locs:  # Only loop through fizzlers once.
        return

    # Determine the origins by first finding the bounding box of the brushes,
    # then averaging.
    for fizz in vmf.by_class['trigger_portal_cleanser']:
        name = fizz['targetname'][:-6]  # Strip off '_brush'
        bbox_min, bbox_max = fizz.get_bbox()
        if name in tag_fizzler_locs:
            orig_min, orig_max = tag_fizzler_locs[name]
            orig_min.min(bbox_min)
            orig_max.max(bbox_max)
        else:
            tag_fizzler_locs[name] = bbox_min, bbox_max

    for name, (s, l) in tag_fizzler_locs.items():
        # Figure out how to compare for this brush.
        # If it's horizontal, signs should point to the center:
        if abs(s.z - l.z) == 2:
            tag_fizzler_locs[name] =(
                'z',
                s.x + l.x / 2,
                s.y + l.y / 2,
                s.z + 1,
            )
            continue
        # For the vertical directions, we want to compare based on the line segment.
        if abs(s.x - l.x) == 2:  # Y direction
            tag_fizzler_locs[name] = (
                'y',
                s.y,
                l.y,
                s.x + 1,
            )
        else:  # Extends in X direction
            tag_fizzler_locs[name] = (
                'x',
                s.x,
                l.x,
                s.y + 1,
            )
コード例 #10
0
def res_find_potential_tag_fizzlers(vmf: VMF, inst: Entity):
    """We need to know which items are 'real' fizzlers.

    This is used for Aperture Tag paint fizzlers.
    """
    if vbsp_options.get(str, 'game_id') != utils.STEAM_IDS['TAG']:
        return RES_EXHAUSTED

    if inst['file'].casefold() not in resolve_inst('<ITEM_BARRIER_HAZARD:0>'):
        return

    # The key list in the dict will be a set of all fizzler items!
    tag_fizzlers[inst['targetname']] = inst

    if tag_fizzler_locs:  # Only loop through fizzlers once.
        return

    # Determine the origins by first finding the bounding box of the brushes,
    # then averaging.
    for fizz in vmf.by_class['trigger_portal_cleanser']:
        name = fizz['targetname'][:-6]  # Strip off '_brush'
        bbox_min, bbox_max = fizz.get_bbox()
        if name in tag_fizzler_locs:
            orig_min, orig_max = tag_fizzler_locs[name]
            orig_min.min(bbox_min)
            orig_max.max(bbox_max)
        else:
            tag_fizzler_locs[name] = bbox_min, bbox_max

    for name, (s, l) in tag_fizzler_locs.items():
        # Figure out how to compare for this brush.
        # If it's horizontal, signs should point to the center:
        if abs(s.z - l.z) == 2:
            tag_fizzler_locs[name] = (
                'z',
                s.x + l.x / 2,
                s.y + l.y / 2,
                s.z + 1,
            )
            continue
        # For the vertical directions, we want to compare based on the line segment.
        if abs(s.x - l.x) == 2:  # Y direction
            tag_fizzler_locs[name] = (
                'y',
                s.y,
                l.y,
                s.x + 1,
            )
        else:  # Extends in X direction
            tag_fizzler_locs[name] = (
                'x',
                s.x,
                l.x,
                s.y + 1,
            )
コード例 #11
0
ファイル: conditions.py プロジェクト: NotFirestorm348/BEE2.4
def res_add_overlay_inst(inst, res):
    """Add another instance on top of this one."""
    print('adding overlay', res['file'])
    VMF.create_ent(
        classname='func_instance',
        targetname=inst['targetname'],
        file=resolve_inst(res['file', ''])[0],
        angles=inst['angles'],
        origin=inst['origin'],
        fixup_style=res['fixup_style', '0'],
    )
コード例 #12
0
def res_unst_scaffold_setup(res: Property):
    group = res['group', 'DEFAULT_GROUP']

    if group not in SCAFFOLD_CONFIGS:
        # Store our values in the CONFIGS dictionary
        targ_inst, links = SCAFFOLD_CONFIGS[group] = {}, {}
    else:
        # Grab the already-filled values, and add to them
        targ_inst, links = SCAFFOLD_CONFIGS[group]

    for block in res.find_all("Instance"):
        conf = {
            # If set, adjusts the offset appropriately
            'is_piston': srctools.conv_bool(block['isPiston', '0']),
            'rotate_logic': srctools.conv_bool(block['AlterAng', '1'], True),
            'off_floor': Vec.from_str(block['FloorOff', '0 0 0']),
            'off_wall': Vec.from_str(block['WallOff', '0 0 0']),
            'logic_start': block['startlogic', ''],
            'logic_end': block['endLogic', ''],
            'logic_mid': block['midLogic', ''],
            'logic_start_rev': block['StartLogicRev', None],
            'logic_end_rev': block['EndLogicRev', None],
            'logic_mid_rev': block['EndLogicRev', None],
            'inst_wall': block['wallInst', ''],
            'inst_floor': block['floorInst', ''],
            'inst_offset': block['offsetInst', None],
            # Specially rotated to face the next track!
            'inst_end': block['endInst', None],
        }
        for logic_type in ('logic_start', 'logic_mid', 'logic_end'):
            if conf[logic_type + '_rev'] is None:
                conf[logic_type + '_rev'] = conf[logic_type]

        for inst in resolve_inst(block['file']):
            targ_inst[inst] = conf

    # We need to provide vars to link the tracks and beams.
    for block in res.find_all('LinkEnt'):
        # The name for this set of entities.
        # It must be a '@' name, or the name will be fixed-up incorrectly!
        loc_name = block['name']
        if not loc_name.startswith('@'):
            loc_name = '@' + loc_name
        links[block['nameVar']] = {
            'name': loc_name,
            # The next entity (not set in end logic)
            'next': block['nextVar'],
            # A '*' name to reference all the ents (set on the start logic)
            'all': block['allVar', None],
        }

    return group  # We look up the group name to find the values.
コード例 #13
0
ファイル: scaffold.py プロジェクト: goodDOS/BEE2.4
def res_unst_scaffold_setup(res):
    group = res["group", "DEFAULT_GROUP"]

    if group not in SCAFFOLD_CONFIGS:
        # Store our values in the CONFIGS dictionary
        targ_inst, links = SCAFFOLD_CONFIGS[group] = {}, {}
    else:
        # Grab the already-filled values, and add to them
        targ_inst, links = SCAFFOLD_CONFIGS[group]

    for block in res.find_all("Instance"):
        conf = {
            # If set, adjusts the offset appropriately
            "is_piston": utils.conv_bool(block["isPiston", "0"]),
            "rotate_logic": utils.conv_bool(block["AlterAng", "1"], True),
            "off_floor": Vec.from_str(block["FloorOff", "0 0 0"]),
            "off_wall": Vec.from_str(block["WallOff", "0 0 0"]),
            "logic_start": block["startlogic", ""],
            "logic_end": block["endLogic", ""],
            "logic_mid": block["midLogic", ""],
            "logic_start_rev": block["StartLogicRev", None],
            "logic_end_rev": block["EndLogicRev", None],
            "logic_mid_rev": block["EndLogicRev", None],
            "inst_wall": block["wallInst", ""],
            "inst_floor": block["floorInst", ""],
            "inst_offset": block["offsetInst", None],
            # Specially rotated to face the next track!
            "inst_end": block["endInst", None],
        }
        for logic_type in ("logic_start", "logic_mid", "logic_end"):
            if conf[logic_type + "_rev"] is None:
                conf[logic_type + "_rev"] = conf[logic_type]

        for inst in resolve_inst(block["file"]):
            targ_inst[inst] = conf

    # We need to provide vars to link the tracks and beams.
    for block in res.find_all("LinkEnt"):
        # The name for this set of entities.
        # It must be a '@' name, or the name will be fixed-up incorrectly!
        loc_name = block["name"]
        if not loc_name.startswith("@"):
            loc_name = "@" + loc_name
        links[block["nameVar"]] = {
            "name": loc_name,
            # The next entity (not set in end logic)
            "next": block["nextVar"],
            # A '*' name to reference all the ents (set on the start logic)
            "all": block["allVar", None],
        }

    return group  # We look up the group name to find the values.
コード例 #14
0
ファイル: cutoutTile.py プロジェクト: goodDOS/BEE2.4
def find_indicator_panels(inst):
    """We need to locate indicator panels, so they aren't overwritten.
    """
    if inst['file'].casefold() not in resolve_inst('[indpan]'):
        return
    loc = Vec(0, 0, -64).rotate_by_str(inst['angles'])
    loc += Vec.from_str(inst['origin'])

    # Sometimes (light bridges etc) a sign will be halfway between
    # tiles, so in that case we need to force 2 tiles.
    loc_min = (loc - (15, 15, 0)) // 32 * 32 + (16, 16, 0)
    loc_max = (loc + (15, 15, 0)) // 32 * 32 + (16, 16, 0)
    FORCE_LOCATIONS.add(loc_min.as_tuple())
    FORCE_LOCATIONS.add(loc_max.as_tuple())
コード例 #15
0
ファイル: conditions.py プロジェクト: xDadiKx/BEE2.4
def res_add_overlay_inst(inst, res):
    """Add another instance on top of this one."""
    print("adding overlay", res["file"])
    overlay_inst = VMF.create_ent(
        classname="func_instance",
        targetname=inst["targetname", ""],
        file=resolve_inst(res["file", ""])[0],
        angles=inst["angles", "0 0 0"],
        origin=inst["origin"],
        fixup_style=res["fixup_style", "0"],
    )
    if utils.conv_bool(res["copy_fixup", "1"]):
        # Copy the fixup values across from the original instance
        for fixup, value in inst.fixup.items():
            overlay_inst.fixup[fixup] = value
コード例 #16
0
ファイル: vactubes.py プロジェクト: GLiTcH2/BEE2.4
def res_vactube_setup(res):
    group = res['group', 'DEFAULT_GROUP']

    if group not in VAC_CONFIGS:
        # Store our values in the CONFIGS dictionary
        config, inst_configs = VAC_CONFIGS[group] = {}, {}
    else:
        # Grab the already-filled values, and add to them
        config, inst_configs = VAC_CONFIGS[group]

    for block in res.find_all("Instance"):
        # Configuration info for each instance set..
        conf = {
            # The three sizes of corner instance
            ('corner', 1): block['corner_small_inst', ''],
            ('corner', 2): block['corner_medium_inst', ''],
            ('corner', 3): block['corner_large_inst', ''],

            ('corner_temp', 1): block['temp_corner_small', ''],
            ('corner_temp', 2): block['temp_corner_medium', ''],
            ('corner_temp', 3): block['temp_corner_large', ''],

            # Straight instances connected to the next part
            'straight': block['straight_inst', ''],

            # Supports attach to the 4 sides of the straight part,
            # if there's a brush there.
            'support': block['support_inst', ''],

            'is_tsection': utils.conv_bool(block['is_tsection', '0']),

            ('entry', 'wall'): block['entry_inst'],
            ('entry', 'floor'): block['entry_floor_inst'],
            ('entry', 'ceiling'): block['entry_ceil_inst'],

            'exit': block['exit_inst'],
        }

        for prop in block.find_all("File"):
            try:
                size, file = prop.value.split(":", 1)
            except ValueError:
                size = 1
                file = prop.value

            inst_configs[resolve_inst(file)[0]] = conf, utils.conv_int(size, 1)

    return group
コード例 #17
0
def find_indicator_panels(inst: Entity):
    """We need to locate indicator panels, so they aren't overwritten.
    """
    if inst['file'].casefold() not in resolve_inst('[indpan]'):
        return
    loc = Vec(0, 0, -64).rotate_by_str(inst['angles'])
    loc += Vec.from_str(inst['origin'])

    # Sometimes (light bridges etc) a sign will be halfway between
    # tiles, so in that case we need to force 2 tiles.
    loc_min = (loc - (15, 15, 0)) // 32 * 32  # type: Vec
    loc_max = (loc + (15, 15, 0)) // 32 * 32  # type: Vec
    loc_min += (16, 16, 0)
    loc_max += (16, 16, 0)
    FORCE_LOCATIONS.add(loc_min.as_tuple())
    FORCE_LOCATIONS.add(loc_max.as_tuple())
コード例 #18
0
ファイル: conditions.py プロジェクト: NotFirestorm348/BEE2.4
def res_cust_output(inst, res):
    """Add an additional output to the instance with any values.

    Always points to the targeted item.
    """
    over_name = '@' + inst['targetname'] + '_indicator'
    for toggle in 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

    # Make this a set to ignore repeated targetnames
    targets = {o.target for o in inst.outputs if o.target != toggle_name}

    kill_signs = utils.conv_bool(res["remIndSign", '0'], False)
    dec_con_count = utils.conv_bool(res["decConCount", '0'], False)
    targ_conditions = list(res.find_all("targCondition"))

    pan_files = resolve_inst('[indPan]')

    if kill_signs or dec_con_count or targ_conditions:
        for con_inst in VMF.by_class['func_instance']:
            if con_inst['targetname'] in targets:
                if kill_signs and con_inst in pan_files:
                    VMF.remove_ent(con_inst)
                if targ_conditions:
                    for cond in targ_conditions:
                        cond.value.test(con_inst)
                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
                        utils.con_log(
                            con_inst['targetname'] +
                            ' has invalid ConnectionCount!'
                        )
    for targ in targets:
        for out in res.find_all('addOut'):
            add_output(inst, out, targ)
コード例 #19
0
def mon_remove_bullseyes(inst: Entity):
    """Remove bullsyes used for cameras."""
    if not BULLSYE_LOCS:
        return RES_EXHAUSTED

    if inst['file'].casefold() not in resolve_inst('<ITEM_CATAPULT_TARGET>'):
        return

    LOGGER.info('Bullseye {}', BULLSYE_LOCS)

    origin = Vec(0, 0, -64)
    origin.localise(Vec.from_str(inst['origin']), Vec.from_str(inst['angles']))
    origin = origin.as_tuple()

    LOGGER.info('Pos: {} -> ', origin, BULLSYE_LOCS[origin])

    if BULLSYE_LOCS[origin]:
        BULLSYE_LOCS[origin] -= 1
        inst.remove()
コード例 #20
0
ファイル: addInstance.py プロジェクト: BenVlodgi/BEE2.4
def res_add_global_inst(res: Property):
    """Add one instance in a location.

    Options:
        allow_multiple: Allow multiple copies of this instance. If 0, the
            instance will not be added if it was already added.
        name: The targetname of the instance. IF blank, the instance will
              be given a name of the form 'inst_1234'.
        file: The filename for the instance.
        Angles: The orientation of the instance (defaults to '0 0 0').
        Origin: The location of the instance (defaults to '0 0 -10000').
        Fixup_style: The Fixup style for the instance. '0' (default) is
            Prefix, '1' is Suffix, and '2' is None.
    """
    if res.value is not None:
        if srctools.conv_bool(res["allow_multiple", "0"]) or res["file"] not in GLOBAL_INSTANCES:
            # By default we will skip adding the instance
            # if was already added - this is helpful for
            # items that add to original items, or to avoid
            # bugs.
            new_inst = Entity(
                vbsp.VMF,
                keys={
                    "classname": "func_instance",
                    "targetname": res["name", ""],
                    "file": resolve_inst(res["file"])[0],
                    "angles": res["angles", "0 0 0"],
                    "origin": res["position", "0 0 -10000"],
                    "fixup_style": res["fixup_style", "0"],
                },
            )
            GLOBAL_INSTANCES.add(res["file"])
            if new_inst["targetname"] == "":
                new_inst["targetname"] = "inst_"
                new_inst.make_unique()
            vbsp.VMF.add_ent(new_inst)
    return RES_EXHAUSTED
コード例 #21
0
def res_add_global_inst(res: Property):
    """Add one instance in a location.

    Options:
        allow_multiple: Allow multiple copies of this instance. If 0, the
            instance will not be added if it was already added.
        name: The targetname of the instance. IF blank, the instance will
              be given a name of the form 'inst_1234'.
        file: The filename for the instance.
        Angles: The orientation of the instance (defaults to '0 0 0').
        Origin: The location of the instance (defaults to '0 0 -10000').
        Fixup_style: The Fixup style for the instance. '0' (default) is
            Prefix, '1' is Suffix, and '2' is None.
    """
    if res.value is not None:
        if (
                srctools.conv_bool(res['allow_multiple', '0']) or
                res['file'] not in GLOBAL_INSTANCES):
            # By default we will skip adding the instance
            # if was already added - this is helpful for
            # items that add to original items, or to avoid
            # bugs.
            new_inst = Entity(vbsp.VMF, keys={
                "classname": "func_instance",
                "targetname": res['name', ''],
                "file": resolve_inst(res['file'])[0],
                "angles": res['angles', '0 0 0'],
                "origin": res['position', '0 0 -10000'],
                "fixup_style": res['fixup_style', '0'],
                })
            GLOBAL_INSTANCES.add(res['file'])
            if new_inst['targetname'] == '':
                new_inst['targetname'] = "inst_"
                new_inst.make_unique()
            vbsp.VMF.add_ent(new_inst)
    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
コード例 #23
0
ファイル: cutoutTile.py プロジェクト: GLiTcH2/BEE2.4
def res_cutout_tile(inst, res):
    """Generate random quarter tiles, like in Destroyed or Retro maps.

    - "MarkerItem" is the instance to look for.
    - "TileSize" can be "2x2" or "4x4".
    - rotateMax is the amount of degrees to rotate squarebeam models.

    Materials:
    - "squarebeams" is the squarebeams variant to use.
    - "ceilingwalls" are the sides of the ceiling section.
    - "floorbase" is the texture under floor sections.
    - "tile_glue" is used on top of a thinner tile segment.
    - "clip" is the player_clip texture used over floor segments.
        (This allows customising the surfaceprop.)
    - "Floor4x4Black", "Ceil2x2White" and other combinations can be used to
       override the textures used.
    """
    item = resolve_inst(res['markeritem'])

    INST_LOCS = {}  # Map targetnames -> surface loc
    CEIL_IO = []  # Pairs of ceil inst corners to cut out.
    FLOOR_IO = []  # Pairs of floor inst corners to cut out.

    overlay_ids = {}  # When we replace brushes, we need to fix any overlays
    # on that surface.

    MATS.clear()
    floor_edges = []  # Values to pass to add_floor_sides() at the end

    sign_loc = set(FORCE_LOCATIONS)
    # If any signage is present in the map, we need to force tiles to
    # appear at that location!
    for over in conditions.VMF.by_class['info_overlay']:
        if (
                over['material'].casefold() in FORCE_TILE_MATS and
                # Only check floor/ceiling overlays
                over['basisnormal'] in ('0 0 1', '0 0 -1')
                ):
            loc = Vec.from_str(over['origin'])
            # Sometimes (light bridges etc) a sign will be halfway between
            # tiles, so in that case we need to force 2 tiles.
            loc_min = (loc - (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            loc_max = (loc + (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            sign_loc.add(loc_min.as_tuple())
            sign_loc.add(loc_max.as_tuple())

    SETTINGS = {
        'floor_chance': utils.conv_int(
            res['floorChance', '100'], 100),
        'ceil_chance': utils.conv_int(
            res['ceilingChance', '100'], 100),
        'floor_glue_chance': utils.conv_int(
            res['floorGlueChance', '0']),
        'ceil_glue_chance': utils.conv_int(
            res['ceilingGlueChance', '0']),

        'rotate_beams': int(utils.conv_float(
            res['rotateMax', '0']) * BEAM_ROT_PRECISION),

        'beam_skin': res['squarebeamsSkin', '0'],

        'base_is_disp': utils.conv_bool(res['dispBase', '0']),

        'quad_floor': res['FloorSize', '4x4'].casefold() == '2x2',
        'quad_ceil': res['CeilingSize', '4x4'].casefold() == '2x2',
    }

    random.seed(vbsp.MAP_RAND_SEED + '_CUTOUT_TILE_NOISE')
    noise = SimplexNoise(period=4 * 40)  # 4 tiles/block, 50 blocks max

    # We want to know the number of neighbouring tile cutouts before
    # placing tiles - blocks away from the sides generate fewer tiles.
    floor_neighbours = defaultdict(dict)  # all_floors[z][x,y] = count

    for mat_prop in res['Materials', []]:
        MATS[mat_prop.name].append(mat_prop.value)

    if SETTINGS['base_is_disp']:
        # We want the normal brushes to become nodraw.
        MATS['floorbase_disp'] = MATS['floorbase']
        MATS['floorbase'] = ['tools/toolsnodraw']

        # Since this uses random data for initialisation, the alpha and
        # regular will use slightly different patterns.
        alpha_noise = SimplexNoise(period=4 * 50)
    else:
        alpha_noise = None

    for key, default in TEX_DEFAULT:
        if key not in MATS:
            MATS[key] = [default]

    # Find our marker ents
    for inst in conditions.VMF.by_class['func_instance']: # type: VLib.Entity
        if inst['file'].casefold() not in item:
            continue
        targ = inst['targetname']
        orient = Vec(0, 0, 1).rotate_by_str(inst['angles', '0 0 0'])
        # Check the orientation of the marker to figure out what to generate
        if orient == (0, 0, 1):
            io_list = FLOOR_IO
        else:
            io_list = CEIL_IO

        # Reuse orient to calculate where the solid face will be.
        loc = (orient * -64) + Vec.from_str(inst['origin'])
        INST_LOCS[targ] = loc

        for out in inst.output_targets():
            io_list.append((targ, out))

        if not inst.outputs and inst.fixup['$connectioncount'] == '0':
            # If the item doesn't have any connections, 'connect'
            # it to itself so we'll generate a 128x128 tile segment.
            io_list.append((targ, targ))
        inst.remove()  # Remove the instance itself from the map.

    for start_floor, end_floor in FLOOR_IO:
        if end_floor not in INST_LOCS:
            # Not a marker - remove this and the antline.
            for toggle in conditions.VMF.by_target[end_floor]:
                conditions.remove_ant_toggle(toggle)
            continue

        box_min = Vec(INST_LOCS[start_floor])
        box_min.min(INST_LOCS[end_floor])

        box_max = Vec(INST_LOCS[start_floor])
        box_max.max(INST_LOCS[end_floor])

        if box_min.z != box_max.z:
            continue  # They're not in the same level!
        z = box_min.z

        if SETTINGS['rotate_beams']:
            # We have to generate 1 model per 64x64 block to do rotation...
            gen_rotated_squarebeams(
                box_min - (64, 64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin'],
                max_rot=SETTINGS['rotate_beams'],
            )
        else:
            # Make the squarebeams props, using big models if possible
            gen_squarebeams(
                box_min + (-64, -64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin']
            )

        # Add a player_clip brush across the whole area
        conditions.VMF.add_brush(conditions.VMF.make_prism(
            p1=box_min - (64, 64, FLOOR_DEPTH),
            p2=box_max + (64, 64, 0),
            mat=MATS['clip'][0],
        ).solid)

        # Add a noportal_volume covering the surface, in case there's
        # room for a portal.
        noportal_solid = conditions.VMF.make_prism(
            # Don't go all the way to the sides, so it doesn't affect wall
            # brushes.
            p1=box_min - (63, 63, 9),
            p2=box_max + (63, 63, 0),
            mat='tools/toolsinvisible',
        ).solid
        noportal_ent = conditions.VMF.create_ent(
            classname='func_noportal_volume',
            origin=box_min.join(' '),
        )
        noportal_ent.solids.append(noportal_solid)

        if SETTINGS['base_is_disp']:
            # Use displacements for the base instead.
            make_alpha_base(
                box_min + (-64, -64, 0),
                box_max + (64, 64, 0),
                noise=alpha_noise,
            )

        for x, y in utils.iter_grid(
                min_x=int(box_min.x),
                max_x=int(box_max.x) + 1,
                min_y=int(box_min.y),
                max_y=int(box_max.y) + 1,
                stride=128,
                ):
            # Build the set of all positions..
            floor_neighbours[z][x, y] = -1

        # Mark borders we need to fill in, and the angle (for func_instance)
        # The wall is the face pointing inwards towards the bottom brush,
        # and the ceil is the ceiling of the block above the bordering grid
        # points.
        for x in range(int(box_min.x), int(box_max.x) + 1, 128):
            # North
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_max.y + 64, z - 64),
                ceil=Vec_tuple(x, box_max.y + 128, z),
                rot=270,
            ))
            # South
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_min.y - 64, z - 64),
                ceil=Vec_tuple(x, box_min.y - 128, z),
                rot=90,
            ))

        for y in range(int(box_min.y), int(box_max.y) + 1, 128):
            # East
            floor_edges.append(BorderPoints(
                wall=Vec(box_max.x + 64, y, z - 64),
                ceil=Vec_tuple(box_max.x + 128, y, z),
                rot=180,
            ))

            # West
            floor_edges.append(BorderPoints(
                wall=Vec(box_min.x - 64, y, z - 64),
                ceil=Vec_tuple(box_min.x - 128, y, z),
                rot=0,
            ))

    # Now count boundries near tiles, then generate them.

    # Do it seperately for each z-level:
    for z, xy_dict in floor_neighbours.items():  # type: float, dict
        for x, y in xy_dict:  # type: float, float
            # We want to count where there aren't any tiles
            xy_dict[x, y] = (
                ((x - 128, y - 128) not in xy_dict) +
                ((x - 128, y + 128) not in xy_dict) +
                ((x + 128, y - 128) not in xy_dict) +
                ((x + 128, y + 128) not in xy_dict) +

                ((x - 128, y) not in xy_dict) +
                ((x + 128, y) not in xy_dict) +
                ((x, y - 128) not in xy_dict) +
                ((x, y + 128) not in xy_dict)
            )

        max_x = max_y = 0

        weights = {}
        # Now the counts are all correct, compute the weight to apply
        # for tiles.
        # Adding the neighbouring counts will make a 5x5 area needed to set
        # the center to 0.

        for (x, y), cur_count in xy_dict.items():
            max_x = max(x, max_x)
            max_y = max(y, max_y)

            # Orthrogonal is worth 0.2, diagonal is worth 0.1.
            # Not-present tiles would be 8 - the maximum
            tile_count = (
                0.8 * cur_count +
                0.1 * xy_dict.get((x - 128, y - 128), 8) +
                0.1 * xy_dict.get((x - 128, y + 128), 8) +
                0.1 * xy_dict.get((x + 128, y - 128), 8) +
                0.1 * xy_dict.get((x + 128, y + 128), 8) +

                0.2 * xy_dict.get((x - 128, y), 8) +
                0.2 * xy_dict.get((x, y - 128), 8) +
                0.2 * xy_dict.get((x, y + 128), 8) +
                0.2 * xy_dict.get((x + 128, y), 8)
            )
            # The number ranges from 0 (all tiles) to 12.8 (no tiles).
            # All tiles should still have a small chance to generate tiles.
            weights[x, y] = min((tile_count + 0.5) / 8, 1)

        # Share the detail entity among same-height tiles..
        detail_ent = conditions.VMF.create_ent(
            classname='func_detail',
        )

        for x, y in xy_dict:
            convert_floor(
                Vec(x, y, z),
                overlay_ids,
                MATS,
                SETTINGS,
                sign_loc,
                detail_ent,
                noise_weight=weights[x, y],
                noise_func=noise,
            )

    add_floor_sides(floor_edges)

    conditions.reallocate_overlays(overlay_ids)

    return conditions.RES_EXHAUSTED
コード例 #24
0
ファイル: custItems.py プロジェクト: goodDOS/BEE2.4
def res_cust_output(inst, res):
    """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 = resolve_inst('[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: VLib.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'] = resolve_inst(sign_file_id)[0]

                # 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(VLib.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(VLib.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)
コード例 #25
0
ファイル: trackPlat.py プロジェクト: Coolasp1e/BEE2.4
def res_track_plat(_, res):
    """Logic specific to Track Platforms.

    This allows switching the instances used depending on if the track
    is horizontal or vertical and sets the track
    targetnames to a useful value.
    Values:
        - Orig_item: The "<ITEM_ID>" for the track platform, with angle brackets
        - Single_plat: An instance used for platform with 1 rail
        - Track_name: The name to give to the tracks.
        - Vert_suffix: Add suffixes to vertical tracks
            (_vert)
        - Horiz_suffix: Add suffixes to horizontal tracks
            (_horiz, _horiz_mirrored)
        - plat_suffix: Also add the above _vert or _horiz suffixes to
            the platform.
        - plat_var: If set, save the orientation to the given $fixup variable
    """
    # Get the instances from editoritems
    (
        inst_bot_grate, inst_bottom, inst_middle,
        inst_top, inst_plat, inst_plat_oscil, inst_single
    ) = resolve_inst(res['orig_item'])
    single_plat_inst = res['single_plat', '']
    track_targets = res['track_name', '']

    track_files = [inst_bottom, inst_middle, inst_top, inst_single]
    platforms = [inst_plat, inst_plat_oscil]

    # All the track_set in the map, indexed by origin
    track_instances = {
        Vec.from_str(inst['origin']).as_tuple(): inst
        for inst in
        vbsp.VMF.by_class['func_instance']
        if inst['file'].casefold() in track_files
    }

    LOGGER.debug('Track instances:')
    LOGGER.debug('\n'.join(
        '{!s}: {}'.format(k, v['file'])
        for k, v in
        track_instances.items()
    ))

    if not track_instances:
        return RES_EXHAUSTED

    # Now we loop through all platforms in the map, and then locate their
    # track_set
    for plat_inst in vbsp.VMF.by_class['func_instance']:
        if plat_inst['file'].casefold() not in platforms:
            continue  # Not a platform!

        LOGGER.debug('Modifying "' + plat_inst['targetname'] + '"!')

        plat_loc = Vec.from_str(plat_inst['origin'])
        # The direction away from the wall/floor/ceil
        normal = Vec(0, 0, 1).rotate_by_str(
            plat_inst['angles']
        )

        for tr_origin, first_track in track_instances.items():
            if plat_loc == tr_origin:
                # Check direction

                if normal == Vec(0, 0, 1).rotate(
                        *Vec.from_str(first_track['angles'])
                        ):
                    break
        else:
            raise Exception('Platform "{}" has no track!'.format(
                plat_inst['targetname']
            ))

        track_type = first_track['file'].casefold()
        if track_type == inst_single:
            # Track is one block long, use a single-only instance and
            # remove track!
            plat_inst['file'] = single_plat_inst
            first_track.remove()
            continue  # Next platform

        track_set = set()
        if track_type == inst_top or track_type == inst_middle:
            # search left
            track_scan(
                track_set,
                track_instances,
                first_track,
                middle_file=inst_middle,
                x_dir=-1,
            )
        if track_type == inst_bottom or track_type == inst_middle:
            # search right
            track_scan(
                track_set,
                track_instances,
                first_track,
                middle_file=inst_middle,
                x_dir=+1,
            )

        # Give every track a targetname matching the platform
        for ind, track in enumerate(track_set, start=1):
            if track_targets == '':
                track['targetname'] = plat_inst['targetname']
            else:
                track['targetname'] = (
                    plat_inst['targetname'] +
                    '-' +
                    track_targets + str(ind)
                )

        # Now figure out which way the track faces:

        # The direction horizontal track is offset
        side_dir = Vec(0, 1, 0).rotate_by_str(first_track['angles'])

        # The direction of the platform surface
        facing = Vec(-1, 0, 0).rotate_by_str(plat_inst['angles'])
        if side_dir == facing:
            track_facing = 'HORIZ'
        elif side_dir == -facing:
            track_facing = 'HORIZ_MIRR'
        else:
            track_facing = 'VERT'
        # Now add the suffixes
        if track_facing == 'VERT':
            if utils.conv_bool(res['vert_suffix', '']):
                for inst in track_set:
                    conditions.add_suffix(inst, '_vert')
                if utils.conv_bool(res['plat_suffix', '']):
                    conditions.add_suffix(plat_inst, '_vert')
        elif track_facing == 'HORIZ_MIRR':
            if utils.conv_bool(res['horiz_suffix', '']):
                for inst in track_set:
                    conditions.add_suffix(inst, '_horiz_mirrored')
                if utils.conv_bool(res['plat_suffix', '']):
                    conditions.add_suffix(plat_inst, '_horiz')
        else:  # == 'HORIZ'
            if utils.conv_bool(res['horiz_suffix', '']):
                for inst in track_set:
                    conditions.add_suffix(inst, '_horiz')
                if utils.conv_bool(res['plat_suffix', '']):
                    conditions.add_suffix(plat_inst, '_horiz')

        plat_var = res['plat_var', '']
        if plat_var != '':
            # Skip the '_mirrored' section if needed
            plat_inst.fixup[plat_var] = track_facing[:5].lower()

    return RES_EXHAUSTED # Don't re-run
コード例 #26
0
ファイル: instances.py プロジェクト: goodDOS/BEE2.4
def flag_file_equal(inst, flag):
    """Evaluates True if the instance matches the given file."""
    return inst['file'].casefold() in resolve_inst(flag.value)
コード例 #27
0
ファイル: conditions.py プロジェクト: xDadiKx/BEE2.4
def make_static_pist_setup(res):
    return {
        name: resolve_inst(res[name, ""])[0]
        for name in ("bottom_1", "bottom_2", "bottom_3", "static_0", "static_1", "static_2", "static_3", "static_4")
    }
コード例 #28
0
def res_track_plat(res: Property):
    """Logic specific to Track Platforms.

    This allows switching the instances used depending on if the track
    is horizontal or vertical and sets the track
    targetnames to a useful value.
    Values:
        - Orig_item: The "<ITEM_ID>" for the track platform, with angle brackets
        - Single_plat: An instance used for platform with 1 rail
        - Track_name: The name to give to the tracks.
        - Vert_suffix: Add suffixes to vertical tracks
            (_vert)
        - Horiz_suffix: Add suffixes to horizontal tracks
            (_horiz, _horiz_mirrored)
        - plat_suffix: Also add the above _vert or _horiz suffixes to
            the platform.
        - vert_bottom_suffix: Add '_bottom' / '_vert_bottom' to the track at the
            bottom of vertical platforms.
        - plat_var: If set, save the orientation to the given $fixup variable
    """
    # Get the instances from editoritems
    (inst_bot_grate, inst_bottom, inst_middle, inst_top, inst_plat,
     inst_plat_oscil, inst_single) = resolve_inst(res['orig_item'])
    # If invalid, [] = false so ''[0] = ''.
    single_plat_inst = (resolve_inst(res['single_plat', '']) or '')[0]
    track_targets = res['track_name', '']

    track_files = [inst_bottom, inst_middle, inst_top, inst_single]
    platforms = [inst_plat, inst_plat_oscil]

    # All the track_set in the map, indexed by origin
    track_instances = {
        Vec.from_str(inst['origin']).as_tuple(): inst
        for inst in vbsp.VMF.by_class['func_instance']
        if inst['file'].casefold() in track_files
    }

    LOGGER.debug('Track instances:')
    LOGGER.debug('\n'.join('{!s}: {}'.format(k, v['file'])
                           for k, v in track_instances.items()))

    if not track_instances:
        return RES_EXHAUSTED

    # Now we loop through all platforms in the map, and then locate their
    # track_set
    for plat_inst in vbsp.VMF.by_class['func_instance']:
        if plat_inst['file'].casefold() not in platforms:
            continue  # Not a platform!

        LOGGER.debug('Modifying "' + plat_inst['targetname'] + '"!')

        plat_loc = Vec.from_str(plat_inst['origin'])
        # The direction away from the wall/floor/ceil
        normal = Vec(0, 0, 1).rotate_by_str(plat_inst['angles'])

        for tr_origin, first_track in track_instances.items():
            if plat_loc == tr_origin:
                # Check direction

                if normal == Vec(
                        0, 0, 1).rotate(*Vec.from_str(first_track['angles'])):
                    break
        else:
            raise Exception('Platform "{}" has no track!'.format(
                plat_inst['targetname']))

        track_type = first_track['file'].casefold()
        if track_type == inst_single:
            # Track is one block long, use a single-only instance and
            # remove track!
            plat_inst['file'] = single_plat_inst
            first_track.remove()
            continue  # Next platform

        track_set = set()
        if track_type == inst_top or track_type == inst_middle:
            # search left
            track_scan(
                track_set,
                track_instances,
                first_track,
                middle_file=inst_middle,
                x_dir=-1,
            )
        if track_type == inst_bottom or track_type == inst_middle:
            # search right
            track_scan(
                track_set,
                track_instances,
                first_track,
                middle_file=inst_middle,
                x_dir=+1,
            )

        # Give every track a targetname matching the platform
        for ind, track in enumerate(track_set, start=1):
            if track_targets == '':
                track['targetname'] = plat_inst['targetname']
            else:
                track['targetname'] = (plat_inst['targetname'] + '-' +
                                       track_targets + str(ind))

        # Now figure out which way the track faces:

        # The direction horizontal track is offset
        side_dir = Vec(0, 1, 0).rotate_by_str(first_track['angles'])

        # The direction of the platform surface
        facing = Vec(-1, 0, 0).rotate_by_str(plat_inst['angles'])
        if side_dir == facing:
            track_facing = 'HORIZ'
        elif side_dir == -facing:
            track_facing = 'HORIZ_MIRR'
        else:
            track_facing = 'VERT'
        # Now add the suffixes
        if track_facing == 'VERT':
            if srctools.conv_bool(res['vert_suffix', '']):
                for inst in track_set:
                    conditions.add_suffix(inst, '_vert')
                if srctools.conv_bool(res['plat_suffix', '']):
                    conditions.add_suffix(plat_inst, '_vert')
            if srctools.conv_bool(res['vert_bottom_suffix', '']):
                # We want to find the bottom/top track which is facing the
                # same direction as the platform.
                track_dirs = {
                    inst_top: Vec(-1, 0, 0),
                    inst_bottom: Vec(1, 0, 0)
                }
                for inst in track_set:
                    try:
                        norm_off = track_dirs[inst['file'].casefold()]
                    except KeyError:
                        continue

                    if norm_off.rotate_by_str(inst['angles']) == facing:
                        conditions.add_suffix(inst, '_bottom')

        elif track_facing == 'HORIZ_MIRR':
            if srctools.conv_bool(res['horiz_suffix', '']):
                for inst in track_set:
                    conditions.add_suffix(inst, '_horiz_mirrored')
                if srctools.conv_bool(res['plat_suffix', '']):
                    conditions.add_suffix(plat_inst, '_horiz')
        else:  # == 'HORIZ'
            if srctools.conv_bool(res['horiz_suffix', '']):
                for inst in track_set:
                    conditions.add_suffix(inst, '_horiz')
                if srctools.conv_bool(res['plat_suffix', '']):
                    conditions.add_suffix(plat_inst, '_horiz')

        plat_var = res['plat_var', '']
        if plat_var != '':
            # Skip the '_mirrored' section if needed
            plat_inst.fixup[plat_var] = track_facing[:5].lower()

    return RES_EXHAUSTED  # Don't re-run
コード例 #29
0
ファイル: conditions.py プロジェクト: xDadiKx/BEE2.4
def flag_file_equal(inst, flag):
    return inst["file"].casefold() in resolve_inst(flag.value)
コード例 #30
0
def res_make_catwalk(res: Property):
    """Speciallised result to generate catwalks from markers.

    Only runs once, and then quits the condition list.
    Instances:
        MarkerInst: The instance set in editoritems.
        Straight_128/256/512: Straight sections. Extends East
        Corner: A corner piece. Connects on N and W sides.
        TJunction; A T-piece. Connects on all but the East side.
        CrossJunction: A X-piece. Connects on all sides.
        End: An end piece. Connects on the East side.
        Stair: A stair. Starts East and goes Up and West.
        End_wall: Connects a West wall to a East catwalk.
        Support_Wall: A support extending from the East wall.
        Support_Ceil: A support extending from the ceiling.
        Support_Floor: A support extending from the floor.
        Support_Goo: A floor support, designed for goo pits.
        Single_Wall: A section connecting to an East wall.
    """
    LOGGER.info("Starting catwalk generator...")
    marker = resolve_inst(res['markerInst'])
    output_target = res['output_name', 'MARKER']

    instances = {
        name: resolve_inst(res[name, ''])[0]
        for name in (
            'straight_128',
            'straight_256',
            'straight_512',
            'corner',
            'tjunction',
            'crossjunction',
            'end',
            'stair',
            'end_wall',
            'support_wall',
            'support_ceil',
            'support_floor',
            'support_goo',
            'single_wall',
            'markerInst',
        )
    }
    # If there are no attachments remove a catwalk piece
    instances['NONE'] = ''
    if instances['end_wall'] == '':
        instances['end_wall'] = instances['end']

    connections = {}  # The directions this instance is connected by (NSEW)
    markers = {}

    # Find all our markers, so we can look them up by targetname.
    for inst in vbsp.VMF.by_class['func_instance']:
        if inst['file'].casefold() not in marker:
            continue
        #                   [North, South, East,  West ]
        connections[inst] = [False, False, False, False]
        markers[inst['targetname']] = inst

        # Snap the markers to the grid. If on glass it can become offset...
        origin = Vec.from_str(inst['origin'])
        origin = origin // 128 * 128  # type: Vec
        origin += 64

        while origin.as_tuple() in conditions.GOO_LOCS:
            # The instance is in goo! Switch to floor orientation, and move
            # up until it's in air.
            inst['angles'] = '0 0 0'
            origin.z += 128

        inst['origin'] = str(origin)

    if not markers:
        return RES_EXHAUSTED

    LOGGER.info('Connections: {}', connections)
    LOGGER.info('Markers: {}', markers)

    # First loop through all the markers, adding connecting sections
    for inst in markers.values():
        for conn in inst.outputs:
            if conn.output != output_target or conn.input != output_target:
                # Indicator toggles or similar, delete these entities.
                # Find the associated overlays too.
                for del_inst in vbsp.VMF.by_target[conn.target]:
                    conditions.remove_ant_toggle(del_inst)
                continue

            inst2 = markers[conn.target]
            LOGGER.debug('{} <-> {}', inst['targetname'], inst2['targetname'])
            origin1 = Vec.from_str(inst['origin'])
            origin2 = Vec.from_str(inst2['origin'])
            if origin1.x != origin2.x and origin1.y != origin2.y:
                LOGGER.warning('Instances not aligned!')
                continue

            y_dir = origin1.x == origin2.x  # Which way the connection is
            if y_dir:
                dist = abs(origin1.y - origin2.y)
            else:
                dist = abs(origin1.x - origin2.x)
            vert_dist = origin1.z - origin2.z

            LOGGER.debug('Dist = {}, Vert = {}', dist, vert_dist)

            if (dist - 128) // 2 < abs(vert_dist):
                # The stairs are 2 long, 1 high. Check there's enough room
                # Subtract the last block though, since that's a corner.
                LOGGER.warning('Not enough room for stairs!')
                continue

            if dist > 128:
                # add straight sections in between
                place_catwalk_connections(instances, origin1, origin2)

            # Update the lists based on the directions that were set
            conn_lst1 = connections[inst]
            conn_lst2 = connections[inst2]
            if origin1.x < origin2.x:
                conn_lst1[2] = True  # E
                conn_lst2[3] = True  # W
            elif origin2.x < origin1.x:
                conn_lst1[3] = True  # W
                conn_lst2[2] = True  # E

            if origin1.y < origin2.y:
                conn_lst1[0] = True  # N
                conn_lst2[1] = True  # S
            elif origin2.y < origin1.y:
                conn_lst1[1] = True  # S
                conn_lst2[0] = True  # N

        inst.outputs.clear()  # Remove the outputs now, they're useless

    for inst, dir_mask in connections.items():
        # Set the marker instances based on the attached walkways.
        normal = Vec(0, 0, 1).rotate_by_str(inst['angles'])

        new_type, inst['angles'] = utils.CONN_LOOKUP[tuple(dir_mask)]
        inst['file'] = instances[CATWALK_TYPES[new_type]]

        if new_type is utils.CONN_TYPES.side:
            # If the end piece is pointing at a wall, switch the instance.
            if normal.z == 0:
                # Treat booleans as ints to get the direction the connection is
                # in - True == 1, False == 0
                conn_dir = Vec(
                    x=dir_mask[2] - dir_mask[3],  # +E, -W
                    y=dir_mask[0] - dir_mask[1],  # +N, -S,
                    z=0,
                )
                if normal == conn_dir:
                    inst['file'] = instances['end_wall']
            continue  # We never have normal supports on end pieces
        elif new_type is utils.CONN_TYPES.none:
            # Unconnected catwalks on the wall switch to a special instance.
            # This lets players stand next to a portal surface on the wall.
            if normal.z == 0:
                inst['file'] = instances['single_wall']
                inst['angles'] = INST_ANGLE[normal.as_tuple()]
            else:
                inst.remove()
            continue  # These don't get supports otherwise

        # Add regular supports
        if normal == (0, 0, 1):
            # If in goo, use different supports!
            origin = Vec.from_str(inst['origin'])
            origin.z -= 128
            if origin.as_tuple() in conditions.GOO_LOCS:
                supp = instances['support_goo']
            else:
                supp = instances['support_floor']
        elif normal == (0, 0, -1):
            supp = instances['support_ceil']
        else:
            supp = instances['support_wall']

        if supp:
            vbsp.VMF.create_ent(
                classname='func_instance',
                origin=inst['origin'],
                angles=INST_ANGLE[normal.as_tuple()],
                file=supp,
            )

    LOGGER.info('Finished catwalk generation!')
    return RES_EXHAUSTED
コード例 #31
0
def res_add_overlay_inst(inst: Entity, res: Property):
    """Add another instance on top of this one.

    Values:
        File: The filename.
        Fixup Style: The Fixup style for the instance. '0' (default) is
            Prefix, '1' is Suffix, and '2' is None.
        Copy_Fixup: If true, all the $replace values from the original
            instance will be copied over.
        move_outputs: If true, outputs will be moved to this instance.
        offset: The offset (relative to the base) that the instance
            will be placed. Can be set to '<piston_top>' and
            '<piston_bottom>' to offset based on the configuration.
            '<piston_start>' will set it to the starting position, and
            '<piston_end>' will set it to the ending position.
            of piston platform handles.
        angles: If set, overrides the base instance angles. This does
            not affect the offset property.
        fixup/localfixup: Keyvalues in this block will be copied to the
            overlay entity.
            If the value starts with $, the variable will be copied over.
            If this is present, copy_fixup will be disabled.
    """

    angle = res['angles', inst['angles', '0 0 0']]
    overlay_inst = vbsp.VMF.create_ent(
        classname='func_instance',
        targetname=inst['targetname', ''],
        file=resolve_inst(res['file', ''])[0],
        angles=angle,
        origin=inst['origin'],
        fixup_style=res['fixup_style', '0'],
    )
    # Don't run if the fixup block exists..
    if srctools.conv_bool(res['copy_fixup', '1']):
        if 'fixup' not in res and 'localfixup' not in res:
            # Copy the fixup values across from the original instance
            for fixup, value in inst.fixup.items():
                overlay_inst.fixup[fixup] = value

    conditions.set_ent_keys(overlay_inst.fixup, inst, res, 'fixup')

    if res.bool('move_outputs', False):
        overlay_inst.outputs = inst.outputs
        inst.outputs = []

    if 'offset' in res:
        folded_off = res['offset'].casefold()
        # Offset the overlay by the given distance
        # Some special placeholder values:
        if folded_off == '<piston_start>':
            if srctools.conv_bool(inst.fixup['$start_up', '']):
                folded_off = '<piston_top>'
            else:
                folded_off = '<piston_bottom>'
        elif folded_off == '<piston_end>':
            if srctools.conv_bool(inst.fixup['$start_up', '']):
                folded_off = '<piston_bottom>'
            else:
                folded_off = '<piston_top>'

        if folded_off == '<piston_bottom>':
            offset = Vec(
                z=srctools.conv_int(inst.fixup['$bottom_level']) * 128,
            )
        elif folded_off == '<piston_top>':
            offset = Vec(
                z=srctools.conv_int(inst.fixup['$top_level'], 1) * 128,
            )
        else:
            # Regular vector
            offset = Vec.from_str(conditions.resolve_value(inst, res['offset']))

        offset.rotate_by_str(
            inst['angles', '0 0 0']
        )
        overlay_inst['origin'] = (
            offset + Vec.from_str(inst['origin'])
        ).join(' ')
    return overlay_inst
コード例 #32
0
def res_cutout_tile(res: Property):
    """Generate random quarter tiles, like in Destroyed or Retro maps.

    - "MarkerItem" is the instance to look for.
    - "TileSize" can be "2x2" or "4x4".
    - rotateMax is the amount of degrees to rotate squarebeam models.

    Materials:
    - "squarebeams" is the squarebeams variant to use.
    - "ceilingwalls" are the sides of the ceiling section.
    - "floorbase" is the texture under floor sections.
    - "tile_glue" is used on top of a thinner tile segment.
    - "clip" is the player_clip texture used over floor segments.
        (This allows customising the surfaceprop.)
    - "Floor4x4Black", "Ceil2x2White" and other combinations can be used to
       override the textures used.
    """
    item = resolve_inst(res['markeritem'])

    INST_LOCS = {}  # Map targetnames -> surface loc
    CEIL_IO = []  # Pairs of ceil inst corners to cut out.
    FLOOR_IO = []  # Pairs of floor inst corners to cut out.

    overlay_ids = {}  # When we replace brushes, we need to fix any overlays
    # on that surface.

    MATS.clear()
    floor_edges = []  # Values to pass to add_floor_sides() at the end

    sign_loc = set(FORCE_LOCATIONS)
    # If any signage is present in the map, we need to force tiles to
    # appear at that location!
    for over in conditions.VMF.by_class['info_overlay']:
        if (over['material'].casefold() in FORCE_TILE_MATS and
                # Only check floor/ceiling overlays
                over['basisnormal'] in ('0 0 1', '0 0 -1')):
            loc = Vec.from_str(over['origin'])
            # Sometimes (light bridges etc) a sign will be halfway between
            # tiles, so in that case we need to force 2 tiles.
            loc_min = (loc - (15, 15, 0)) // 32 * 32  # type: Vec
            loc_max = (loc + (15, 15, 0)) // 32 * 32  # type: Vec
            loc_min += (16, 16, 0)
            loc_max += (16, 16, 0)
            FORCE_LOCATIONS.add(loc_min.as_tuple())
            FORCE_LOCATIONS.add(loc_max.as_tuple())

    SETTINGS = {
        'floor_chance':
        srctools.conv_int(res['floorChance', '100'], 100),
        'ceil_chance':
        srctools.conv_int(res['ceilingChance', '100'], 100),
        'floor_glue_chance':
        srctools.conv_int(res['floorGlueChance', '0']),
        'ceil_glue_chance':
        srctools.conv_int(res['ceilingGlueChance', '0']),
        'rotate_beams':
        int(srctools.conv_float(res['rotateMax', '0']) * BEAM_ROT_PRECISION),
        'beam_skin':
        res['squarebeamsSkin', '0'],
        'base_is_disp':
        srctools.conv_bool(res['dispBase', '0']),
        'quad_floor':
        res['FloorSize', '4x4'].casefold() == '2x2',
        'quad_ceil':
        res['CeilingSize', '4x4'].casefold() == '2x2',
    }

    random.seed(vbsp.MAP_RAND_SEED + '_CUTOUT_TILE_NOISE')
    noise = SimplexNoise(period=4 * 40)  # 4 tiles/block, 50 blocks max

    # We want to know the number of neighbouring tile cutouts before
    # placing tiles - blocks away from the sides generate fewer tiles.
    floor_neighbours = defaultdict(dict)  # all_floors[z][x,y] = count

    for mat_prop in res['Materials', []]:
        MATS[mat_prop.name].append(mat_prop.value)

    if SETTINGS['base_is_disp']:
        # We want the normal brushes to become nodraw.
        MATS['floorbase_disp'] = MATS['floorbase']
        MATS['floorbase'] = ['tools/toolsnodraw']

        # Since this uses random data for initialisation, the alpha and
        # regular will use slightly different patterns.
        alpha_noise = SimplexNoise(period=4 * 50)
    else:
        alpha_noise = None

    for key, default in TEX_DEFAULT:
        if key not in MATS:
            MATS[key] = [default]

    # Find our marker ents
    for inst in conditions.VMF.by_class['func_instance']:  # type: VLib.Entity
        if inst['file'].casefold() not in item:
            continue
        targ = inst['targetname']
        orient = Vec(0, 0, 1).rotate_by_str(inst['angles', '0 0 0'])
        # Check the orientation of the marker to figure out what to generate
        if orient == (0, 0, 1):
            io_list = FLOOR_IO
        else:
            io_list = CEIL_IO

        # Reuse orient to calculate where the solid face will be.
        loc = (orient * -64) + Vec.from_str(inst['origin'])
        INST_LOCS[targ] = loc

        for out in inst.output_targets():
            io_list.append((targ, out))

        if not inst.outputs and inst.fixup['$connectioncount'] == '0':
            # If the item doesn't have any connections, 'connect'
            # it to itself so we'll generate a 128x128 tile segment.
            io_list.append((targ, targ))
        inst.remove()  # Remove the instance itself from the map.

    for start_floor, end_floor in FLOOR_IO:
        if end_floor not in INST_LOCS:
            # Not a marker - remove this and the antline.
            for toggle in conditions.VMF.by_target[end_floor]:
                conditions.remove_ant_toggle(toggle)
            continue

        box_min = Vec(INST_LOCS[start_floor])
        box_min.min(INST_LOCS[end_floor])

        box_max = Vec(INST_LOCS[start_floor])
        box_max.max(INST_LOCS[end_floor])

        if box_min.z != box_max.z:
            continue  # They're not in the same level!
        z = box_min.z

        if SETTINGS['rotate_beams']:
            # We have to generate 1 model per 64x64 block to do rotation...
            gen_rotated_squarebeams(
                box_min - (64, 64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin'],
                max_rot=SETTINGS['rotate_beams'],
            )
        else:
            # Make the squarebeams props, using big models if possible
            gen_squarebeams(box_min + (-64, -64, 0),
                            box_max + (64, 64, -8),
                            skin=SETTINGS['beam_skin'])

        # Add a player_clip brush across the whole area
        conditions.VMF.add_brush(
            conditions.VMF.make_prism(
                p1=box_min - (64, 64, FLOOR_DEPTH),
                p2=box_max + (64, 64, 0),
                mat=MATS['clip'][0],
            ).solid)

        # Add a noportal_volume covering the surface, in case there's
        # room for a portal.
        noportal_solid = conditions.VMF.make_prism(
            # Don't go all the way to the sides, so it doesn't affect wall
            # brushes.
            p1=box_min - (63, 63, 9),
            p2=box_max + (63, 63, 0),
            mat='tools/toolsinvisible',
        ).solid
        noportal_ent = conditions.VMF.create_ent(
            classname='func_noportal_volume',
            origin=box_min.join(' '),
        )
        noportal_ent.solids.append(noportal_solid)

        if SETTINGS['base_is_disp']:
            # Use displacements for the base instead.
            make_alpha_base(
                box_min + (-64, -64, 0),
                box_max + (64, 64, 0),
                noise=alpha_noise,
            )

        for x, y in utils.iter_grid(
                min_x=int(box_min.x),
                max_x=int(box_max.x) + 1,
                min_y=int(box_min.y),
                max_y=int(box_max.y) + 1,
                stride=128,
        ):
            # Build the set of all positions..
            floor_neighbours[z][x, y] = -1

        # Mark borders we need to fill in, and the angle (for func_instance)
        # The wall is the face pointing inwards towards the bottom brush,
        # and the ceil is the ceiling of the block above the bordering grid
        # points.
        for x in range(int(box_min.x), int(box_max.x) + 1, 128):
            # North
            floor_edges.append(
                BorderPoints(
                    wall=Vec(x, box_max.y + 64, z - 64),
                    ceil=Vec_tuple(x, box_max.y + 128, z),
                    rot=270,
                ))
            # South
            floor_edges.append(
                BorderPoints(
                    wall=Vec(x, box_min.y - 64, z - 64),
                    ceil=Vec_tuple(x, box_min.y - 128, z),
                    rot=90,
                ))

        for y in range(int(box_min.y), int(box_max.y) + 1, 128):
            # East
            floor_edges.append(
                BorderPoints(
                    wall=Vec(box_max.x + 64, y, z - 64),
                    ceil=Vec_tuple(box_max.x + 128, y, z),
                    rot=180,
                ))

            # West
            floor_edges.append(
                BorderPoints(
                    wall=Vec(box_min.x - 64, y, z - 64),
                    ceil=Vec_tuple(box_min.x - 128, y, z),
                    rot=0,
                ))

    # Now count boundries near tiles, then generate them.

    # Do it seperately for each z-level:
    for z, xy_dict in floor_neighbours.items():  # type: float, dict
        for x, y in xy_dict:  # type: float, float
            # We want to count where there aren't any tiles
            xy_dict[x, y] = (((x - 128, y - 128) not in xy_dict) +
                             ((x - 128, y + 128) not in xy_dict) +
                             ((x + 128, y - 128) not in xy_dict) +
                             ((x + 128, y + 128) not in xy_dict) +
                             ((x - 128, y) not in xy_dict) +
                             ((x + 128, y) not in xy_dict) +
                             ((x, y - 128) not in xy_dict) +
                             ((x, y + 128) not in xy_dict))

        max_x = max_y = 0

        weights = {}
        # Now the counts are all correct, compute the weight to apply
        # for tiles.
        # Adding the neighbouring counts will make a 5x5 area needed to set
        # the center to 0.

        for (x, y), cur_count in xy_dict.items():
            max_x = max(x, max_x)
            max_y = max(y, max_y)

            # Orthrogonal is worth 0.2, diagonal is worth 0.1.
            # Not-present tiles would be 8 - the maximum
            tile_count = (0.8 * cur_count + 0.1 * xy_dict.get(
                (x - 128, y - 128), 8) + 0.1 * xy_dict.get(
                    (x - 128, y + 128), 8) + 0.1 * xy_dict.get(
                        (x + 128, y - 128), 8) + 0.1 * xy_dict.get(
                            (x + 128, y + 128), 8) + 0.2 * xy_dict.get(
                                (x - 128, y), 8) + 0.2 * xy_dict.get(
                                    (x, y - 128), 8) + 0.2 * xy_dict.get(
                                        (x, y + 128), 8) + 0.2 * xy_dict.get(
                                            (x + 128, y), 8))
            # The number ranges from 0 (all tiles) to 12.8 (no tiles).
            # All tiles should still have a small chance to generate tiles.
            weights[x, y] = min((tile_count + 0.5) / 8, 1)

        # Share the detail entity among same-height tiles..
        detail_ent = conditions.VMF.create_ent(classname='func_detail', )

        for x, y in xy_dict:
            convert_floor(
                Vec(x, y, z),
                overlay_ids,
                MATS,
                SETTINGS,
                sign_loc,
                detail_ent,
                noise_weight=weights[x, y],
                noise_func=noise,
            )

    add_floor_sides(floor_edges)

    conditions.reallocate_overlays(overlay_ids)

    return conditions.RES_EXHAUSTED
コード例 #33
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 = resolve_inst('[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'] = resolve_inst(sign_file_id)[0]

                # 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)
コード例 #34
0
def flag_has_inst(flag: Property):
    """Checks if the given instance is present anywhere in the map."""
    flags = resolve_inst(flag.value)
    return any(inst.casefold() in flags for inst in ALL_INST)
コード例 #35
0
def res_make_catwalk(res: Property):
    """Speciallised result to generate catwalks from markers.

    Only runs once, and then quits the condition list.
    Instances:
        MarkerInst: The instance set in editoritems.
        Straight_128/256/512: Straight sections. Extends East
        Corner: A corner piece. Connects on N and W sides.
        TJunction; A T-piece. Connects on all but the East side.
        CrossJunction: A X-piece. Connects on all sides.
        End: An end piece. Connects on the East side.
        Stair: A stair. Starts East and goes Up and West.
        End_wall: Connects a West wall to a East catwalk.
        Support_Wall: A support extending from the East wall.
        Support_Ceil: A support extending from the ceiling.
        Support_Floor: A support extending from the floor.
        Support_Goo: A floor support, designed for goo pits.
        Single_Wall: A section connecting to an East wall.
    """
    LOGGER.info("Starting catwalk generator...")
    marker = resolve_inst(res['markerInst'])
    output_target = res['output_name', 'MARKER']

    instances = {
        name: resolve_inst(res[name, ''])[0]
        for name in
        (
            'straight_128', 'straight_256', 'straight_512',
            'corner', 'tjunction', 'crossjunction', 'end', 'stair', 'end_wall',
            'support_wall', 'support_ceil', 'support_floor', 'support_goo',
            'single_wall',
            'markerInst',
        )
    }
    # If there are no attachments remove a catwalk piece
    instances['NONE'] = ''
    if instances['end_wall'] == '':
        instances['end_wall'] = instances['end']

    connections = {}  # The directions this instance is connected by (NSEW)
    markers = {}

    # Find all our markers, so we can look them up by targetname.
    for inst in vbsp.VMF.by_class['func_instance']:
        if inst['file'].casefold() not in marker:
            continue
        #                   [North, South, East,  West ]
        connections[inst] = [False, False, False, False]
        markers[inst['targetname']] = inst

        # Snap the markers to the grid. If on glass it can become offset...
        origin = Vec.from_str(inst['origin'])
        origin = origin // 128 * 128  # type: Vec
        origin += 64

        while origin.as_tuple() in conditions.GOO_LOCS:
            # The instance is in goo! Switch to floor orientation, and move
            # up until it's in air.
            inst['angles'] = '0 0 0'
            origin.z += 128

        inst['origin'] = str(origin)

    if not markers:
        return RES_EXHAUSTED

    LOGGER.info('Connections: {}', connections)
    LOGGER.info('Markers: {}', markers)

    # First loop through all the markers, adding connecting sections
    for inst in markers.values():
        for conn in inst.outputs:
            if conn.output != output_target or conn.input != output_target:
                # Indicator toggles or similar, delete these entities.
                # Find the associated overlays too.
                for del_inst in vbsp.VMF.by_target[conn.target]:
                    conditions.remove_ant_toggle(del_inst)
                continue

            inst2 = markers[conn.target]
            LOGGER.debug('{} <-> {}', inst['targetname'], inst2['targetname'])
            origin1 = Vec.from_str(inst['origin'])
            origin2 = Vec.from_str(inst2['origin'])
            if origin1.x != origin2.x and origin1.y != origin2.y:
                LOGGER.warning('Instances not aligned!')
                continue

            y_dir = origin1.x == origin2.x  # Which way the connection is
            if y_dir:
                dist = abs(origin1.y - origin2.y)
            else:
                dist = abs(origin1.x - origin2.x)
            vert_dist = origin1.z - origin2.z

            LOGGER.debug('Dist = {}, Vert = {}', dist, vert_dist)

            if (dist - 128) // 2 < abs(vert_dist):
                # The stairs are 2 long, 1 high. Check there's enough room
                # Subtract the last block though, since that's a corner.
                LOGGER.warning('Not enough room for stairs!')
                continue

            if dist > 128:
                # add straight sections in between
                place_catwalk_connections(instances, origin1, origin2)

            # Update the lists based on the directions that were set
            conn_lst1 = connections[inst]
            conn_lst2 = connections[inst2]
            if origin1.x < origin2.x:
                conn_lst1[2] = True  # E
                conn_lst2[3] = True  # W
            elif origin2.x < origin1.x:
                conn_lst1[3] = True  # W
                conn_lst2[2] = True  # E

            if origin1.y < origin2.y:
                conn_lst1[0] = True  # N
                conn_lst2[1] = True  # S
            elif origin2.y < origin1.y:
                conn_lst1[1] = True  # S
                conn_lst2[0] = True  # N

        inst.outputs.clear()  # Remove the outputs now, they're useless

    for inst, dir_mask in connections.items():
        # Set the marker instances based on the attached walkways.
        normal = Vec(0, 0, 1).rotate_by_str(inst['angles'])

        new_type, inst['angles'] = utils.CONN_LOOKUP[tuple(dir_mask)]
        inst['file'] = instances[CATWALK_TYPES[new_type]]

        if new_type is utils.CONN_TYPES.side:
            # If the end piece is pointing at a wall, switch the instance.
            if normal.z == 0:
                # Treat booleans as ints to get the direction the connection is
                # in - True == 1, False == 0
                conn_dir = Vec(
                    x=dir_mask[2] - dir_mask[3],  # +E, -W
                    y=dir_mask[0] - dir_mask[1],  # +N, -S,
                    z=0,
                )
                if normal == conn_dir:
                    inst['file'] = instances['end_wall']
            continue  # We never have normal supports on end pieces
        elif new_type is utils.CONN_TYPES.none:
            # Unconnected catwalks on the wall switch to a special instance.
            # This lets players stand next to a portal surface on the wall.
            if normal.z == 0:
                inst['file'] = instances['single_wall']
                inst['angles'] = INST_ANGLE[normal.as_tuple()]
            else:
                inst.remove()
            continue  # These don't get supports otherwise

        # Add regular supports
        if normal == (0, 0, 1):
            # If in goo, use different supports!
            origin = Vec.from_str(inst['origin'])
            origin.z -= 128
            if origin.as_tuple() in conditions.GOO_LOCS:
                supp = instances['support_goo']
            else:
                supp = instances['support_floor']
        elif normal == (0, 0, -1):
            supp = instances['support_ceil']
        else:
            supp = instances['support_wall']

        if supp:
            vbsp.VMF.create_ent(
                classname='func_instance',
                origin=inst['origin'],
                angles=INST_ANGLE[normal.as_tuple()],
                file=supp,
            )

    LOGGER.info('Finished catwalk generation!')
    return RES_EXHAUSTED
コード例 #36
0
def res_make_tag_fizzler(vmf: VMF, inst: Entity, res: Property):
    """Add an Aperture Tag Paint Gun activation fizzler.

    These fizzlers are created via signs, and work very specially.
    MUST be priority -100 so it runs before fizzlers!
    """
    import vbsp
    if vbsp_options.get(str, 'game_id') != utils.STEAM_IDS['TAG']:
        # Abort - TAG fizzlers shouldn't appear in any other game!
        inst.remove()
        return

    fizz_base = fizz_name = None

    # Look for the fizzler instance we want to replace
    for targetname in inst.output_targets():
        if targetname in tag_fizzlers:
            fizz_name = targetname
            fizz_base = tag_fizzlers[targetname]
            del tag_fizzlers[targetname]  # Don't let other signs mod this one!
            continue
        else:
            # It's an indicator toggle, remove it and the antline to clean up.
            LOGGER.warning('Toggle: {}', targetname)
            for ent in vmf.by_target[targetname]:
                remove_ant_toggle(ent)
    inst.outputs.clear()  # Remove the outptuts now, they're not valid anyway.

    if fizz_base is None:
        # No fizzler - remove this sign
        inst.remove()
        return

    # The distance from origin the double signs are seperated by.
    sign_offset = res.int('signoffset', 16)

    sign_loc = (
        # The actual location of the sign - on the wall
        Vec.from_str(inst['origin']) +
        Vec(0, 0, -64).rotate_by_str(inst['angles']))

    # Now deal with the visual aspect:
    # Blue signs should be on top.

    blue_enabled = inst.fixup.bool('$start_enabled')
    oran_enabled = inst.fixup.bool('$start_reversed')
    # If True, single-color signs will also turn off the other color.
    # This also means we always show both signs.
    # If both are enabled or disabled, this has no effect.
    disable_other = (not inst.fixup.bool('$disable_autorespawn', True)
                     and blue_enabled != oran_enabled)
    # Delete fixups now, they aren't useful.
    inst.fixup.clear()

    if not blue_enabled and not oran_enabled:
        # Hide the sign in this case!
        inst.remove()

    inst_angle = srctools.parse_vec_str(inst['angles'])

    inst_normal = Vec(0, 0, 1).rotate(*inst_angle)
    loc = Vec.from_str(inst['origin'])

    if disable_other or (blue_enabled and oran_enabled):
        inst['file'] = res['frame_double']
        # On a wall, and pointing vertically
        if inst_normal.z == 0 and Vec(y=1).rotate(*inst_angle).z:
            # They're vertical, make sure blue's on top!
            blue_loc = Vec(loc.x, loc.y, loc.z + sign_offset)
            oran_loc = Vec(loc.x, loc.y, loc.z - sign_offset)
            # If orange is enabled, with two frames put that on top
            # instead since it's more important
            if disable_other and oran_enabled:
                blue_loc, oran_loc = oran_loc, blue_loc

        else:
            offset = Vec(0, sign_offset, 0).rotate(*inst_angle)
            blue_loc = loc + offset
            oran_loc = loc - offset
    else:
        inst['file'] = res['frame_single']
        # They're always centered
        blue_loc = loc
        oran_loc = loc

    if inst_normal.z != 0:
        # If on floors/ceilings, rotate to point at the fizzler!
        sign_floor_loc = sign_loc.copy()
        sign_floor_loc.z = 0  # We don't care about z-positions.

        # Grab the data saved earlier in res_find_potential_tag_fizzlers()
        axis, side_min, side_max, normal = tag_fizzler_locs[fizz_name]

        # The Z-axis fizzler (horizontal) must be treated differently.
        if axis == 'z':
            # For z-axis, just compare to the center point.
            # The values are really x, y, z, not what they're named.
            sign_dir = sign_floor_loc - (side_min, side_max, normal)
        else:
            # For the other two, we compare to the line,
            # or compare to the closest side (in line with the fizz)
            other_axis = 'x' if axis == 'y' else 'y'
            if abs(sign_floor_loc[other_axis] - normal) < 32:
                # Compare to the closest side. Use ** to swap x/y arguments
                # appropriately. The closest side is the one with the
                # smallest magnitude.
                vmf.create_ent(
                    classname='info_null',
                    targetname=inst['targetname'] + '_min',
                    origin=sign_floor_loc - Vec(**{
                        axis: side_min,
                        other_axis: normal,
                    }),
                )
                vmf.create_ent(
                    classname='info_null',
                    targetname=inst['targetname'] + '_max',
                    origin=sign_floor_loc - Vec(**{
                        axis: side_max,
                        other_axis: normal,
                    }),
                )
                sign_dir = min(
                    sign_floor_loc - Vec(**{
                        axis: side_min,
                        other_axis: normal,
                    }),
                    sign_floor_loc - Vec(**{
                        axis: side_max,
                        other_axis: normal,
                    }),
                    key=Vec.mag,
                )
            else:
                # Align just based on whether we're in front or behind.
                sign_dir = Vec()
                sign_dir[other_axis] = sign_floor_loc[other_axis] - normal

        sign_angle = math.degrees(math.atan2(sign_dir.y, sign_dir.x))
        # Round to nearest 90 degrees
        # Add 45 so the switchover point is at the diagonals
        sign_angle = (sign_angle + 45) // 90 * 90

        # Rotate to fit the instances - south is down
        sign_angle = int(sign_angle + 90) % 360
        if inst_normal.z > 0:
            sign_angle = '0 {} 0'.format(sign_angle)
        elif inst_normal.z < 0:
            # Flip upside-down for ceilings
            sign_angle = '0 {} 180'.format(sign_angle)
    else:
        # On a wall, face upright
        sign_angle = PETI_INST_ANGLE[inst_normal.as_tuple()]

    # If disable_other, we show off signs. Otherwise we don't use that sign.
    blue_sign = 'blue_sign' if blue_enabled else 'blue_off_sign' if disable_other else None
    oran_sign = 'oran_sign' if oran_enabled else 'oran_off_sign' if disable_other else None

    if blue_sign:
        vmf.create_ent(
            classname='func_instance',
            file=res[blue_sign, ''],
            targetname=inst['targetname'],
            angles=sign_angle,
            origin=blue_loc.join(' '),
        )

    if oran_sign:
        vmf.create_ent(
            classname='func_instance',
            file=res[oran_sign, ''],
            targetname=inst['targetname'],
            angles=sign_angle,
            origin=oran_loc.join(' '),
        )

    # Now modify the fizzler...

    fizz_brushes = list(vmf.by_class['trigger_portal_cleanser']
                        & vmf.by_target[fizz_name + '_brush'])

    if 'base_inst' in res:
        fizz_base['file'] = resolve_inst(res['base_inst'])[0]
    fizz_base.outputs.clear()  # Remove outputs, otherwise they break
    # branch_toggle entities

    # Subtract the sign from the list of connections, but don't go below
    # zero
    fizz_base.fixup['$connectioncount'] = str(
        max(0,
            srctools.conv_int(fizz_base.fixup['$connectioncount', ''], 0) - 1))

    if 'model_inst' in res:
        model_inst = resolve_inst(res['model_inst'])[0]
        for mdl_inst in vmf.by_class['func_instance']:
            if mdl_inst['targetname', ''].startswith(fizz_name + '_model'):
                mdl_inst['file'] = model_inst

    # Find the direction the fizzler front/back points - z=floor fizz
    # Signs will associate with the given side!
    bbox_min, bbox_max = fizz_brushes[0].get_bbox()
    for axis, val in zip('xyz', bbox_max - bbox_min):
        if val == 2:
            fizz_axis = axis
            sign_center = (bbox_min[axis] + bbox_max[axis]) / 2
            break
    else:
        # A fizzler that's not 128*x*2?
        raise Exception('Invalid fizzler brush ({})!'.format(fizz_name))

    # Figure out what the sides will set values to...
    pos_blue = False
    pos_oran = False
    neg_blue = False
    neg_oran = False
    if sign_loc[fizz_axis] < sign_center:
        pos_blue = blue_enabled
        pos_oran = oran_enabled
    else:
        neg_blue = blue_enabled
        neg_oran = oran_enabled

    fizz_off_tex = {
        'left': res['off_left'],
        'center': res['off_center'],
        'right': res['off_right'],
        'short': res['off_short'],
    }
    fizz_on_tex = {
        'left': res['on_left'],
        'center': res['on_center'],
        'right': res['on_right'],
        'short': res['on_short'],
    }

    # If it activates the paint gun, use different textures
    if pos_blue or pos_oran:
        pos_tex = fizz_on_tex
    else:
        pos_tex = fizz_off_tex

    if neg_blue or neg_oran:
        neg_tex = fizz_on_tex
    else:
        neg_tex = fizz_off_tex

    if vbsp.GAME_MODE == 'COOP':
        # We need ATLAS-specific triggers
        pos_trig = vmf.create_ent(classname='trigger_playerteam', )
        neg_trig = vmf.create_ent(classname='trigger_playerteam', )
        output = 'OnStartTouchBluePlayer'
    else:
        pos_trig = vmf.create_ent(classname='trigger_multiple', )
        neg_trig = vmf.create_ent(
            classname='trigger_multiple',
            spawnflags='1',
        )
        output = 'OnStartTouch'

    pos_trig['origin'] = neg_trig['origin'] = fizz_base['origin']
    pos_trig['spawnflags'] = neg_trig['spawnflags'] = '1'  # Clients Only

    pos_trig['targetname'] = fizz_name + '-trig_pos'
    neg_trig['targetname'] = fizz_name + '-trig_neg'

    pos_trig.outputs = [
        Output(
            output,
            fizz_name + '-trig_neg',
            'Enable',
        ),
        Output(
            output,
            fizz_name + '-trig_pos',
            'Disable',
        ),
    ]

    neg_trig.outputs = [
        Output(
            output,
            fizz_name + '-trig_pos',
            'Enable',
        ),
        Output(
            output,
            fizz_name + '-trig_neg',
            'Disable',
        ),
    ]

    voice_attr = vbsp.settings['has_attr']

    if blue_enabled or disable_other:
        # If this is blue/oran only, don't affect the other color
        neg_trig.outputs.append(
            Output(
                output,
                '@BlueIsEnabled',
                'SetValue',
                param=srctools.bool_as_int(neg_blue),
            ))
        pos_trig.outputs.append(
            Output(
                output,
                '@BlueIsEnabled',
                'SetValue',
                param=srctools.bool_as_int(pos_blue),
            ))
        if blue_enabled:
            # Add voice attributes - we have the gun and gel!
            voice_attr['bluegelgun'] = True
            voice_attr['bluegel'] = True
            voice_attr['bouncegun'] = True
            voice_attr['bouncegel'] = True

    if oran_enabled or disable_other:
        neg_trig.outputs.append(
            Output(
                output,
                '@OrangeIsEnabled',
                'SetValue',
                param=srctools.bool_as_int(neg_oran),
            ))
        pos_trig.outputs.append(
            Output(
                output,
                '@OrangeIsEnabled',
                'SetValue',
                param=srctools.bool_as_int(pos_oran),
            ))
        if oran_enabled:
            voice_attr['orangegelgun'] = True
            voice_attr['orangegel'] = True
            voice_attr['speedgelgun'] = True
            voice_attr['speedgel'] = True

    if not oran_enabled and not blue_enabled:
        # If both are disabled, we must shutdown the gun when touching
        # either side - use neg_trig for that purpose!
        # We want to get rid of pos_trig to save ents
        vmf.remove_ent(pos_trig)
        neg_trig['targetname'] = fizz_name + '-trig'
        neg_trig.outputs.clear()
        neg_trig.add_out(
            Output(output, '@BlueIsEnabled', 'SetValue', param='0'))
        neg_trig.add_out(
            Output(output, '@OrangeIsEnabled', 'SetValue', param='0'))

    for fizz_brush in fizz_brushes:  # portal_cleanser ent, not solid!
        # Modify fizzler textures
        bbox_min, bbox_max = fizz_brush.get_bbox()
        for side in fizz_brush.sides():
            norm = side.normal()
            if norm[fizz_axis] == 0:
                # Not the front/back: force nodraw
                # Otherwise the top/bottom will have the odd stripes
                # which won't match the sides
                side.mat = 'tools/toolsnodraw'
                continue
            if norm[fizz_axis] == 1:
                side.mat = pos_tex[vbsp.TEX_FIZZLER[side.mat.casefold()]]
            else:
                side.mat = neg_tex[vbsp.TEX_FIZZLER[side.mat.casefold()]]
        # The fizzler shouldn't kill cubes
        fizz_brush['spawnflags'] = '1'

        fizz_brush.outputs.append(
            Output(
                'OnStartTouch',
                '@shake_global',
                'StartShake',
            ))

        fizz_brush.outputs.append(
            Output(
                'OnStartTouch',
                '@shake_global_sound',
                'PlaySound',
            ))

        # The triggers are 8 units thick, 24 from the center
        # (-1 because fizzlers are 2 thick on each side).
        neg_min, neg_max = Vec(bbox_min), Vec(bbox_max)
        neg_min[fizz_axis] -= 23
        neg_max[fizz_axis] -= 17

        pos_min, pos_max = Vec(bbox_min), Vec(bbox_max)
        pos_min[fizz_axis] += 17
        pos_max[fizz_axis] += 23

        if blue_enabled or oran_enabled:
            neg_trig.solids.append(
                vmf.make_prism(
                    neg_min,
                    neg_max,
                    mat='tools/toolstrigger',
                ).solid, )
            pos_trig.solids.append(
                vmf.make_prism(
                    pos_min,
                    pos_max,
                    mat='tools/toolstrigger',
                ).solid, )
        else:
            # If neither enabled, use one trigger
            neg_trig.solids.append(
                vmf.make_prism(
                    neg_min,
                    pos_max,
                    mat='tools/toolstrigger',
                ).solid, )
コード例 #37
0
def res_make_tag_fizzler(vmf: VMF, inst: Entity, res: Property):
    """Add an Aperture Tag Paint Gun activation fizzler.

    These fizzlers are created via signs, and work very specially.
    MUST be priority -100 so it runs before fizzlers!
    """
    import vbsp
    if vbsp_options.get(str, 'game_id') != utils.STEAM_IDS['TAG']:
        # Abort - TAG fizzlers shouldn't appear in any other game!
        inst.remove()
        return

    fizz_base = fizz_name = None

    # Look for the fizzler instance we want to replace
    for targetname in inst.output_targets():
        if targetname in tag_fizzlers:
            fizz_name = targetname
            fizz_base = tag_fizzlers[targetname]
            del tag_fizzlers[targetname]  # Don't let other signs mod this one!
            continue
        else:
            # It's an indicator toggle, remove it and the antline to clean up.
            LOGGER.warning('Toggle: {}', targetname)
            for ent in vmf.by_target[targetname]:
                remove_ant_toggle(ent)
    inst.outputs.clear()  # Remove the outptuts now, they're not valid anyway.

    if fizz_base is None:
        # No fizzler - remove this sign
        inst.remove()
        return

    # The distance from origin the double signs are seperated by.
    sign_offset = res.int('signoffset', 16)

    sign_loc = (
        # The actual location of the sign - on the wall
        Vec.from_str(inst['origin']) +
        Vec(0, 0, -64).rotate_by_str(inst['angles'])
    )

    # Now deal with the visual aspect:
    # Blue signs should be on top.

    blue_enabled = inst.fixup.bool('$start_enabled')
    oran_enabled = inst.fixup.bool('$start_reversed')
    # If True, single-color signs will also turn off the other color.
    # This also means we always show both signs.
    # If both are enabled or disabled, this has no effect.
    disable_other = (
        not inst.fixup.bool('$disable_autorespawn', True) and
        blue_enabled != oran_enabled
    )
    # Delete fixups now, they aren't useful.
    inst.fixup.clear()

    if not blue_enabled and not oran_enabled:
        # Hide the sign in this case!
        inst.remove()

    inst_angle = srctools.parse_vec_str(inst['angles'])

    inst_normal = Vec(0, 0, 1).rotate(*inst_angle)
    loc = Vec.from_str(inst['origin'])

    if disable_other or (blue_enabled and oran_enabled):
        inst['file'] = res['frame_double']
        # On a wall, and pointing vertically
        if inst_normal.z == 0 and Vec(y=1).rotate(*inst_angle).z:
            # They're vertical, make sure blue's on top!
            blue_loc = Vec(loc.x, loc.y, loc.z + sign_offset)
            oran_loc = Vec(loc.x, loc.y, loc.z - sign_offset)
            # If orange is enabled, with two frames put that on top
            # instead since it's more important
            if disable_other and oran_enabled:
                blue_loc, oran_loc = oran_loc, blue_loc

        else:
            offset = Vec(0, sign_offset, 0).rotate(*inst_angle)
            blue_loc = loc + offset
            oran_loc = loc - offset
    else:
        inst['file'] = res['frame_single']
        # They're always centered
        blue_loc = loc
        oran_loc = loc

    if inst_normal.z != 0:
        # If on floors/ceilings, rotate to point at the fizzler!
        sign_floor_loc = sign_loc.copy()
        sign_floor_loc.z = 0  # We don't care about z-positions.

        # Grab the data saved earlier in res_find_potential_tag_fizzlers()
        axis, side_min, side_max, normal = tag_fizzler_locs[fizz_name]

        # The Z-axis fizzler (horizontal) must be treated differently.
        if axis == 'z':
            # For z-axis, just compare to the center point.
            # The values are really x, y, z, not what they're named.
            sign_dir = sign_floor_loc - (side_min, side_max, normal)
        else:
            # For the other two, we compare to the line,
            # or compare to the closest side (in line with the fizz)
            other_axis = 'x' if axis == 'y' else 'y'
            if abs(sign_floor_loc[other_axis] - normal) < 32:
                # Compare to the closest side. Use ** to swap x/y arguments
                # appropriately. The closest side is the one with the
                # smallest magnitude.
                vmf.create_ent(
                    classname='info_null',
                    targetname=inst['targetname'] + '_min',
                    origin=sign_floor_loc - Vec(**{
                        axis: side_min,
                        other_axis: normal,
                    }),
                )
                vmf.create_ent(
                    classname='info_null',
                    targetname=inst['targetname'] + '_max',
                    origin=sign_floor_loc - Vec(**{
                        axis: side_max,
                        other_axis: normal,
                    }),
                )
                sign_dir = min(
                    sign_floor_loc - Vec(**{
                        axis: side_min,
                        other_axis: normal,
                    }),
                    sign_floor_loc - Vec(**{
                        axis: side_max,
                        other_axis: normal,
                    }),
                    key=Vec.mag,
                )
            else:
                # Align just based on whether we're in front or behind.
                sign_dir = Vec()
                sign_dir[other_axis] = sign_floor_loc[other_axis] - normal

        sign_angle = math.degrees(
            math.atan2(sign_dir.y, sign_dir.x)
        )
        # Round to nearest 90 degrees
        # Add 45 so the switchover point is at the diagonals
        sign_angle = (sign_angle + 45) // 90 * 90

        # Rotate to fit the instances - south is down
        sign_angle = int(sign_angle + 90) % 360
        if inst_normal.z > 0:
            sign_angle = '0 {} 0'.format(sign_angle)
        elif inst_normal.z < 0:
            # Flip upside-down for ceilings
            sign_angle = '0 {} 180'.format(sign_angle)
    else:
        # On a wall, face upright
        sign_angle = PETI_INST_ANGLE[inst_normal.as_tuple()]

    # If disable_other, we show off signs. Otherwise we don't use that sign.
    blue_sign = 'blue_sign' if blue_enabled else 'blue_off_sign' if disable_other else None
    oran_sign = 'oran_sign' if oran_enabled else 'oran_off_sign' if disable_other else None

    if blue_sign:
        vmf.create_ent(
            classname='func_instance',
            file=res[blue_sign, ''],
            targetname=inst['targetname'],
            angles=sign_angle,
            origin=blue_loc.join(' '),
        )

    if oran_sign:
        vmf.create_ent(
            classname='func_instance',
            file=res[oran_sign, ''],
            targetname=inst['targetname'],
            angles=sign_angle,
            origin=oran_loc.join(' '),
        )

    # Now modify the fizzler...

    fizz_brushes = list(
        vmf.by_class['trigger_portal_cleanser'] &
        vmf.by_target[fizz_name + '_brush']
    )

    if 'base_inst' in res:
        fizz_base['file'] = resolve_inst(res['base_inst'])[0]
    fizz_base.outputs.clear()  # Remove outputs, otherwise they break
    # branch_toggle entities

    # Subtract the sign from the list of connections, but don't go below
    # zero
    fizz_base.fixup['$connectioncount'] = str(max(
        0,
        srctools.conv_int(fizz_base.fixup['$connectioncount', ''], 0) - 1
    ))

    if 'model_inst' in res:
        model_inst = resolve_inst(res['model_inst'])[0]
        for mdl_inst in vmf.by_class['func_instance']:
            if mdl_inst['targetname', ''].startswith(fizz_name + '_model'):
                mdl_inst['file'] = model_inst

    # Find the direction the fizzler front/back points - z=floor fizz
    # Signs will associate with the given side!
    bbox_min, bbox_max = fizz_brushes[0].get_bbox()
    for axis, val in zip('xyz', bbox_max-bbox_min):
        if val == 2:
            fizz_axis = axis
            sign_center = (bbox_min[axis] + bbox_max[axis]) / 2
            break
    else:
        # A fizzler that's not 128*x*2?
        raise Exception('Invalid fizzler brush ({})!'.format(fizz_name))

    # Figure out what the sides will set values to...
    pos_blue = False
    pos_oran = False
    neg_blue = False
    neg_oran = False
    if sign_loc[fizz_axis] < sign_center:
        pos_blue = blue_enabled
        pos_oran = oran_enabled
    else:
        neg_blue = blue_enabled
        neg_oran = oran_enabled

    fizz_off_tex = {
        'left': res['off_left'],
        'center': res['off_center'],
        'right': res['off_right'],
        'short': res['off_short'],
    }
    fizz_on_tex = {
        'left': res['on_left'],
        'center': res['on_center'],
        'right': res['on_right'],
        'short': res['on_short'],
    }

    # If it activates the paint gun, use different textures
    if pos_blue or pos_oran:
        pos_tex = fizz_on_tex
    else:
        pos_tex = fizz_off_tex

    if neg_blue or neg_oran:
        neg_tex = fizz_on_tex
    else:
        neg_tex = fizz_off_tex

    if vbsp.GAME_MODE == 'COOP':
        # We need ATLAS-specific triggers
        pos_trig = vmf.create_ent(
            classname='trigger_playerteam',
        )
        neg_trig = vmf.create_ent(
            classname='trigger_playerteam',
        )
        output = 'OnStartTouchBluePlayer'
    else:
        pos_trig = vmf.create_ent(
            classname='trigger_multiple',
        )
        neg_trig = vmf.create_ent(
            classname='trigger_multiple',
            spawnflags='1',
        )
        output = 'OnStartTouch'

    pos_trig['origin'] = neg_trig['origin'] = fizz_base['origin']
    pos_trig['spawnflags'] = neg_trig['spawnflags'] = '1'  # Clients Only

    pos_trig['targetname'] = fizz_name + '-trig_pos'
    neg_trig['targetname'] = fizz_name + '-trig_neg'

    pos_trig.outputs = [
        Output(
            output,
            fizz_name + '-trig_neg',
            'Enable',
        ),
        Output(
            output,
            fizz_name + '-trig_pos',
            'Disable',
        ),
    ]

    neg_trig.outputs = [
        Output(
            output,
            fizz_name + '-trig_pos',
            'Enable',
        ),
        Output(
            output,
            fizz_name + '-trig_neg',
            'Disable',
        ),
    ]

    voice_attr = vbsp.settings['has_attr']

    if blue_enabled or disable_other:
        # If this is blue/oran only, don't affect the other color
        neg_trig.outputs.append(Output(
            output,
            '@BlueIsEnabled',
            'SetValue',
            param=srctools.bool_as_int(neg_blue),
        ))
        pos_trig.outputs.append(Output(
            output,
            '@BlueIsEnabled',
            'SetValue',
            param=srctools.bool_as_int(pos_blue),
        ))
        if blue_enabled:
            # Add voice attributes - we have the gun and gel!
            voice_attr['bluegelgun'] = True
            voice_attr['bluegel'] = True
            voice_attr['bouncegun'] = True
            voice_attr['bouncegel'] = True

    if oran_enabled or disable_other:
        neg_trig.outputs.append(Output(
            output,
            '@OrangeIsEnabled',
            'SetValue',
            param=srctools.bool_as_int(neg_oran),
        ))
        pos_trig.outputs.append(Output(
            output,
            '@OrangeIsEnabled',
            'SetValue',
            param=srctools.bool_as_int(pos_oran),
        ))
        if oran_enabled:
            voice_attr['orangegelgun'] = True
            voice_attr['orangegel'] = True
            voice_attr['speedgelgun'] = True
            voice_attr['speedgel'] = True

    if not oran_enabled and not blue_enabled:
        # If both are disabled, we must shutdown the gun when touching
        # either side - use neg_trig for that purpose!
        # We want to get rid of pos_trig to save ents
        vmf.remove_ent(pos_trig)
        neg_trig['targetname'] = fizz_name + '-trig'
        neg_trig.outputs.clear()
        neg_trig.add_out(Output(
            output,
            '@BlueIsEnabled',
            'SetValue',
            param='0'
        ))
        neg_trig.add_out(Output(
            output,
            '@OrangeIsEnabled',
            'SetValue',
            param='0'
        ))

    for fizz_brush in fizz_brushes:  # portal_cleanser ent, not solid!
        # Modify fizzler textures
        bbox_min, bbox_max = fizz_brush.get_bbox()
        for side in fizz_brush.sides():
            norm = side.normal()
            if norm[fizz_axis] == 0:
                # Not the front/back: force nodraw
                # Otherwise the top/bottom will have the odd stripes
                # which won't match the sides
                side.mat = 'tools/toolsnodraw'
                continue
            if norm[fizz_axis] == 1:
                side.mat = pos_tex[
                    vbsp.TEX_FIZZLER[
                        side.mat.casefold()
                    ]
                ]
            else:
                side.mat = neg_tex[
                    vbsp.TEX_FIZZLER[
                        side.mat.casefold()
                    ]
                ]
        # The fizzler shouldn't kill cubes
        fizz_brush['spawnflags'] = '1'

        fizz_brush.outputs.append(Output(
            'OnStartTouch',
            '@shake_global',
            'StartShake',
        ))

        fizz_brush.outputs.append(Output(
            'OnStartTouch',
            '@shake_global_sound',
            'PlaySound',
        ))

        # The triggers are 8 units thick, 24 from the center
        # (-1 because fizzlers are 2 thick on each side).
        neg_min, neg_max = Vec(bbox_min), Vec(bbox_max)
        neg_min[fizz_axis] -= 23
        neg_max[fizz_axis] -= 17

        pos_min, pos_max = Vec(bbox_min), Vec(bbox_max)
        pos_min[fizz_axis] += 17
        pos_max[fizz_axis] += 23

        if blue_enabled or oran_enabled:
            neg_trig.solids.append(
                vmf.make_prism(
                    neg_min,
                    neg_max,
                    mat='tools/toolstrigger',
                ).solid,
            )
            pos_trig.solids.append(
                vmf.make_prism(
                    pos_min,
                    pos_max,
                    mat='tools/toolstrigger',
                ).solid,
            )
        else:
            # If neither enabled, use one trigger
            neg_trig.solids.append(
                vmf.make_prism(
                    neg_min,
                    pos_max,
                    mat='tools/toolstrigger',
                ).solid,
            )
コード例 #38
0
def res_change_instance(inst: Entity, res: Property):
    """Set the file to a value."""
    inst['file'] = resolve_inst(res.value)[0]
コード例 #39
0
ファイル: conditions.py プロジェクト: xDadiKx/BEE2.4
def res_make_catwalk(_, res):
    """Speciallised result to generate catwalks from markers.

    Only runs once, and then quits the condition list.
    """
    utils.con_log("Starting catwalk generator...")
    marker = resolve_inst(res["markerInst"])
    output_target = res["output_name", "MARKER"]

    instances = {
        name: resolve_inst(res[name, ""])[0]
        for name in (
            "straight_128",
            "straight_256",
            "straight_512",
            "corner",
            "tjunction",
            "crossjunction",
            "end",
            "stair",
            "end_wall",
            "support_wall",
            "support_ceil",
            "support_floor",
            "single_wall",
            "markerInst",
        )
    }
    # If there are no attachments remove a catwalk piece
    instances["NONE"] = ""
    if instances["end_wall"] == "":
        instances["end_wall"] = instances["end"]

    connections = {}  # The directions this instance is connected by (NSEW)
    markers = {}

    for inst in VMF.by_class["func_instance"]:
        if inst["file"].casefold() not in marker:
            continue
        #                   [North, South, East,  West ]
        connections[inst] = [False, False, False, False]
        markers[inst["targetname"]] = inst

    if not markers:
        return True  # No catwalks!

    utils.con_log("Conn:", connections)
    utils.con_log("Markers:", markers)

    # First loop through all the markers, adding connecting sections
    for inst in markers.values():
        for conn in inst.outputs:
            if conn.output != output_target or conn.input != output_target:
                # Indicator toggles or similar, delete these
                print("Removing ", conn.target)
                for del_inst in VMF.by_target[conn.target]:
                    del_inst.remove()
                continue

            inst2 = markers[conn.target]
            print(inst["targetname"], "<->", inst2["targetname"])
            origin1 = Vec.from_str(inst["origin"])
            origin2 = Vec.from_str(inst2["origin"])
            if origin1.x != origin2.x and origin1.y != origin2.y:
                utils.con_log("Instances not aligned!")
                continue

            y_dir = origin1.x == origin2.x  # Which way the connection is
            if y_dir:
                dist = abs(origin1.y - origin2.y)
            else:
                dist = abs(origin1.x - origin2.x)
            vert_dist = origin1.z - origin2.z

            utils.con_log("Dist =", dist, ", Vert =", vert_dist)

            if dist // 2 < vert_dist:
                # The stairs are 2 long, 1 high.
                utils.con_log("Not enough room for stairs!")
                continue

            if dist > 128:
                # add straight sections in between
                place_catwalk_connections(instances, origin1, origin2)

            # Update the lists based on the directions that were set
            conn_lst1 = connections[inst]
            conn_lst2 = connections[inst2]
            if origin1.x < origin2.x:
                conn_lst1[2] = True  # E
                conn_lst2[3] = True  # W
            elif origin2.x < origin1.x:
                conn_lst1[3] = True  # W
                conn_lst2[2] = True  # E

            if origin1.y < origin2.y:
                conn_lst1[0] = True  # N
                conn_lst2[1] = True  # S
            elif origin2.y < origin1.y:
                conn_lst1[1] = True  # S
                conn_lst2[0] = True  # N

        inst.outputs.clear()  # Remove the outputs now, they're useless

    for inst, dir_mask in connections.items():
        # Set the marker instances based on the attached walkways.
        print(inst["targetname"], dir_mask)
        angle = Vec.from_str(inst["angles"], 0, 0, 0)
        new_type, inst["angles"] = utils.CONN_LOOKUP[tuple(dir_mask)]
        inst["file"] = instances[CATWALK_TYPES[new_type]]

        normal = Vec(0, 0, 1).rotate(angle.x, angle.y, angle.z)
        ":type normal: Vec"

        if new_type is utils.CONN_TYPES.side:
            # If the end piece is pointing at a wall, switch the instance.
            if normal.z == 0:
                # Treat booleans as ints to get the direction the connection is
                # in - True == 1, False == 0
                conn_dir = Vec(x=dir_mask[2] - dir_mask[3], y=dir_mask[0] - dir_mask[1], z=0)  # +E, -W  # +N, -S,
                if normal == conn_dir:
                    inst["file"] = instances["end_wall"]
            continue  # We never have normal supports on end pieces
        elif new_type is utils.CONN_TYPES.none:
            # Unconnected catwalks on the wall switch to a special instance.
            # This lets players stand next to a portal surface on the wall.
            if normal.z == 0:
                inst["file"] = instances["single_wall"]
                inst["angles"] = INST_ANGLE[normal.as_tuple()]
            else:
                inst.remove()
            continue  # These don't get supports otherwise

        # Add regular supports
        if normal == (0, 0, 1):
            supp = instances["support_floor"]
        elif normal == (0, 0, -1):
            supp = instances["support_ceil"]
        else:
            supp = instances["support_wall"]

        if supp:
            VMF.create_ent(
                classname="func_instance", origin=inst["origin"], angles=INST_ANGLE[normal.as_tuple()], file=supp
            )

    utils.con_log("Finished catwalk generation!")
    return True  # Don't run this again
コード例 #40
0
ファイル: fizzler.py プロジェクト: prrg/BEE2.4
def res_reshape_fizzler(vmf: VMF, shape_inst: Entity, res: Property):
    """Convert a fizzler connected via the output to a new shape.

    This allows for different placing of fizzler items.
    Each `segment` parameter should be a `x y z;x y z` pair of positions
    that represent the ends of the fizzler.
    `up_axis` should be set to a normal vector pointing in the new 'upward'
    direction.
    `default` is the ID of a fizzler type which should be used if no outputs
    are fired.
    """
    shape_name = shape_inst['targetname']
    shape_item = connections.ITEMS.pop(shape_name)

    shape_angles = Vec.from_str(shape_inst['angles'])
    up_axis = res.vec('up_axis').rotate(*shape_angles)

    for conn in shape_item.outputs:
        fizz_item = conn.to_item
        try:
            fizz = fizzler.FIZZLERS[fizz_item.name]
        except KeyError:
            LOGGER.warning(
                'Reshaping fizzler with non-fizzler output ({})! Ignoring!',
                fizz_item.name)
            continue
        fizz.emitters.clear()  # Remove old positions.
        fizz.up_axis = up_axis
        break
    else:
        # No fizzler, so generate a default.
        # We create the fizzler instance, Fizzler object, and Item object
        # matching it.
        # This is hardcoded to use regular Emancipation Fields.
        base_inst = vmf.create_ent(
            targetname=shape_name,
            classname='func_instance',
            origin=shape_inst['origin'],
            file=resolve_inst('<ITEM_BARRIER_HAZARD:fizz_base>'),
        )
        base_inst.fixup.update(shape_inst.fixup)
        fizz = fizzler.FIZZLERS[shape_name] = fizzler.Fizzler(
            fizzler.FIZZ_TYPES['VALVE_MATERIAL_EMANCIPATION_GRID'],
            up_axis,
            base_inst,
            [],
        )
        fizz_item = connections.Item(
            base_inst,
            connections.ITEM_TYPES['item_barrier_hazard'],
            shape_item.ant_floor_style,
            shape_item.ant_wall_style,
        )
        connections.ITEMS[shape_name] = fizz_item

    # Detach this connection and remove traces of it.
    for conn in list(shape_item.outputs):
        conn.remove()
    for coll in [
            shape_item.antlines, shape_item.ind_panels, shape_item.shape_signs
    ]:
        for ent in coll:
            ent.remove()
        coll.clear()

    for inp in list(shape_item.inputs):
        inp.to_item = fizz_item

    fizz_base = fizz.base_inst
    fizz_base['origin'] = shape_inst['origin']
    origin = Vec.from_str(shape_inst['origin'])

    for seg_prop in res.find_all('Segment'):
        vec1, vec2 = seg_prop.value.split(';')
        seg_min_max = Vec.bbox(
            Vec.from_str(vec1).rotate(*shape_angles) + origin,
            Vec.from_str(vec2).rotate(*shape_angles) + origin,
        )
        fizz.emitters.append(seg_min_max)
コード例 #41
0
ファイル: conditions.py プロジェクト: xDadiKx/BEE2.4
def res_track_plat(_, res):
    """Logic specific to Track Platforms.

    This allows switching the instances used depending on if the track
    is horizontal or vertical and sets the track
    targetnames to a useful value.
    """
    # Get the instances from editoritems
    (inst_bot_grate, inst_bottom, inst_middle, inst_top, inst_plat, inst_plat_oscil, inst_single) = resolve_inst(
        res["orig_item"]
    )
    single_plat_inst = res["single_plat", ""]
    track_targets = res["track_name", ""]

    track_files = [inst_bottom, inst_middle, inst_top, inst_single]
    platforms = [inst_plat, inst_plat_oscil]

    # All the track_set in the map, indexed by origin
    track_instances = {
        Vec.from_str(inst["origin"]).as_tuple(): inst
        for inst in VMF.by_class["func_instance"]
        if inst["file"].casefold() in track_files
    }
    utils.con_log("Track instances:")
    utils.con_log("\n".join("{!s}: {}".format(k, v["file"]) for k, v in track_instances.items()))

    # Now we loop through all platforms in the map, and then locate their
    # track_set
    for plat_inst in VMF.by_class["func_instance"]:
        if plat_inst["file"].casefold() not in platforms:
            continue  # Not a platform!

        utils.con_log('Modifying "' + plat_inst["targetname"] + '"!')

        plat_loc = Vec.from_str(plat_inst["origin"])
        angles = Vec.from_str(plat_inst["angles"])
        # The direction away from the wall/floor/ceil
        normal = Vec(0, 0, 1).rotate(angles.x, angles.y, angles.z)

        for tr_origin, first_track in track_instances.items():
            if plat_loc == tr_origin:
                # Check direction

                if normal == Vec(0, 0, 1).rotate(*Vec.from_str(first_track["angles"])):
                    break
        else:
            raise Exception('Platform "{}" has no track!'.format(plat_inst["targetname"]))

        track_type = first_track["file"].casefold()
        if track_type == inst_single:
            # Track is one block long, use a single-only instance and
            # remove track!
            plat_inst["file"] = single_plat_inst
            first_track.remove()
            continue  # Next platform

        track_set = set()
        if track_type == inst_top or track_type == inst_middle:
            # search left
            track_scan(track_set, track_instances, first_track, middle_file=inst_middle, x_dir=-1)
        if track_type == inst_bottom or track_type == inst_middle:
            # search right
            track_scan(track_set, track_instances, first_track, middle_file=inst_middle, x_dir=+1)

        # Give every track a targetname matching the platform
        for ind, track in enumerate(track_set, start=1):
            if track_targets == "":
                track["targetname"] = plat_inst["targetname"]
            else:
                track["targetname"] = plat_inst["targetname"] + "-" + track_targets + str(ind)

        # Now figure out which way the track faces:

        # The direction horizontal track is offset
        side_dir = Vec(0, 1, 0).rotate(*Vec.from_str(first_track["angles"]))

        # The direction of the platform surface
        facing = Vec(-1, 0, 0).rotate(angles.x, angles.y, angles.z)
        if side_dir == facing:
            track_facing = "HORIZ"
        elif side_dir == -facing:
            track_facing = "HORIZ_MIRR"
        else:
            track_facing = "VERT"
        # Now add the suffixes
        if track_facing == "VERT":
            if utils.conv_bool(res["vert_suffix", ""]):
                for inst in track_set:
                    add_suffix(inst, "_vert")
                if utils.conv_bool(res["plat_suffix", ""]):
                    add_suffix(plat_inst, "_vert")
        elif track_facing == "HORIZ_MIRR":
            if utils.conv_bool(res["horiz_suffix", ""]):
                for inst in track_set:
                    add_suffix(inst, "_horiz_mirrored")
                if utils.conv_bool(res["plat_suffix", ""]):
                    add_suffix(plat_inst, "_horiz")
        else:  # == 'HORIZ'
            if utils.conv_bool(res["horiz_suffix", ""]):
                for inst in track_set:
                    add_suffix(inst, "_horiz")
                if utils.conv_bool(res["plat_suffix", ""]):
                    add_suffix(plat_inst, "_horiz")
    return True  # Only run once!
コード例 #42
0
ファイル: fizzler.py プロジェクト: SnowedFox/BEE2.4
def res_reshape_fizzler(vmf: VMF, shape_inst: Entity, res: Property):
    """Convert a fizzler connected via the output to a new shape.

    This allows for different placing of fizzler items.

    * Each `segment` parameter should be a `x y z;x y z` pair of positions
    that represent the ends of the fizzler.
    * `up_axis` should be set to a normal vector pointing in the new 'upward'
    direction.
    * If none are connected, a regular fizzler will be synthesized.

    The following fixup vars will be set to allow the shape to match the fizzler:

    * `$uses_nodraw` will be 1 if the fizzler nodraws surfaces behind it.
    """
    shape_name = shape_inst['targetname']
    shape_item = connections.ITEMS.pop(shape_name)

    shape_angles = Vec.from_str(shape_inst['angles'])
    up_axis = res.vec('up_axis').rotate(*shape_angles)

    for conn in shape_item.outputs:
        fizz_item = conn.to_item
        try:
            fizz = fizzler.FIZZLERS[fizz_item.name]
        except KeyError:
            LOGGER.warning(
                'Reshaping fizzler with non-fizzler output ({})! Ignoring!',
                fizz_item.name)
            continue
        fizz.emitters.clear()  # Remove old positions.
        fizz.up_axis = up_axis
        fizz.base_inst['origin'] = shape_inst['origin']
        fizz.base_inst['angles'] = shape_inst['angles']
        break
    else:
        # No fizzler, so generate a default.
        # We create the fizzler instance, Fizzler object, and Item object
        # matching it.
        # This is hardcoded to use regular Emancipation Fields.
        base_inst = vmf.create_ent(
            targetname=shape_name,
            classname='func_instance',
            origin=shape_inst['origin'],
            angles=shape_inst['angles'],
            file=resolve_inst('<ITEM_BARRIER_HAZARD:fizz_base>'),
        )
        base_inst.fixup.update(shape_inst.fixup)
        fizz = fizzler.FIZZLERS[shape_name] = fizzler.Fizzler(
            fizzler.FIZZ_TYPES['VALVE_MATERIAL_EMANCIPATION_GRID'],
            up_axis,
            base_inst,
            [],
        )
        fizz_item = connections.Item(
            base_inst,
            connections.ITEM_TYPES['item_barrier_hazard'],
            shape_item.ant_floor_style,
            shape_item.ant_wall_style,
        )
        connections.ITEMS[shape_name] = fizz_item

    # Detach this connection and remove traces of it.
    for conn in list(shape_item.outputs):
        conn.remove()

    # Transfer the inputs from us to the fizzler.
    for inp in list(shape_item.inputs):
        inp.to_item = fizz_item

    shape_item.delete_antlines()

    fizz_base = fizz.base_inst
    fizz_base['origin'] = shape_inst['origin']
    origin = Vec.from_str(shape_inst['origin'])

    fizz.has_cust_position = True
    # Since the fizzler is moved elsewhere, it's the responsibility of
    # the new item to have holes.
    fizz.embedded = False
    # So tell it whether or not it needs to do so.
    shape_inst.fixup['$uses_nodraw'] = fizz.fizz_type.nodraw_behind

    for seg_prop in res.find_all('Segment'):
        vec1, vec2 = seg_prop.value.split(';')
        seg_min_max = Vec.bbox(
            Vec.from_str(vec1).rotate(*shape_angles) + origin,
            Vec.from_str(vec2).rotate(*shape_angles) + origin,
        )
        fizz.emitters.append(seg_min_max)
コード例 #43
0
ファイル: conditions.py プロジェクト: xDadiKx/BEE2.4
def res_change_instance(inst, res):
    """Set the file to a value."""
    inst["file"] = resolve_inst(res.value)[0]
コード例 #44
0
ファイル: addInstance.py プロジェクト: BenVlodgi/BEE2.4
def res_add_overlay_inst(inst: Entity, res: Property):
    """Add another instance on top of this one.

    Values:
        File: The filename.
        Fixup Style: The Fixup style for the instance. '0' (default) is
            Prefix, '1' is Suffix, and '2' is None.
        Copy_Fixup: If true, all the $replace values from the original
            instance will be copied over.
        move_outputs: If true, outputs will be moved to this instance.
        offset: The offset (relative to the base) that the instance
            will be placed. Can be set to '<piston_top>' and
            '<piston_bottom>' to offset based on the configuration.
            '<piston_start>' will set it to the starting position, and
            '<piston_end>' will set it to the ending position.
            of piston platform handles.
        angles: If set, overrides the base instance angles. This does
            not affect the offset property.
        fixup/localfixup: Keyvalues in this block will be copied to the
            overlay entity.
            If the value starts with $, the variable will be copied over.
            If this is present, copy_fixup will be disabled.
    """

    angle = res["angles", inst["angles", "0 0 0"]]
    overlay_inst = vbsp.VMF.create_ent(
        classname="func_instance",
        targetname=inst["targetname", ""],
        file=resolve_inst(res["file", ""])[0],
        angles=angle,
        origin=inst["origin"],
        fixup_style=res["fixup_style", "0"],
    )
    # Don't run if the fixup block exists..
    if srctools.conv_bool(res["copy_fixup", "1"]):
        if "fixup" not in res and "localfixup" not in res:
            # Copy the fixup values across from the original instance
            for fixup, value in inst.fixup.items():
                overlay_inst.fixup[fixup] = value

    conditions.set_ent_keys(overlay_inst.fixup, inst, res, "fixup")

    if res.bool("move_outputs", False):
        overlay_inst.outputs = inst.outputs
        inst.outputs = []

    if "offset" in res:
        folded_off = res["offset"].casefold()
        # Offset the overlay by the given distance
        # Some special placeholder values:
        if folded_off == "<piston_start>":
            if srctools.conv_bool(inst.fixup["$start_up", ""]):
                folded_off = "<piston_top>"
            else:
                folded_off = "<piston_bottom>"
        elif folded_off == "<piston_end>":
            if srctools.conv_bool(inst.fixup["$start_up", ""]):
                folded_off = "<piston_bottom>"
            else:
                folded_off = "<piston_top>"

        if folded_off == "<piston_bottom>":
            offset = Vec(z=srctools.conv_int(inst.fixup["$bottom_level"]) * 128)
        elif folded_off == "<piston_top>":
            offset = Vec(z=srctools.conv_int(inst.fixup["$top_level"], 1) * 128)
        else:
            # Regular vector
            offset = Vec.from_str(conditions.resolve_value(inst, res["offset"]))

        offset.rotate_by_str(inst["angles", "0 0 0"])
        overlay_inst["origin"] = (offset + Vec.from_str(inst["origin"])).join(" ")
    return overlay_inst
コード例 #45
0
def flag_file_equal(inst: Entity, flag: Property):
    """Evaluates True if the instance matches the given file."""
    return inst['file'].casefold() in resolve_inst(flag.value)
コード例 #46
0
ファイル: cutoutTile.py プロジェクト: goodDOS/BEE2.4
def res_cutout_tile(inst, res):
    """Generate random quarter tiles, like in Destroyed or Retro maps.

    - "MarkerItem" is the instance to look for.
    - "TileSize" can be "2x2" or "4x4".
    - rotateMax is the amount of degrees to rotate squarebeam models.

    Materials:
    - "squarebeams" is the squarebeams variant to use.
    - "ceilingwalls" are the sides of the ceiling section.
    - "floorbase" is the texture under floor sections.
    - "tile_glue" is used on top of a thinner tile segment.
    - "clip" is the player_clip texture used over floor segments.
        (This allows customising the surfaceprop.)
    - "Floor4x4Black", "Ceil2x2White" and other combinations can be used to
       override the textures used.
    """
    item = resolve_inst(res['markeritem'])

    INST_LOCS = {}  # Map targetnames -> surface loc
    CEIL_IO = []  # Pairs of ceil inst corners to cut out.
    FLOOR_IO = []  # Pairs of floor inst corners to cut out.

    overlay_ids = {}  # When we replace brushes, we need to fix any overlays
    # on that surface.

    MATS.clear()
    floor_edges = []  # Values to pass to add_floor_sides() at the end

    sign_loc = set(FORCE_LOCATIONS)
    # If any signage is present in the map, we need to force tiles to
    # appear at that location!
    for over in conditions.VMF.by_class['info_overlay']:
        if (
                over['material'].casefold() in FORCE_TILE_MATS and
                # Only check floor/ceiling overlays
                over['basisnormal'] in ('0 0 1', '0 0 -1')
                ):
            loc = Vec.from_str(over['origin'])
            # Sometimes (light bridges etc) a sign will be halfway between
            # tiles, so in that case we need to force 2 tiles.
            loc_min = (loc - (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            loc_max = (loc + (15, 15, 0)) // 32 * 32 + (16, 16, 0)
            sign_loc.add(loc_min.as_tuple())
            sign_loc.add(loc_max.as_tuple())

    SETTINGS = {
        'floor_chance': utils.conv_int(
            res['floorChance', '100'], 100),
        'ceil_chance': utils.conv_int(
            res['ceilingChance', '100'], 100),
        'floor_glue_chance': utils.conv_int(
            res['floorGlueChance', '0']),
        'ceil_glue_chance': utils.conv_int(
            res['ceilingGlueChance', '0']),

        'rotate_beams': int(utils.conv_float(
            res['rotateMax', '0']) * BEAM_ROT_PRECISION),

        'beam_skin': res['squarebeamsSkin', '0'],

        'base_is_disp': utils.conv_bool(res['dispBase', '0']),

        'quad_floor': res['FloorSize', '4x4'].casefold() == '2x2',
        'quad_ceil': res['CeilingSize', '4x4'].casefold() == '2x2',
    }

    for mat_prop in res['Materials', []]:
        MATS[mat_prop.name].append(mat_prop.value)

    if SETTINGS['base_is_disp']:
        # We want the normal brushes to become nodraw.
        MATS['floorbase_disp'] = MATS['floorbase']
        MATS['floorbase'] = ['tools/toolsnodraw']

    for key, default in TEX_DEFAULT:
        if key not in MATS:
            MATS[key] = [default]

    # Find our marker ents
    for inst in conditions.VMF.by_class['func_instance']: # type: VLib.Entity
        if inst['file'].casefold() not in item:
            continue
        targ = inst['targetname']
        orient = Vec(0, 0, 1).rotate_by_str(inst['angles', '0 0 0'])
        # Check the orientation of the marker to figure out what to generate
        if orient == (0, 0, 1):
            io_list = FLOOR_IO
        else:
            io_list = CEIL_IO

        # Reuse orient to calculate where the solid face will be.
        loc = (orient * -64) + Vec.from_str(inst['origin'])
        INST_LOCS[targ] = loc

        for out in inst.output_targets():
            io_list.append((targ, out))

        if not inst.outputs and inst.fixup['$connectioncount'] == '0':
            # If the item doesn't have any connections, 'connect'
            # it to itself so we'll generate a 128x128 tile segment.
            io_list.append((targ, targ))
        inst.remove()  # Remove the instance itself from the map.
    for start_floor, end_floor in FLOOR_IO:
        if end_floor not in INST_LOCS:
            # Not a marker - remove this and the antline.
            for toggle in conditions.VMF.by_target[end_floor]:
                conditions.remove_ant_toggle(toggle)
            continue

        detail_ent = conditions.VMF.create_ent(
            classname='func_detail'
        )

        box_min = Vec(INST_LOCS[start_floor])
        box_min.min(INST_LOCS[end_floor])

        box_max = Vec(INST_LOCS[start_floor])
        box_max.max(INST_LOCS[end_floor])

        if box_min.z != box_max.z:
            continue  # They're not in the same level!
        z = box_min.z

        if SETTINGS['rotate_beams']:
            # We have to generate 1 model per 64x64 block to do rotation...
            gen_rotated_squarebeams(
                box_min - (64, 64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin'],
                max_rot=SETTINGS['rotate_beams'],
            )
        else:
            # Make the squarebeams props, using big models if possible
            gen_squarebeams(
                box_min + (-64, -64, 0),
                box_max + (64, 64, -8),
                skin=SETTINGS['beam_skin']
            )

        # Add a player_clip brush across the whole area
        conditions.VMF.add_brush(conditions.VMF.make_prism(
            p1=box_min - (64, 64, FLOOR_DEPTH),
            p2=box_max + (64, 64, 0),
            mat=MATS['clip'][0],
        ).solid)

        # Add a noportal_volume covering the surface, in case there's
        # room for a portal.
        noportal_solid = conditions.VMF.make_prism(
            # Don't go all the way to the sides, so it doesn't affect wall
            # brushes.
            p1=box_min - (63, 63, 9),
            p2=box_max + (63, 63, 0),
            mat='tools/toolsinvisible',
        ).solid
        noportal_ent = conditions.VMF.create_ent(
            classname='func_noportal_volume',
            origin=box_min.join(' '),
        )
        noportal_ent.solids.append(noportal_solid)

        if SETTINGS['base_is_disp']:
            # Use displacements for the base instead.
            make_alpha_base(
                box_min + (-64, -64, 0),
                box_max + (64, 64, 0),
            )

        for x, y in utils.iter_grid(
                min_x=int(box_min.x),
                max_x=int(box_max.x)+1,
                min_y=int(box_min.y),
                max_y=int(box_max.y)+1,
                stride=128,
                ):
            convert_floor(
                Vec(x, y, z),
                overlay_ids,
                MATS,
                SETTINGS,
                sign_loc,
                detail_ent,
            )

        # Mark borders we need to fill in, and the angle (for func_instance)
        # The wall is the face pointing inwards towards the bottom brush,
        # and the ceil is the ceiling of the block above the bordering grid
        # points.
        for x in range(int(box_min.x), int(box_max.x) + 1, 128):
            # North
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_max.y + 64, z - 64),
                ceil=Vec_tuple(x, box_max.y + 128, z),
                rot=270,
            ))
            # South
            floor_edges.append(BorderPoints(
                wall=Vec(x, box_min.y - 64, z - 64),
                ceil=Vec_tuple(x, box_min.y - 128, z),
                rot=90,
            ))

        for y in range(int(box_min.y), int(box_max.y) + 1, 128):
            # East
            floor_edges.append(BorderPoints(
                wall=Vec(box_max.x + 64, y, z - 64),
                ceil=Vec_tuple(box_max.x + 128, y, z),
                rot=180,
            ))

            # West
            floor_edges.append(BorderPoints(
                wall=Vec(box_min.x - 64, y, z - 64),
                ceil=Vec_tuple(box_min.x - 128, y, z),
                rot=0,
            ))

    add_floor_sides(floor_edges)

    conditions.reallocate_overlays(overlay_ids)

    return True