示例#1
0
def res_cube_coloriser(inst: Entity):
    """Allows recoloring cubes placed at a position."""
    origin = Vec.from_str(inst['origin'])
    # Provided from the timer value directly.
    timer_delay = inst.fixup.int('$timer_delay')

    # Provided from item config panel
    color_override = parse_vec_str(inst.fixup['$color'])

    if color_override != (0, 0, 0):
        color = COLOR_POS[origin.as_tuple()] = color_override
    elif 3 <= timer_delay <= 30:
        color = COLOR_POS[origin.as_tuple()] = COLORS[timer_delay - 3]
    else:
        LOGGER.warning('Unknown timer value "{}"!', timer_delay)
        color = None
    inst.remove()

    # If pointing up, copy the value to the ceiling, so droppers
    # can find a coloriser placed on the illusory cube item under them.
    if Vec(z=1).rotate_by_str(
            inst['angles']) == (0, 0, 1) and color is not None:
        pos = brushLoc.POS.raycast_world(
            origin,
            direction=(0, 0, 1),
        )
        COLOR_SEC_POS[pos.as_tuple()] = color
示例#2
0
 def randomise(inst: Entity) -> None:
     """Apply the random number."""
     rng = rand.seed(b'rand_num', inst, seed)
     if is_float:
         inst.fixup[var] = rng.uniform(min_val, max_val)
     else:
         inst.fixup[var] = rng.randint(min_val, max_val)
示例#3
0
def res_replace_instance(inst: Entity, res: Property):
    """Replace an instance with another entity.

    `keys` and `localkeys` defines the new keyvalues used.
    `targetname` and `angles` are preset, and `origin` will be used to offset
    the given amount from the current location.
    If `keep_instance` is true, the instance entity will be kept instead of
    removed.
    """
    import vbsp

    origin = Vec.from_str(inst['origin'])
    angles = inst['angles']

    if not srctools.conv_bool(res['keep_instance', '0'], False):
        inst.remove()  # Do this first to free the ent ID, so the new ent has
        # the same one.

    # We copy to allow us to still access the $fixups and other values.
    new_ent = inst.copy(des_id=inst.id)
    new_ent.clear_keys()
    # Ensure there's a classname, just in case.
    new_ent['classname'] = 'info_null'

    vbsp.VMF.add_ent(new_ent)

    conditions.set_ent_keys(new_ent, inst, res)

    origin += Vec.from_str(new_ent['origin']).rotate_by_str(angles)
    new_ent['origin'] = origin
    new_ent['angles'] = angles
    new_ent['targetname'] = inst['targetname']
示例#4
0
def res_replace_instance(inst: Entity, res: Property):
    """Replace an instance with another entity.

    'keys' and 'localkeys' defines the new keyvalues used.
    'targetname' and 'angles' are preset, and 'origin' will be used to offset
    the given amount from the current location.
    If 'keep_instance' is true, the instance entity will be kept instead of
    removed.
    """
    import vbsp

    origin = Vec.from_str(inst['origin'])
    angles = inst['angles']

    if not srctools.conv_bool(res['keep_instance', '0'], False):
        inst.remove()  # Do this first to free the ent ID, so the new ent has
        # the same one.

    # We copy to allow us to still acess the $fixups and other values.
    new_ent = inst.copy(des_id=inst.id)
    new_ent.clear_keys()
    # Ensure there's a classname, just in case.
    new_ent['classname'] = 'info_null'

    vbsp.VMF.add_ent(new_ent)

    conditions.set_ent_keys(new_ent, inst, res)

    origin += Vec.from_str(new_ent['origin']).rotate_by_str(angles)
    new_ent['origin'] = origin
    new_ent['angles'] = angles
    new_ent['targetname'] = inst['targetname']
示例#5
0
    def add_output(inst: Entity) -> None:
        """Add the output."""
        if out_type in ('activate', 'deactivate'):
            try:
                item_type = connections.ITEM_TYPES[out_id.casefold()]
            except KeyError:
                LOGGER.warning('"{}" has no connections!', out_id)
                return
            if out_type[0] == 'a':
                if item_type.output_act is None:
                    return

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

        inst.add_out(
            Output(
                inst.fixup.substitute(output),
                conditions.local_name(inst, inst.fixup.substitute(targ))
                or inst['targetname'],
                inst.fixup.substitute(input_name),
                inst.fixup.substitute(parm),
                srctools.conv_float(inst.fixup.substitute(delay)),
                times=times,
                inst_out=inst.fixup.substitute(inst_out) or None,
                inst_in=inst.fixup.substitute(inst_in) or None,
            ))
示例#6
0
def res_replace_instance(vmf: VMF, inst: Entity, res: Property):
    """Replace an instance with another entity.

    `keys` and `localkeys` defines the new keyvalues used.
    `targetname` and `angles` are preset, and `origin` will be used to offset
    the given amount from the current location.
    If `keep_instance` is true, the instance entity will be kept instead of
    removed.
    """
    origin = Vec.from_str(inst['origin'])
    angles = Angle.from_str(inst['angles'])

    if not res.bool('keep_instance'):
        inst.remove()  # Do this first to free the ent ID, so the new ent has
        # the same one.

    # We copy to allow us to still access the $fixups and other values.
    new_ent = inst.copy(des_id=inst.id)
    new_ent.clear_keys()
    # Ensure there's a classname, just in case.
    new_ent['classname'] = 'info_null'

    vmf.add_ent(new_ent)

    conditions.set_ent_keys(new_ent, inst, res)

    new_ent['origin'] = Vec.from_str(new_ent['origin']) @ angles + origin
    new_ent['angles'] = angles
    new_ent['targetname'] = inst['targetname']
示例#7
0
文件: barriers.py 项目: BEEmod/BEE2.4
def res_glass_hole(inst: Entity, res: Property):
    """Add Glass/grating holes. The value should be 'large' or 'small'."""
    hole_type = HoleType(res.value)

    normal: Vec = round(Vec(z=-1) @ Angle.from_str(inst['angles']), 6)
    origin: Vec = Vec.from_str(inst['origin']) // 128 * 128 + 64

    if test_hole_spot(origin, normal, hole_type):
        HOLES[origin.as_tuple(), normal.as_tuple()] = hole_type
        inst['origin'] = origin
        inst['angles'] = normal.to_angle()
        return

    # Test the opposite side of the glass too.

    inv_origin = origin + 128 * normal
    inv_normal = -normal

    if test_hole_spot(inv_origin, inv_normal, hole_type):
        HOLES[inv_origin.as_tuple(), inv_normal.as_tuple()] = hole_type
        inst['origin'] = inv_origin
        inst['angles'] = inv_normal.to_angle()
    else:
        # Remove the instance, so this does nothing.
        inst.remove()
示例#8
0
    def __init__(self, ent: Entity) -> None:
        self.origin = Vec.from_str(ent['origin'])
        self.matrix = Matrix.from_angle(Angle.from_str(ent['angles']))
        self.ent = ent

        self.has_input = False  # We verify every node has an input if used.
        # DestType -> output.
        self.outputs: Dict[DestType, Optional[Node]] = dict.fromkeys(
            self.out_types, None)
        # Outputs fired when cubes reach this point.
        pass_outputs = [
            out for out in ent.outputs
            if out.output.casefold() == self.pass_out_name
        ]
        self.has_pass = bool(pass_outputs)
        if self.has_pass:
            for out in pass_outputs:
                out.output = 'On' + PASS_OUT
            if ent['classname'].startswith('comp_'):
                # Remove the extra keyvalues we use.
                ent.keys = {
                    'classname': 'info_target',
                    'targetname': ent['targetname'],
                    'origin': ent['origin'],
                    'angles': ent['angles'],
                }
            ent.make_unique('_vac_node')
        elif not self.keep_ent:
            ent.remove()
示例#9
0
def res_glass_hole(inst: Entity, res: Property):
    """Add Glass/grating holes. The value should be 'large' or 'small'."""
    hole_type = HoleType(res.value)

    normal = Vec(z=-1).rotate_by_str(inst['angles'])
    origin = Vec.from_str(inst['origin']) // 128 * 128 + 64

    if test_hole_spot(origin, normal, hole_type):
        HOLES[origin.as_tuple(), normal.as_tuple()] = hole_type
        inst['origin'] = origin
        inst['angles'] = normal.to_angle()
        return

    # Test the opposite side of the glass too.

    inv_origin = origin + 128 * normal
    inv_normal = -normal

    if test_hole_spot(inv_origin, inv_normal, hole_type):
        HOLES[inv_origin.as_tuple(), inv_normal.as_tuple()] = hole_type
        inst['origin'] = inv_origin
        inst['angles'] = inv_normal.to_angle()
    else:
        # Remove the instance, so this does nothing.
        inst.remove()
示例#10
0
文件: __init__.py 项目: BEEmod/BEE2.4
def remove_blank_inst(inst: Entity) -> None:
    """Remove instances with a blank file keyvalue.

    This allows conditions to strip the instances when requested.
    """
    # If editoritems instances are set to "", PeTI will autocorrect it to
    # ".vmf" - we need to handle that too.
    if inst['file', ''] in ('', '.vmf'):
        inst.remove()
示例#11
0
def add_output(inst: Entity, prop: Property, target: str) -> None:
    """Add a customisable output to an instance."""
    inst.add_out(Output(
        prop['output', ''],
        target,
        prop['input', ''],
        inst_in=prop['targ_in', ''],
        inst_out=prop['targ_out', ''],
        ))
示例#12
0
文件: __init__.py 项目: BEEmod/BEE2.4
def add_output(inst: Entity, prop: Property, target: str) -> None:
    """Add a customisable output to an instance."""
    inst.add_out(Output(
        prop['output', ''],
        target,
        prop['input', ''],
        inst_in=prop['targ_in', ''],
        inst_out=prop['targ_out', ''],
        ))
示例#13
0
def make_static_pist(vmf: srctools.VMF, ent: Entity, res: Property):
    """Convert a regular piston into a static version.

    This is done to save entities and improve lighting.
    If changed to static pistons, the $bottom and $top level become equal.
    Instances:
        Bottom_1/2/3: Moving piston with the given $bottom_level
        Logic_0/1/2/3: Additional logic instance for the given $bottom_level
        Static_0/1/2/3/4: A static piston at the given height.
    Alternatively, specify all instances via editoritems, by setting the value
    to the item ID optionally followed by a :prefix.
    """

    bottom_pos = ent.fixup.int(consts.FixupVars.PIST_BTM, 0)

    if (ent.fixup.int(consts.FixupVars.CONN_COUNT) > 0
            or ent.fixup.bool(consts.FixupVars.DIS_AUTO_DROP)):  # can it move?
        ent.fixup[consts.FixupVars.BEE_PIST_IS_STATIC] = True

        # Use instances based on the height of the bottom position.
        val = res.value['bottom_' + str(bottom_pos)]
        if val:  # Only if defined
            ent['file'] = val

        logic_file = res.value['logic_' + str(bottom_pos)]
        if logic_file:
            # Overlay an additional logic file on top of the original
            # piston. This allows easily splitting the piston logic
            # from the styled components
            logic_ent = ent.copy()
            logic_ent['file'] = logic_file
            vmf.add_ent(logic_ent)
            # If no connections are present, set the 'enable' value in
            # the logic to True so the piston can function
            logic_ent.fixup[consts.FixupVars.BEE_PIST_MANAGER_A] = (
                ent.fixup.int(consts.FixupVars.CONN_COUNT) == 0)
    else:  # we are static
        ent.fixup[consts.FixupVars.BEE_PIST_IS_STATIC] = False
        if ent.fixup.bool(consts.FixupVars.PIST_IS_UP):
            pos = bottom_pos = ent.fixup.int(consts.FixupVars.PIST_TOP, 1)
        else:
            pos = bottom_pos
        ent.fixup[consts.FixupVars.PIST_TOP] = ent.fixup[
            consts.FixupVars.PIST_BTM] = pos

        val = res.value['static_' + str(pos)]
        if val:
            ent['file'] = val

    # Add in the grating for the bottom as an overlay.
    # It's low to fit the piston at minimum, or higher if needed.
    grate = res.value['grate_high' if bottom_pos > 0 else 'grate_low']
    if grate:
        grate_ent = ent.copy()
        grate_ent['file'] = grate
        vmf.add_ent(grate_ent)
示例#14
0
def res_add_output(inst: Entity, res: Property):
    """Add an output from an instance to a global or local name.

    Values:

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

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

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

    inst.add_out(
        Output(
            resolve_value(inst, output),
            local_name(inst, resolve_value(inst, targ)) or inst['targetname'],
            resolve_value(inst, input_name),
            resolve_value(inst, parm),
            srctools.conv_float(resolve_value(inst, delay)),
            times=times,
            inst_out=resolve_value(inst, inst_out) or None,
            inst_in=resolve_value(inst, inst_in) or None,
        ))
示例#15
0
def make_static_pist(vmf: srctools.VMF, ent: Entity, res: Property):
    """Convert a regular piston into a static version.

    This is done to save entities and improve lighting.
    If changed to static pistons, the $bottom and $top level become equal.
    Instances:
        Bottom_1/2/3: Moving piston with the given $bottom_level
        Logic_0/1/2/3: Additional logic instance for the given $bottom_level
        Static_0/1/2/3/4: A static piston at the given height.
    Alternatively, specify all instances via editoritems, by setting the value
    to the item ID optionally followed by a :prefix.
    """

    bottom_pos = ent.fixup.int('bottom_level', 0)

    if (ent.fixup['connectioncount', '0'] != "0"
            or ent.fixup['disable_autodrop', '0'] != "0"):  # can it move?
        ent.fixup['$is_static'] = True

        # Use instances based on the height of the bottom position.
        val = res.value['bottom_' + str(bottom_pos)]
        if val:  # Only if defined
            ent['file'] = val

        logic_file = res.value['logic_' + str(bottom_pos)]
        if logic_file:
            # Overlay an additional logic file on top of the original
            # piston. This allows easily splitting the piston logic
            # from the styled components
            logic_ent = ent.copy()
            logic_ent['file'] = logic_file
            vmf.add_ent(logic_ent)
            # If no connections are present, set the 'enable' value in
            # the logic to True so the piston can function
            logic_ent.fixup['manager_a'] = srctools.bool_as_int(
                ent.fixup['connectioncount', '0'] == '0')
    else:  # we are static
        ent.fixup['$is_static'] = False
        if ent.fixup.bool('start_up'):
            pos = bottom_pos = ent.fixup.int('top_level', 1)
        else:
            pos = bottom_pos
        ent.fixup['top_level'] = ent.fixup['bottom_level'] = pos

        val = res.value['static_' + str(pos)]
        if val:
            ent['file'] = val

    # Add in the grating for the bottom as an overlay.
    # It's low to fit the piston at minimum, or higher if needed.
    grate = res.value['grate_high' if bottom_pos > 0 else 'grate_low']
    if grate:
        grate_ent = ent.copy()
        grate_ent['file'] = grate
        vmf.add_ent(grate_ent)
示例#16
0
def res_add_output(inst: Entity, res: Property):
    """Add an output from an instance to a global or local name.

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

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

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

    inst.add_out(
        Output(
            resolve_value(inst, output),
            local_name(inst, resolve_value(inst, targ)),
            resolve_value(inst, input_name),
            resolve_value(inst, parm),
            srctools.conv_float(resolve_value(inst, delay)),
            times=times,
            inst_out=resolve_value(inst, inst_out) or None,
            inst_in=resolve_value(inst, inst_in) or None,
        ))
示例#17
0
def remove_ant_toggle(toggle_ent: Entity):
    """Remove a texture_toggle instance , plus the associated antline.

    For non-toggle instances, they will just be removed.
    """
    toggle_ent.remove()

    # Assume anything with '$indicator_name' is a toggle instance
    # This will likely be called on the signs too, if present.
    overlay_name = toggle_ent.fixup[consts.FixupVars.TOGGLE_OVERLAY, '']
    if overlay_name != '':
        for ent in VMF.by_target[overlay_name]:
            ent.remove()
示例#18
0
def res_antigel(inst: Entity) -> None:
    """Implement the Antigel marker."""
    inst.remove()
    origin = Vec.from_str(inst['origin'])
    orient = Matrix.from_angle(Angle.from_str(inst['angles']))

    pos = round(origin - 128 * orient.up(), 6)
    norm = round(orient.up(), 6)
    try:
        tiling.TILES[pos.as_tuple(), norm.as_tuple()].is_antigel = True
    except KeyError:
        LOGGER.warning('No tile to set antigel at {}, {}', pos, norm)
    texturing.ANTIGEL_LOCS.add((origin // 128).as_tuple())
示例#19
0
文件: __init__.py 项目: prrg/BEE2.4
def remove_ant_toggle(toggle_ent: Entity):
    """Remove a texture_toggle instance , plus the associated antline.

    For non-toggle instances, they will just be removed.
    """
    toggle_ent.remove()

    # Assume anything with '$indicator_name' is a toggle instance
    # This will likely be called on the signs too, if present.
    overlay_name = toggle_ent.fixup[consts.FixupVars.TOGGLE_OVERLAY, '']
    if overlay_name != '':
        for ent in VMF.by_target[overlay_name]:
            ent.remove()
示例#20
0
def res_add_output(inst: Entity, res: Property):
    """Add an output from an instance to a global or local name.

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

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

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

    inst.add_out(Output(
        resolve_value(inst, output),
        local_name(inst, resolve_value(inst, targ)),
        resolve_value(inst, input_name),
        resolve_value(inst, parm),
        srctools.conv_float(resolve_value(inst, delay)),
        times=times,
        inst_out=resolve_value(inst, inst_out) or None,
        inst_in=resolve_value(inst, inst_in) or None,
    ))
示例#21
0
文件: __init__.py 项目: BEEmod/BEE2.4
    def make_static(ent: Entity) -> None:
        """Make a piston static."""
        bottom_pos = ent.fixup.int(consts.FixupVars.PIST_BTM, 0)

        if (
            ent.fixup.int(consts.FixupVars.CONN_COUNT) > 0 or
            ent.fixup.bool(consts.FixupVars.DIS_AUTO_DROP)
        ):  # can it move?
            ent.fixup[consts.FixupVars.BEE_PIST_IS_STATIC] = True

            # Use instances based on the height of the bottom position.
            val = instances['bottom_' + str(bottom_pos)]
            if val:  # Only if defined
                ent['file'] = val

            logic_file = instances['logic_' + str(bottom_pos)]
            if logic_file:
                # Overlay an additional logic file on top of the original
                # piston. This allows easily splitting the piston logic
                # from the styled components
                logic_ent = ent.copy()
                logic_ent['file'] = logic_file
                vmf.add_ent(logic_ent)
                # If no connections are present, set the 'enable' value in
                # the logic to True so the piston can function
                logic_ent.fixup[consts.FixupVars.BEE_PIST_MANAGER_A] = (
                    ent.fixup.int(consts.FixupVars.CONN_COUNT) == 0
                )
        else:  # we are static
            ent.fixup[consts.FixupVars.BEE_PIST_IS_STATIC] = False
            if ent.fixup.bool(consts.FixupVars.PIST_IS_UP):
                pos = bottom_pos = ent.fixup.int(consts.FixupVars.PIST_TOP, 1)
            else:
                pos = bottom_pos
            ent.fixup[consts.FixupVars.PIST_TOP] = ent.fixup[consts.FixupVars.PIST_BTM] = pos

            val = instances['static_' + str(pos)]
            if val:
                ent['file'] = val

        # Add in the grating for the bottom as an overlay.
        # It's low to fit the piston at minimum, or higher if needed.
        grate = instances[
            'grate_high'
            if bottom_pos > 0 else
            'grate_low'
        ]
        if grate:
            grate_ent = ent.copy()
            grate_ent['file'] = grate
            vmf.add_ent(grate_ent)
示例#22
0
def res_rand_vec(inst: Entity, res: Property) -> None:
    """A modification to RandomNum which generates a random vector instead.

    'decimal', 'seed' and 'ResultVar' work like RandomNum. min/max x/y/z
    are for each section. If the min and max are equal that number will be used
    instead.
    """
    is_float = srctools.conv_bool(res['decimal'])
    var = res['resultvar', '$random']

    set_random_seed(inst, 'e' + res['seed', 'random'])

    if is_float:
        func = random.uniform
    else:
        func = random.randint

    value = Vec()

    for axis in 'xyz':
        max_val = srctools.conv_float(res['max_' + axis, 0.0])
        min_val = srctools.conv_float(res['min_' + axis, 0.0])
        if min_val == max_val:
            value[axis] = min_val
        else:
            value[axis] = func(min_val, max_val)

    inst.fixup[var] = value.join(' ')
示例#23
0
def res_rand_vec(inst: Entity, res: Property) -> None:
    """A modification to RandomNum which generates a random vector instead.

    `decimal`, `seed` and `ResultVar` work like RandomNum. `min_x`, `max_y` etc
    are used to define the boundaries. If the min and max are equal that number
    will be always used instead.
    """
    is_float = srctools.conv_bool(res['decimal'])
    var = res['resultvar', '$random']

    set_random_seed(inst, 'e' + res['seed', 'random'])

    if is_float:
        func = random.uniform
    else:
        func = random.randint

    value = Vec()

    for axis in 'xyz':
        max_val = srctools.conv_float(res['max_' + axis, 0.0])
        min_val = srctools.conv_float(res['min_' + axis, 0.0])
        if min_val == max_val:
            value[axis] = min_val
        else:
            value[axis] = func(min_val, max_val)

    inst.fixup[var] = value.join(' ')
示例#24
0
def res_python(inst: Entity, res: Property):
    """Apply a function to a fixup."""
    func, result_var = res.value
    result = func(inst.fixup)
    if isinstance(result, bool):
        result = int(result)
    inst.fixup[result_var] = str(result)
示例#25
0
def res_python(inst: Entity, res: Property):
    """Apply a function to a fixup."""
    func, result_var = res.value
    result = func(inst.fixup)
    if isinstance(result, bool):
        result = int(result)
    inst.fixup[result_var] = str(result)
def res_rand_vec(inst: Entity, res: Property):
    """A modification to RandomNum which generates a random vector instead.

    'decimal', 'seed' and 'ResultVar' work like RandomNum. min/max x/y/z
    are for each section. If the min and max are equal that number will be used
    instead.
    """
    is_float = srctools.conv_bool(res['decimal'])
    var = res['resultvar', '$random']
    seed = res['seed', 'random']

    random.seed(inst['origin'] + inst['angles'] + 'random_' + seed)

    if is_float:
        func = random.uniform
    else:
        func = random.randint

    value = Vec()

    for axis in 'xyz':
        max_val = srctools.conv_float(res['max_' + axis, 0.0])
        min_val = srctools.conv_float(res['min_' + axis, 0.0])
        if min_val == max_val:
            value[axis] = min_val
        else:
            value[axis] = func(min_val, max_val)

    inst.fixup[var] = value.join(' ')
示例#27
0
def res_set_inst_var(inst: Entity, res: Property):
    """Set an instance variable to the given value.

    Values follow the format `$start_enabled 1`, with or without the `$`.
    `$out $in` will copy the value of `$in` into `$out`.
    """
    var_name, val = res.value.split(' ', 1)
    inst.fixup[var_name] = inst.fixup.substitute(val, allow_invert=True)
示例#28
0
def res_set_inst_var(inst: Entity, res: Property):
    """Set an instance variable to the given value.

    Values follow the format `$start_enabled 1`, with or without the `$`.
    `$out $in` will copy the value of `$in` into `$out`.
    """
    var_name, val = res.value.split(' ', 1)
    inst.fixup[var_name] = conditions.resolve_value(inst, val)
示例#29
0
def res_set_inst_var(inst: Entity, res: Property):
    """Set an instance variable to the given value.

    Values follow the format `$start_enabled 1`, with or without the `$`.
    `$out $in` will copy the value of `$in` into `$out`.
    """
    var_name, val = res.value.split(' ', 1)
    inst.fixup[var_name] = conditions.resolve_value(inst, val)
示例#30
0
def mon_remove_bullseyes(inst: Entity) -> Optional[object]:
    """Remove bullsyes used for cameras."""
    if not BULLSYE_LOCS:
        return RES_EXHAUSTED

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

    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()
示例#31
0
def flag_brush_at_loc(inst: Entity, flag: Property):
    """Checks to see if a wall is present at the given location.

    - Pos is the position of the brush, where `0 0 0` is the floor-position
       of the brush.
    - Dir is the normal the face is pointing. (0 0 -1) is 'up'.
    - Type defines the type the brush must be:
      - "Any" requires either a black or white brush.
      - "None" means that no brush must be present.
      - "White" requires a portalable surface.
      - "Black" requires a non-portalable surface.
    - SetVar defines an instvar which will be given a value of "black",
      "white" or "none" to allow the result to be reused.
    - If gridPos is true, the position will be snapped so it aligns with
      the 128 brushes (Useful with fizzler/light strip items).
    - RemoveBrush: If set to 1, the brush will be removed if found.
      Only do this to EmbedFace brushes, since it will remove the other
      sides as well.
    """
    from conditions import VMF
    pos = Vec.from_str(flag['pos', '0 0 0'])
    pos.z -= 64  # Subtract so origin is the floor-position
    pos = pos.rotate_by_str(inst['angles', '0 0 0'])

    # Relative to the instance origin
    pos += Vec.from_str(inst['origin', '0 0 0'])

    norm = flag['dir', None]
    if norm is not None:
        norm = Vec.from_str(norm).rotate_by_str(inst['angles', '0 0 0'], )

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

    result_var = flag['setVar', '']
    should_remove = srctools.conv_bool(flag['RemoveBrush', False], False)
    des_type = flag['type', 'any'].casefold()

    brush = SOLIDS.get(pos.as_tuple(), None)

    if brush is None or (norm is not None and abs(brush.normal) != abs(norm)):
        br_type = 'none'
    else:
        br_type = str(brush.color)
        if should_remove:
            VMF.remove_brush(brush.solid, )

    if result_var:
        inst.fixup[result_var] = br_type

    if des_type == 'any' and br_type != 'none':
        return True

    return des_type == br_type
示例#32
0
def res_monitor(inst: Entity, res: Property) -> None:
    """Result for the monitor component.

    Options:
    - bullseye_name: If possible to break this, this is the name to give the npc_bullseye.
    - bullseye_loc: This is the position to place the bullseye at.
    - bullseye_parent: This is the parent to give the bullseye.

    The fixup variable $is_breakable is set to True if lasers or turrets
    are present to indicate the func_breakable should be added.
    """
    global HAS_MONITOR
    import vbsp

    (
        bullseye_name,
        bullseye_loc,
        bullseye_parent,
    ) = res.value

    HAS_MONITOR = True

    has_laser = vbsp.settings['has_attr']['laser']
    # Allow turrets if the monitor is setup to allow it, and the actor should
    # be shot.
    needs_turret = bullseye_name and options.get(bool,
                                                 'voice_studio_should_shoot')

    inst.fixup['$is_breakable'] = has_laser or needs_turret

    # We need to generate an ai_relationship, which makes turrets hate
    # a bullseye.
    if needs_turret:
        loc = Vec(bullseye_loc)
        loc.localise(
            Vec.from_str(inst['origin']),
            Angle.from_str(inst['angles']),
        )
        bullseye_name = conditions.local_name(inst, bullseye_name)
        inst.map.create_ent(
            classname='npc_bullseye',
            targetname=bullseye_name,
            parentname=conditions.local_name(inst, bullseye_parent),
            spawnflags=221186,  # Non-solid, invisible, etc..
            origin=loc,
        )
        relation = inst.map.create_ent(
            classname='ai_relationship',
            targetname='@monitor_turr_hate',
            parentname=bullseye_name,  # When killed, destroy this too.
            spawnflags=2,  # Notify turrets about monitor locations
            disposition=1,  # Hate
            origin=loc,
            subject='npc_portal_turret_floor',
            target=bullseye_name,
        )
        MONITOR_RELATIONSHIP_ENTS.append(relation)
示例#33
0
def res_map_inst_var(inst: Entity, res: Property):
    """Set one instance var based on the value of another.

    The first value is the in -> out var, and all following are values to map.
    """
    in_name, out_name, table = res.value  # type: str, str, dict
    try:
        inst.fixup[out_name] = table[inst.fixup[in_name]]
    except KeyError:
        pass
示例#34
0
def res_map_inst_var(inst: Entity, res: Property):
    """Set one instance var based on the value of another.

    The first value is the in -> out var, and all following are values to map.
    """
    in_name, out_name, table = res.value  # type: str, str, dict
    try:
        inst.fixup[out_name] = table[inst.fixup[in_name]]
    except KeyError:
        pass
示例#35
0
def get(inst: Entity) -> Set[str]:
    """Return the traits for an instance.

    Modify to set values.
    """
    try:
        return inst.traits
    except AttributeError:
        inst.traits = set()
        return inst.traits
示例#36
0
def get(inst: Entity) -> Set[str]:
    """Return the traits for an instance.

    Modify to set values.
    """
    try:
        return inst.traits
    except AttributeError:
        inst.traits = set()
        return inst.traits
示例#37
0
def res_cube_coloriser(inst: Entity):
    """Allows recoloring cubes placed at a position."""
    origin = Vec.from_str(inst['origin'])
    timer_delay = inst.fixup.int('$timer_delay')

    if 3 <= timer_delay <= 30:
        COLOR_POS[origin.as_tuple()] = COLORS[timer_delay - 3]
    else:
        LOGGER.warning('Unknown timer value "{}"!', timer_delay)
    inst.remove()

    # If pointing up, copy the value to the ceiling, so droppers
    # can find a coloriser placed on the illusory cube item under them.
    if Vec(z=1).rotate_by_str(inst['angles']) == (0, 0, 1):
        pos = brushLoc.POS.raycast_world(
            origin,
            direction=(0, 0, 1),
        )
        COLOR_SEC_POS[pos.as_tuple()] = COLORS[timer_delay - 3]
示例#38
0
文件: nodes.py 项目: kb173/srctools
    def __init__(self, ent: Entity):
        """Convert the entity to have the right logic."""
        self.scanner = None
        self.persist_tv = conv_bool(ent.keys.pop('persist_tv', False))

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

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

        super(Straight, self).__init__(ent)
示例#39
0
def res_item_config_to_fixup(inst: Entity, res: Property) -> None:
    """Load a config from the item config panel onto a fixup.

    * `ID` is the ID of the group.
    * `Name` is the name of the widget, or "name[timer]" to pick the value for
      timer multi-widgets.
    * If `UseTimer` is true, it uses `$timer_delay` to choose the value to use.
    * `resultVar` is the location to store the value into.
    * `Default` is the default value, if the config isn't found.
    """
    default = res['default']
    conf = get_itemconf(inst, res)
    inst.fixup[res['ResultVar']] = conf if conf is not None else default
示例#40
0
def res_local_targetname(inst: Entity, res: Property) -> None:
    """Generate a instvar with an instance-local name.

    Useful with AddOutput commands, or other values which use
    targetnames in the parameter.
    The result takes the form `<prefix><instance name>[-<local>]<suffix>`.
    """
    local_name = res['name', '']
    if local_name:
        name = inst['targetname', ''] + '-' + local_name
    else:
        name = inst['targetname', '']
    inst.fixup[res['resultVar']] = res['prefix', ''] + name + res['suffix', '']
示例#41
0
def res_local_targetname(inst: Entity, res: Property):
    """Generate a instvar with an instance-local name.

    Useful with AddOutput commands, or other values which use
    targetnames in the parameter.
    The result takes the form `<prefix><instance name>[-<local>]<suffix>`.
    """
    local_name = res['name', '']
    if local_name:
        name = inst['targetname', ''] + '-' + local_name
    else:
        name = inst['targetname', '']
    inst.fixup[res['resultVar']] = res['prefix', ''] + name + res['suffix', '']
示例#42
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
示例#43
0
def res_monitor(inst: Entity, res: Property) -> None:
    """Result for the monitor component.

    """
    import vbsp

    (
        break_inst,
        bullseye_name,
        bullseye_loc,
        bullseye_parent,
    ) = res.value

    ALL_MONITORS.append(Monitor(inst))

    has_laser = vbsp.settings['has_attr']['laser']
    # Allow turrets if the monitor is setup to allow it, and the actor should
    # be shot.
    needs_turret = bullseye_name and vbsp_options.get(bool, 'voice_studio_should_shoot')

    inst.fixup['$is_breakable'] = has_laser or needs_turret

    # We need to generate an ai_relationship, which makes turrets hate
    # a bullseye.
    if needs_turret:
        loc = Vec(bullseye_loc)
        loc.localise(
            Vec.from_str(inst['origin']),
            Vec.from_str(inst['angles']),
        )
        bullseye_name = local_name(inst, bullseye_name)
        inst.map.create_ent(
            classname='npc_bullseye',
            targetname=bullseye_name,
            parentname=local_name(inst, bullseye_parent),
            spawnflags=221186,  # Non-solid, invisible, etc..
            origin=loc,
        )
        relation = inst.map.create_ent(
            classname='ai_relationship',
            targetname='@monitor_turr_hate',
            spawnflags=2,  # Notify turrets about monitor locations
            disposition=1,  # Hate
            origin=loc,
            subject='npc_portal_turret_floor',
            target=bullseye_name,
        )
        MONITOR_RELATIONSHIP_ENTS.append(relation)
示例#44
0
def res_calc_opposite_wall_dist(inst: Entity, res: Property):
    """Calculate the distance between this item and the opposing wall.

    The value is stored in the `$var` specified by the property value.
    Alternately it is set by `ResultVar`, and `offset` adds or subtracts to the value.
    `GooCollide` means that it will stop when goo is found, otherwise it is
    ignored.
    `GooAdjust` means additionally if the space is goo, the distance will
    be modified so that it specifies the surface of the goo.
    """
    if res.has_children():
        result_var = res['ResultVar']
        dist_off = res.float('offset')
        collide_goo = res.bool('GooCollide')
        adjust_goo = res.bool('GooAdjust')
    else:
        result_var = res.value
        dist_off = 0
        collide_goo = adjust_goo = False

    origin = Vec.from_str(inst['origin'])
    normal = Vec(z=1).rotate_by_str(inst['angles'])

    mask = [
        brushLoc.Block.SOLID,
        brushLoc.Block.EMBED,
        brushLoc.Block.PIT_BOTTOM,
        brushLoc.Block.PIT_SINGLE,
    ]

    # Only if actually downward.
    if normal == (0, 0, -1) and collide_goo:
        mask.append(brushLoc.Block.GOO_TOP)
        mask.append(brushLoc.Block.GOO_SINGLE)

    opposing_pos = brushLoc.POS.raycast_world(
        origin,
        normal,
        mask,
    )

    if adjust_goo and brushLoc.POS['world': opposing_pos + 128*normal].is_goo:
        # If the top is goo, adjust so the 64 below is the top of the goo.
        dist_off += 32

    inst.fixup[result_var] = (origin - opposing_pos).mag() + dist_off
示例#45
0
def res_rand_num(inst: Entity, res: Property) -> None:
    """Generate a random number and save in a fixup value.

    If 'decimal' is true, the value will contain decimals. 'max' and 'min' are
    inclusive. 'ResultVar' is the variable the result will be saved in.
    If 'seed' is set, it will be used to keep the value constant across
    map recompiles. This should be unique.
    """
    is_float = srctools.conv_bool(res['decimal'])
    max_val = srctools.conv_float(res['max', 1.0])
    min_val = srctools.conv_float(res['min', 0.0])
    var = res['resultvar', '$random']
    seed = 'd' + res['seed', 'random']

    set_random_seed(inst, seed)

    if is_float:
        func = random.uniform
    else:
        func = random.randint

    inst.fixup[var] = str(func(min_val, max_val))
示例#46
0
def res_item_config_to_fixup(inst: Entity, res: Property):
    """Load a config from the item config panel onto a fixup.

    * `ID` is the ID of the group.
    * `Name` is the name of the widget.
    * `resultVar` is the location to store the value into.
    * If `UseTimer` is true, it uses `$timer_delay` to choose the value to use.
    * `Default` is the default value, if the config isn't found.
    """
    group_id = res['ID']
    wid_name = res['Name']
    default = res['default']
    if res.bool('UseTimer'):
        timer_delay = inst.fixup.int('$timer_delay')
    else:
        timer_delay = None

    inst.fixup[res['ResultVar']] = vbsp_options.get_itemconf(
        (group_id, wid_name),
        default,
        timer_delay,
    )
示例#47
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
示例#48
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,
            )
示例#49
0
def style_antline(over: Entity, conf: AntType, floor_conf: AntType):
    """Retexture an antline.

    floor_conf, if set is an alternate texture set to use on floors and ceilings.
    """
    # Choose a random one
    random.seed(over['origin'])

    # For P1 style, check to see if the antline is on the floor or walls.
    if Vec.from_str(over['basisNormal']).z != 0:
        conf = floor_conf

    if over['material'] == const.Antlines.STRAIGHT:
        mats = conf.tex_straight
        broken_mats = conf.broken_straight
    elif over['material'] == const.Antlines.CORNER:
        mats = conf.tex_corner
        broken_mats = conf.broken_corner
    else:
        raise ValueError('"{}" is not an antline!'.format(over['material']))

    if conf.broken_chance:  # We can have `broken` antlines.
        bbox_min, bbox_max = srctools.vmf.overlay_bounds(over)
        # Number of 'circles' and the length-wise axis
        length = max(bbox_max - bbox_min)
        long_axis = Vec(0, 1, 0).rotate_by_str(over['angles']).axis()

        # It's a corner or short antline - replace instead of adding more
        if length <= 48:
            if random.randrange(100) < conf.broken_chance:
                mats = broken_mats
        else:
            # Generate multiple for broken overlays.
            min_origin = Vec.from_str(over['origin'])
            min_origin[long_axis] -= length / 2

            broken_iter = broken_antline_iter(
                length // 16,
                conf.broken_chance,
            )
            for sect_min, sect_max, is_broken in broken_iter:
                sect_mats = broken_mats if is_broken else mats

                sect_length = sect_max - sect_min

                # Make a section - base it off the original, and shrink it
                new_over = over.copy()
                over.map.add_ent(new_over)

                # Repeats lengthways
                new_over['startV'] = sect_length
                sect_center = (sect_min + sect_max) / 2

                sect_origin = min_origin.copy()
                sect_origin[long_axis] += sect_center * 16
                new_over['basisorigin'] = new_over['origin'] = sect_origin

                # Set the 4 corner locations to determine the overlay size.
                # They're in local space - x is -8/+8, y=length, z=0
                # Match the sign of the current value
                for axis in '0123':
                    pos = Vec.from_str(new_over['uv' + axis])
                    if pos.y < 0:
                        pos.y = -8 * sect_length
                    else:
                        pos.y = 8 * sect_length
                    new_over['uv' + axis] = pos.join(' ')

                random.choice(sect_mats).apply(new_over)
            # Remove the original overlay
            over.remove()
            return  # Don't texture the original.

    random.choice(mats).apply(over)
示例#50
0
def res_add_overlay_inst(inst: Entity, res: Property):
    """Add another instance on top of this one.

    If a single value, this sets only the filename.
    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.
    """

    if not res.has_children():
        # Use all the defaults.
        res = Property('AddOverlay', [
            Property('File', res.value)
        ])

    angle = res['angles', inst['angles', '0 0 0']]

    orig_name = conditions.resolve_value(inst, res['file', ''])
    filename = instanceLocs.resolve_one(orig_name)

    if not filename:
        if not res.bool('silentLookup'):
            LOGGER.warning('Bad filename for "{}" when adding overlay!', orig_name)
        # Don't bother making a overlay which will be deleted.
        return

    overlay_inst = vbsp.VMF.create_ent(
        classname='func_instance',
        targetname=inst['targetname', ''],
        file=filename,
        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:
        overlay_inst['origin'] = conditions.resolve_offset(inst, res['offset'])

    return overlay_inst
示例#51
0
文件: apTag.py 项目: BenVlodgi/BEE2.4
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.
    This must be before -250 so it runs before fizzlers and connections.
    """
    (
        sign_offset,
        fizz_io_type,
        inst_frame_double,
        inst_frame_single,
        blue_sign_on,
        blue_sign_off,
        oran_sign_on,
        oran_sign_off,
    ) = res.value  # type: int, ItemType, str, str, str, str, str, str
    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

    fizzler = None
    fizzler_item = None

    # Look for the fizzler instance we want to replace.
    sign_item = ITEMS[inst['targetname']]
    for conn in list(sign_item.outputs):
        if conn.to_item.name in FIZZLERS:
            if fizzler is None:
                fizzler = FIZZLERS[conn.to_item.name]
                fizzler_item = conn.to_item
            else:
                raise ValueError('Multiple fizzlers attached to a sign!')

        conn.remove()  # Regardless, remove the useless output.

    sign_item.delete_antlines()

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

    if fizzler.fizz_type.id == TAG_FIZZ_ID:
        LOGGER.warning('Two tag signs attached to one fizzler...')
        inst.remove()
        return

    # Swap to the special Tag Fizzler type.
    fizzler.fizz_type = FIZZ_TYPES[TAG_FIZZ_ID]

    # And also swap the connection's type.
    fizzler_item.item_type = fizz_io_type
    fizzler_item.enable_cmd = fizz_io_type.enable_cmd
    fizzler_item.disable_cmd = fizz_io_type.disable_cmd
    fizzler_item.sec_enable_cmd = fizz_io_type.sec_enable_cmd
    fizzler_item.sec_disable_cmd = fizz_io_type.sec_disable_cmd

    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'] = inst_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'] = inst_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 = calc_fizzler_orient(fizzler)

        # 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.
                sign_dir = min(
                    sign_floor_loc - Vec.with_axes(
                        axis,side_min,
                        other_axis, normal,
                    ),
                    sign_floor_loc - Vec.with_axes(
                        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_on if blue_enabled else blue_sign_off if disable_other else None
    oran_sign = oran_sign_on if oran_enabled else oran_sign_off if disable_other else None

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

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

    # Now modify the fizzler...

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

    # Find the direction the fizzler normal is.
    # Signs will associate with the given side!

    bbox_min, bbox_max = fizzler.emitters[0]
    fizz_norm_axis = fizzler.normal().axis()

    sign_center = (bbox_min[fizz_norm_axis] + bbox_max[fizz_norm_axis]) / 2

    # 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_norm_axis] < sign_center:
        pos_blue = blue_enabled
        pos_oran = oran_enabled
    else:
        neg_blue = blue_enabled
        neg_oran = oran_enabled

    # If it activates the paint gun, use different textures
    fizzler.tag_on_pos = pos_blue or pos_oran
    fizzler.tag_on_neg = neg_blue or neg_oran

    # Now make the trigger ents. We special-case these since they need to swap
    # depending on the sign config and position.

    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'] = fizzler.base_inst['origin']
    pos_trig['spawnflags'] = neg_trig['spawnflags'] = '1'  # Clients Only

    pos_trig['targetname'] = local_name(fizzler.base_inst, 'trig_pos')
    neg_trig['targetname'] = local_name(fizzler.base_inst, 'trig_neg')

    pos_trig['startdisabled'] = neg_trig['startdisabled'] = (
        not fizzler.base_inst.fixup.bool('start_enabled')
    )

    pos_trig.outputs = [
        Output(output, neg_trig, 'Enable'),
        Output(output, pos_trig, 'Disable'),
    ]

    neg_trig.outputs = [
        Output(output, pos_trig, 'Enable'),
        Output(output, neg_trig, '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'] = local_name(fizzler.base_inst, 'trig_off')
        neg_trig.outputs.clear()
        neg_trig.add_out(Output(
            output,
            '@BlueIsEnabled',
            'SetValue',
            param='0'
        ))
        neg_trig.add_out(Output(
            output,
            '@OrangeIsEnabled',
            'SetValue',
            param='0'
        ))

    # Make the triggers.
    for bbox_min, bbox_max in fizzler.emitters:
        bbox_min = bbox_min.copy() - 64 * fizzler.up_axis
        bbox_max = bbox_max.copy() + 64 * fizzler.up_axis

        # The triggers are 8 units thick, with a 32-unit gap in the middle
        neg_min, neg_max = Vec(bbox_min), Vec(bbox_max)
        neg_min[fizz_norm_axis] -= 24
        neg_max[fizz_norm_axis] -= 16

        pos_min, pos_max = Vec(bbox_min), Vec(bbox_max)
        pos_min[fizz_norm_axis] += 16
        pos_max[fizz_norm_axis] += 24

        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,
            )
示例#52
0
def res_faith_mods(inst: Entity, res: Property):
    """Modify the `trigger_catapult` that is created for `ItemFaithPlate` items.

    Values:
      
    - `raise_trig`: Raise or lower the `trigger_catapult`s by this amount.
    - `angled_targ`, `angled_in`: Instance entity and input for angled plates
    - `straight_targ`, `straight_in`: Instance entity and input for
      straight plates
    - `instvar`: A $replace value to set to either 'angled' or '
      'straight'.
    - `enabledVar`: A `$replace` value which will be copied to the main
      trigger's Start Disabled value (and inverted).
    - `trig_temp`: An ID for a template brush to add. This will be offset by
      the trigger's position (in the case of the `helper` trigger).
    """
    # Get data about the trigger this instance uses for flinging
    fixup_var = res['instvar', '']
    trig_enabled = res['enabledVar', None]
    trig_temp = res['trig_temp', '']
    offset = srctools.conv_int(res['raise_trig', '0'])
    if offset:
        offset = Vec(0, 0, offset).rotate_by_str(inst['angles', '0 0 0'])
    else:
        offset = Vec()

    if trig_enabled is not None:
        trig_enabled = srctools.conv_bool(inst.fixup[trig_enabled])
    else:
        trig_enabled = None

    for trig in vbsp.VMF.by_class['trigger_catapult']:
        if inst['targetname'] not in trig['targetname']:
            continue

        # Edit both the normal and the helper trigger..
        trig_origin = trig['origin'] = Vec.from_str(trig['origin']) + offset

        if offset and not trig_temp:
            # No template, shift the current brushes.
            for solid in trig.solids:
                solid.translate(offset)
        elif trig_temp:
            trig.solids = template_brush.import_template(
                temp_name=trig_temp,
                origin=trig_origin,
                angles=Vec.from_str(inst['angles']),
                force_type=template_brush.TEMP_TYPES.world,
            ).world
            # Remove the trigger solids from worldspawn..
            for solid in trig.solids:
                vbsp.VMF.remove_brush(solid)

        if trig_enabled is not None and 'helper' not in trig['targetname']:
            trig['startdisabled'] = srctools.bool_as_int(not trig_enabled)

        # Inspect the outputs to determine the type.
        # We also change them if desired, since that's not possible
        # otherwise.

        for out in trig.outputs:
            if out.inst_in == 'animate_angled_relay':
                # Instead of an instance: output, use local names.
                # This allows us to strip the proxy, as well as use
                # overlay instances.
                out.inst_in = None
                out.target = conditions.local_name(
                    inst,
                    res['angled_targ', 'animate_angled_relay']
                )
                out.input = res['angled_in', 'Trigger']
                if fixup_var:
                    inst.fixup[fixup_var] = 'angled'
                break  # There's only one output we want to look for...

            elif out.inst_in == 'animate_straightup_relay':
                out.inst_in = None
                out.target = conditions.local_name(
                    inst,
                    res[
                        'straight_targ',
                        'animate_straightup_relay'
                    ],
                )
                out.input = res['straight_in', 'Trigger']

                if fixup_var:
                    inst.fixup[fixup_var] = 'straight'
                break
示例#53
0
def flag_brush_at_loc(vmf: VMF, inst: Entity, flag: Property):
    """Checks to see if a wall is present at the given location.

    - `Pos` is the position of the brush, where `0 0 0` is the floor-position
       of the brush.
    - `Dir` is the normal the face is pointing. `(0 0 -1)` is up.
    - `Type` defines the type the brush must be:
      - `Any` requires either a black or white brush.
      - `None` means that no brush must be present.
      - `White` requires a portalable surface.
      - `Black` requires a non-portalable surface.
    - `SetVar` defines an instvar which will be given a value of `black`,
      `white` or `none` to allow the result to be reused.
    - If `gridPos` is true, the position will be snapped so it aligns with
      the 128 grid (Useful with fizzler/light strip items).
    - `RemoveBrush`: If set to `1`, the brush will be removed if found.
      Only do this to `EmbedFace` brushes, since it will remove the other
      sides as well.
    """
    pos = Vec.from_str(flag['pos', '0 0 0'])
    pos.z -= 64  # Subtract so origin is the floor-position
    pos = pos.rotate_by_str(inst['angles', '0 0 0'])

    # Relative to the instance origin
    pos += Vec.from_str(inst['origin', '0 0 0'])

    norm = flag['dir', None]
    if norm is not None:
        norm = Vec.from_str(norm).rotate_by_str(
            inst['angles', '0 0 0'],
        )

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

    result_var = flag['setVar', '']
    should_remove = srctools.conv_bool(flag['RemoveBrush', False], False)
    des_type = flag['type', 'any'].casefold()

    brush = SOLIDS.get(pos.as_tuple(), None)

    if brush is None or (norm is not None and abs(brush.normal) != abs(norm)):
        br_type = 'none'
    else:
        br_type = str(brush.color)
        if should_remove:
            vmf.remove_brush(
                brush.solid,
            )

    if result_var:
        inst.fixup[result_var] = br_type

    if des_type == 'any' and br_type != 'none':
        return True

    return des_type == br_type
示例#54
0
def res_camera(inst: Entity, res: Property):
    """Result for the camera component.

    """
    conf = res.value
    normal = Vec(0, 0, 1).rotate_by_str(inst['angles'])
    if normal.z != 0:
        # Can't be on floor/ceiling!
        inst.remove()
        return
    base_yaw = math.degrees(math.atan2(normal.y, normal.x)) % 360
    inst['angles'] = '0 {:g} 0'.format(base_yaw)

    inst_name = inst['targetname']

    try:
        [target] = inst.map.by_target[inst_name + '-target']  # type: Entity
    except ValueError:
        # No targets with that name
        inst.remove()
        return

    for trig in inst.map.by_class['trigger_catapult']:  # type: Entity
        if trig['targetname'].startswith(inst_name):
            trig.remove()

    target_loc = Vec.from_str(target['origin'])
    target.remove()  # Not needed...

    BULLSYE_LOCS[target_loc.as_tuple()] += 1

    base_loc = Vec.from_str(inst['origin'])

    # Move three times to position the camera arms and lens.
    yaw_pos = Vec(conf['yaw_off']).rotate_by_str(inst['angles'])
    yaw_pos += base_loc

    pitch, yaw, _ = (target_loc - yaw_pos).to_angle()

    inst.map.create_ent(
        classname='func_instance',
        targetname=inst['targetname'],
        file=conf['yaw_inst'],
        angles='0 {:g} 0'.format(yaw),
        origin=yaw_pos,
    )

    pitch_pos = Vec(conf['pitch_off'])
    pitch_pos.rotate(yaw=yaw)
    pitch_pos.rotate_by_str(inst['angles'])
    pitch_pos += yaw_pos

    inst.map.create_ent(
        classname='func_instance',
        targetname=inst['targetname'],
        file=conf['pitch_inst'],
        angles='{:g} {:g} 0'.format(pitch, yaw),
        origin=pitch_pos,
    )

    cam_pos = Vec(conf['cam_off'])
    cam_pos.rotate(pitch=pitch, yaw=yaw)
    cam_pos += pitch_pos

    # Recompute, since this can be slightly different if the camera is large.
    cam_angles = (target_loc - cam_pos).to_angle()

    ALL_CAMERAS.append(Camera(inst, res.value, cam_pos, cam_angles))
示例#55
0
def res_linked_cube_dropper(drp_inst: Entity, res: Property):
    """Link a cube and dropper together, to preplace the cube at a location."""
    time = drp_inst.fixup.int('$timer_delay')
    # Portal 2 bug - when loading existing maps, timers are set to 3...
    if not (3 < time <= 30):
        # Infinite or 3-second - this behaviour is disabled..
        return

    try:

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

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

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

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

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

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

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

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

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

        relay_spawn_3.add_out(Output(
            out='OnTrigger',
            targ=local_name(cube_inst, 'cube'),
            inp='AddOutput',
            param=out.gen_addoutput(),
            only_once=True,
            delay=0.01,
        ))
示例#56
0
def res_piston_plat(vmf: VMF, inst: Entity, res: Property):
    """Generates piston platforms with optimized logic."""
    (
        template,
        visgroup_names,
        inst_filenames,
        automatic_var,
        color_var,
        source_ent,
        snd_start,
        snd_loop,
        snd_stop,
    ) = res.value  # type: template_brush.Template, List[str], Dict[str, str], str, str, str, str, str, str

    min_pos = inst.fixup.int(FixupVars.PIST_BTM)
    max_pos = inst.fixup.int(FixupVars.PIST_TOP)
    start_up = inst.fixup.bool(FixupVars.PIST_IS_UP)

    # Allow doing variable lookups here.
    visgroup_names = [
        conditions.resolve_value(inst, name)
        for name in visgroup_names
    ]

    if len(ITEMS[inst['targetname']].inputs) == 0:
        # No inputs. Check for the 'auto' var if applicable.
        if automatic_var and inst.fixup.bool(automatic_var):
            pass
            # The item is automatically moving, so we generate the dynamics.
        else:
            # It's static, we just make that and exit.
            position = max_pos if start_up else min_pos
            inst.fixup[FixupVars.PIST_BTM] = position
            inst.fixup[FixupVars.PIST_TOP] = position
            static_inst = inst.copy()
            vmf.add_ent(static_inst)
            static_inst['file'] = inst_filenames['fullstatic_' + str(position)]
            return

    init_script = 'SPAWN_UP <- {}'.format('true' if start_up else 'false')

    if snd_start and snd_stop:
        packing.pack_files(vmf, snd_start, snd_stop, file_type='sound')
        init_script += '; START_SND <- `{}`; STOP_SND <- `{}`'.format(snd_start, snd_stop)
    elif snd_start:
        packing.pack_files(vmf, snd_start, file_type='sound')
        init_script += '; START_SND <- `{}`'.format(snd_start)
    elif snd_stop:
        packing.pack_files(vmf, snd_stop, file_type='sound')
        init_script += '; STOP_SND <- `{}`'.format(snd_stop)

    script_ent = vmf.create_ent(
        classname='info_target',
        targetname=local_name(inst, 'script'),
        vscripts='BEE2/piston/common.nut',
        vscript_init_code=init_script,
        origin=inst['origin'],
    )

    if start_up:
        st_pos, end_pos = max_pos, min_pos
    else:
        st_pos, end_pos = min_pos, max_pos

    script_ent.add_out(
        Output('OnUser1', '!self', 'RunScriptCode', 'moveto({})'.format(st_pos)),
        Output('OnUser2', '!self', 'RunScriptCode', 'moveto({})'.format(end_pos)),
    )

    origin = Vec.from_str(inst['origin'])
    angles = Vec.from_str(inst['angles'])
    off = Vec(z=128).rotate(*angles)
    move_ang = off.to_angle()

    # Index -> func_movelinear.
    pistons = {}  # type: Dict[int, Entity]

    static_ent = vmf.create_ent('func_brush', origin=origin)

    color_var = conditions.resolve_value(inst, color_var).casefold()

    if color_var == 'white':
        top_color = template_brush.MAT_TYPES.white
    elif color_var == 'black':
        top_color = template_brush.MAT_TYPES.black
    else:
        top_color = None

    for pist_ind in range(1, 5):
        pist_ent = inst.copy()
        vmf.add_ent(pist_ent)

        if pist_ind <= min_pos:
            # It's below the lowest position, so it can be static.
            pist_ent['file'] = inst_filenames['static_' + str(pist_ind)]
            pist_ent['origin'] = brush_pos = origin + pist_ind * off
            temp_targ = static_ent
        else:
            # It's a moving component.
            pist_ent['file'] = inst_filenames['dynamic_' + str(pist_ind)]
            if pist_ind > max_pos:
                # It's 'after' the highest position, so it never extends.
                # So simplify by merging those all.
                # That's before this so it'll have to exist.
                temp_targ = pistons[max_pos]
                if start_up:
                    pist_ent['origin'] = brush_pos = origin + max_pos * off
                else:
                    pist_ent['origin'] = brush_pos = origin + min_pos * off
                pist_ent.fixup['$parent'] = 'pist' + str(max_pos)
            else:
                # It's actually a moving piston.
                if start_up:
                    brush_pos = origin + pist_ind * off
                else:
                    brush_pos = origin + min_pos * off

                pist_ent['origin'] = brush_pos
                pist_ent.fixup['$parent'] = 'pist' + str(pist_ind)

                pistons[pist_ind] = temp_targ = vmf.create_ent(
                    'func_movelinear',
                    targetname=local_name(pist_ent, 'pist' + str(pist_ind)),
                    origin=brush_pos - off,
                    movedir=move_ang,
                    startposition=start_up,
                    movedistance=128,
                    speed=150,
                )
                if pist_ind - 1 in pistons:
                    pistons[pist_ind]['parentname'] = local_name(
                        pist_ent, 'pist' + str(pist_ind - 1),
                    )

        if not pist_ent['file']:
            # No actual instance, remove.
            pist_ent.remove()

        temp_result = template_brush.import_template(
            template,
            brush_pos,
            angles,
            force_type=template_brush.TEMP_TYPES.world,
            add_to_map=False,
            additional_visgroups={visgroup_names[pist_ind - 1]},
        )
        temp_targ.solids.extend(temp_result.world)

        template_brush.retexture_template(
            temp_result,
            origin,
            pist_ent.fixup,
            force_colour=top_color,
            force_grid='special',
            no_clumping=True,
        )

    if not static_ent.solids:
        static_ent.remove()

    if snd_loop:
        script_ent['classname'] = 'ambient_generic'
        script_ent['message'] = snd_loop
        script_ent['health'] = 10  # Volume
        script_ent['pitch'] = '100'
        script_ent['spawnflags'] = 16  # Start silent, looped.
        script_ent['radius'] = 1024

        if source_ent:
            # Parent is irrelevant for actual entity locations, but it
            # survives for the script to read.
            script_ent['SourceEntityName'] = script_ent['parentname'] = local_name(inst, source_ent)
示例#57
0
def res_cust_fizzler(base_inst: Entity, res: Property):
    """Customises the various components of a custom fizzler item.

    This should be executed on the base instance. Brush and MakeLaserField
    are not permitted on laserfield barriers.
    When executed, the $is_laser variable will be set on the base.
    Options:
        * ModelName: sets the targetname given to the model instances.
        * UniqueModel: If true, each model instance will get a suffix to
            allow unique targetnames.
        * Brush: A brush entity that will be generated (the original is
         deleted.) This cannot be used on laserfields.
            * Name is the instance name for the brush
            * Left/Right/Center/Short/Nodraw are the textures used
            * Keys are a block of keyvalues to be set. Targetname and
              Origin are auto-set.
            * Thickness will change the thickness of the fizzler if set.
              By default it is 2 units thick.
            * Outputs is a block of outputs (laid out like in VMFs). The
              targetnames will be localised to the instance.
            * MergeBrushes, if true will merge this brush set into one
              entity for each fizzler. This is useful for non-fizzlers to
              reduce the entity count.
            * SimplifyBrush, if true will merge the three parts into one brush.
              All sides will receive the "nodraw" texture at 0.25 scale.
            * MaterialModify generates material_modify_controls to control
              the brush. One is generated for each texture used in the brush.
              This has subkeys 'name' and 'var' - the entity name and shader
              variable to be modified. MergeBrushes must be enabled if this
              is present.
        * MakeLaserField generates a brush stretched across the whole
          area.
            * Name, keys and thickness are the same as the regular Brush.
            * Texture/Nodraw are the textures.
            * Width is the pixel width of the laser texture, used to
              scale it correctly.
    """
    model_name = res['modelname', None]
    make_unique = res.bool('UniqueModel')
    fizz_name = base_inst['targetname', '']

    # search for the model instances
    model_targetnames = (
        fizz_name + '_modelStart',
        fizz_name + '_modelEnd',
        )
    is_laser = False
    for inst in vbsp.VMF.by_class['func_instance']:
        if inst['targetname'] in model_targetnames:
            if inst.fixup['skin', '0'] == '2':
                is_laser = True
            if model_name is not None:
                if model_name == '':
                    inst['targetname'] = base_inst['targetname']
                else:
                    inst['targetname'] = (
                        base_inst['targetname'] +
                        '-' +
                        model_name
                    )
            if make_unique:
                inst.make_unique()

            for key, value in base_inst.fixup.items():
                inst.fixup[key] = value

    base_inst.fixup['$is_laser'] = is_laser

    new_brush_config = list(res.find_all('brush'))
    if len(new_brush_config) == 0:
        return  # No brush modifications

    if is_laser:
        # This is a laserfield! We can't edit those brushes!
        LOGGER.warning('CustFizzler executed on LaserField!')
        return

    # Record which materialmodify controls are used, so we can add if needed.
    # Conf id -> (brush_name, conf, [textures])
    modify_controls = {}

    for orig_brush in (
            vbsp.VMF.by_class['trigger_portal_cleanser'] &
            vbsp.VMF.by_target[fizz_name + '_brush']):
        orig_brush.remove()
        for config in new_brush_config:

            new_brush = orig_brush.copy()
            # Unique to the particular config property & fizzler name
            conf_key = (id(config), fizz_name)

            if config.bool('SimplifyBrush'):
                # Replace the brush with a simple one of the same size.
                bbox_min, bbox_max = new_brush.get_bbox()
                new_brush.solids = [vbsp.VMF.make_prism(
                    bbox_min, bbox_max,
                    mat=const.Tools.NODRAW,
                ).solid]

            should_merge = config.bool('MergeBrushes')
            if should_merge and conf_key in FIZZ_BRUSH_ENTS:
                # These are shared by both ents, but new_brush won't be added to
                # the map. (We need it though for the widening code to work).
                FIZZ_BRUSH_ENTS[conf_key].solids.extend(new_brush.solids)
            else:
                vbsp.VMF.add_ent(new_brush)
                # Don't allow restyling it
                vbsp.IGNORED_BRUSH_ENTS.add(new_brush)

                new_brush.clear_keys()  # Wipe the original keyvalues
                new_brush['origin'] = orig_brush['origin']
                new_brush['targetname'] = conditions.local_name(
                    base_inst,
                    config['name'],
                )
                # All ents must have a classname!
                new_brush['classname'] = 'trigger_portal_cleanser'

                conditions.set_ent_keys(
                    new_brush, base_inst,
                    config,
                )

                for out_prop in config.find_children('Outputs'):
                    out = Output.parse(out_prop)
                    out.comma_sep = False
                    out.target = conditions.local_name(
                        base_inst,
                        out.target
                    )
                    new_brush.add_out(out)

                if should_merge:  # The first brush...
                    FIZZ_BRUSH_ENTS[conf_key] = new_brush

            mat_mod_conf = config.find_key('MaterialModify', [])
            if mat_mod_conf:
                try:
                    used_materials = modify_controls[id(mat_mod_conf)][2]
                except KeyError:
                    used_materials = set()
                    modify_controls[id(mat_mod_conf)] = (
                        new_brush['targetname'],
                        mat_mod_conf,
                        used_materials
                    )
                # It can only parent to one brush, so it can't attach
                # to them all properly.
                if not should_merge:
                    raise Exception(
                        "MaterialModify won't work without MergeBrushes!"
                    )
            else:
                used_materials = None

            laserfield_conf = config.find_key('MakeLaserField', None)
            if laserfield_conf.value is not None:
                # Resize the brush into a laserfield format, without
                # the 128*64 parts. If the brush is 128x128, we can
                # skip the resizing since it's already correct.
                laser_tex = laserfield_conf['texture', const.Special.LASERFIELD]
                nodraw_tex = laserfield_conf['nodraw', const.Tools.NODRAW]
                tex_width = laserfield_conf.int('texwidth', 512)
                is_short = False
                for side in new_brush.sides():
                    if side == const.Fizzler.SHORT:
                        is_short = True
                        break

                if is_short:
                    for side in new_brush.sides():
                        if side == const.Fizzler.SHORT:
                            side.mat = laser_tex

                            side.uaxis.offset = 0
                            side.scale = 0.25
                        else:
                            side.mat = nodraw_tex
                else:
                    # The hard part - stretching the brush.
                    convert_to_laserfield(
                        new_brush,
                        laser_tex,
                        nodraw_tex,
                        tex_width,
                    )
                if used_materials is not None:
                    used_materials.add(laser_tex.casefold())
            else:
                # Just change the textures
                for side in new_brush.sides():
                    try:
                        tex_cat = TEX_FIZZLER[side.mat.casefold()]
                        side.mat = config[tex_cat]
                    except (KeyError, IndexError):
                        # If we fail, just use the original textures
                        pass
                    else:
                        if used_materials is not None and tex_cat != 'nodraw':
                            used_materials.add(side.mat.casefold())

            widen_amount = config.float('thickness', 2.0)
            if widen_amount != 2:
                for brush in new_brush.solids:
                    conditions.widen_fizz_brush(
                        brush,
                        thickness=widen_amount,
                    )

    for brush_name, config, textures in modify_controls.values():
        skip_if_static = config.bool('dynamicOnly', True)
        if skip_if_static and base_inst.fixup['$connectioncount'] == '0':
            continue
        mat_mod_name = config['name', 'modify']
        var = config['var', '$outputintensity']
        if not var.startswith('$'):
            var = '$' + var
        for tex in textures:
            vbsp.VMF.create_ent(
                classname='material_modify_control',
                origin=base_inst['origin'],
                targetname=conditions.local_name(base_inst, mat_mod_name),
                materialName='materials/' + tex + '.vmt',
                materialVar=var,
                parentname=brush_name,
            )
示例#58
0
def convert_to_laserfield(
        brush: Entity,
        laser_tex: str,
        nodraw_tex: str,
        tex_width: int,
        ):
    """Convert a fizzler into a laserfield func_brush.

    We need to stretch the brush to get rid of the side sections.
    This is the same as moving all the solids to match the
    bounding box. We first get the origin, used to figure out if
    a point should be set to the max or min axis.

    :param brush: The trigger_portal_cleanser to modify.
    :param tex_width: The pixel width of the laserfield texture, used
                       to rescale it appropriately.
    :param laser_tex: The replacement laserfield texture.
    :param nodraw_tex: A replacement version of tools/nodraw.
    """

    # Get the origin and bbox.
    # The origin isn't in the center, but it still works as long as it's
    # in-between the outermost coordinates
    origin = Vec(*[int(v) for v in brush['origin'].split(' ')])
    bbox_min, bbox_max = brush.get_bbox()

    # we only want the middle one with the center, the others are
    # useless. PeTI happens to always have that in the middle.
    brush.solids = [brush.solids[1]]

    for side in brush.solids[0].sides:
        # For every coordinate, set to the maximum if it's larger than the
        # origin.
        for v in side.planes:
            for ax in 'xyz':
                if int(v[ax]) > origin[ax]:
                    v[ax] = str(bbox_max[ax])
                else:
                    v[ax] = str(bbox_min[ax])

        # Determine the shape of this plane.
        bounds_min, bounds_max = side.get_bbox()
        dimensions = bounds_max - bounds_min

        if 2 in dimensions:  # The front/back won't have this dimension
            # This must be a side of the brush.
            side.mat = nodraw_tex
        else:
            side.mat = laser_tex
            # Now we figure out the corrrect u/vaxis values for the texture.

            size = 0
            offset = 0
            for i, wid in enumerate(dimensions):
                if wid > size:
                    size = int(wid)
                    offset = int(bounds_min[i])
            # texture offset to fit properly
            side.uaxis.offset= tex_width/size * -offset
            side.uaxis.scale= size/tex_width  # scaling

            # heightwise it's always the same
            side.vaxis.offset = 256
            side.vaxis.scale = 0.25
示例#59
0
def res_conveyor_belt(inst: Entity, res: Property):
    """Create a conveyor belt.

    Options:
        SegmentInst: Generated at each square. ('track' is the name of the path.)
        TrackTeleport: Set the track points so they teleport trains to the start.
        Speed: The fixup or number for the train speed.
        MotionTrig: If set, a trigger_multiple will be spawned that EnableMotions
          weighted cubes. The value is the name of the relevant filter.
        EndOutput: Adds an output to the last track. The value is the same as
          outputs in VMFs.
        RotateSegments: If true (default), force segments to face in the
          direction of movement
        RailTemplate: A template for the railings. This is made into a non-solid
          func_brush, combining all sections.
    """
    move_dist = srctools.conv_int(inst.fixup['$travel_distance'])

    if move_dist <= 2:
        # There isn't room for a catwalk, so don't bother.
        inst.remove()
        return

    move_dir = Vec(1, 0, 0).rotate_by_str(inst.fixup['$travel_direction'])
    move_dir.rotate_by_str(inst['angles'])
    start_offset = srctools.conv_float(inst.fixup['$starting_position'], 0)
    teleport_to_start = res.bool('TrackTeleport', True)
    segment_inst_file = res['SegmentInst', '']
    rail_template = res['RailTemplate', None]

    vmf = inst.map

    if segment_inst_file:
        segment_inst_file = conditions.resolve_inst(segment_inst_file)[0]

    track_speed = res['speed', None]

    start_pos = Vec.from_str(inst['origin'])
    end_pos = start_pos + move_dist * move_dir

    if start_offset > 0:
        # If an oscillating platform, move to the closest side..
        offset = start_offset * move_dir
        # The instance is placed this far along, so move back to the end.
        start_pos -= offset
        end_pos -= offset
        if start_offset > 0.5:
            # Swap the direction of movement..
            start_pos, end_pos = end_pos, start_pos
        inst['origin'] = start_pos

    # Find the angle which generates an instance pointing in the direction
    # of movement, with the same normal.
    norm = Vec(z=1).rotate_by_str(inst['angles'])
    for roll in range(0, 360, 90):
        angles = move_dir.to_angle(roll)
        if Vec(z=1).rotate(*angles) == norm:
            break
    else:
        raise ValueError(
            "Can't find angles to give a"
            ' z={} and x={}!'.format(norm, move_dir)
        )

    if res.bool('rotateSegments', True):
        inst['angles'] = angles
    else:
        angles = Vec.from_str(inst['angles'])

    # Add the EnableMotion trigger_multiple seen in platform items.
    # This wakes up cubes when it starts moving.
    motion_filter = res['motionTrig', None]

    # Disable on walls, or if the conveyor can't be turned on.
    if norm != (0, 0, 1) or inst.fixup['$connectioncount'] == '0':
        motion_filter = None

    track_name = conditions.local_name(inst, 'segment_{}')
    rail_temp_solids = []
    last_track = None
    # Place beams at the top, so they don't appear inside wall sections.
    beam_start = start_pos + 48 * norm  # type: Vec
    beam_end = end_pos + 48 * norm  # type: Vec
    for index, pos in enumerate(beam_start.iter_line(beam_end, stride=128), start=1):
        track = vmf.create_ent(
            classname='path_track',
            targetname=track_name.format(index) + '-track',
            origin=pos,
            spawnflags=0,
            orientationtype=0,  # Don't rotate
        )
        if track_speed is not None:
            track['speed'] = track_speed
        if last_track:
            last_track['target'] = track['targetname']

        if index == 1 and teleport_to_start:
            track['spawnflags'] = 16  # Teleport here..

        last_track = track

        # Don't place at the last point - it doesn't teleport correctly,
        # and would be one too many.
        if segment_inst_file and pos != end_pos:
            seg_inst = vmf.create_ent(
                classname='func_instance',
                targetname=track_name.format(index),
                file=segment_inst_file,
                origin=pos,
                angles=angles,
            )
            seg_inst.fixup.update(inst.fixup)

        if rail_template:
            temp = conditions.import_template(
                rail_template,
                pos,
                angles,
                force_type=conditions.TEMP_TYPES.world,
                add_to_map=False,
            )
            rail_temp_solids.extend(temp.world)

    if rail_temp_solids:
        vmf.create_ent(
            classname='func_brush',
            origin=beam_start,
            spawnflags=1,  # Ignore +USE
            solidity=1,  # Not solid
            vrad_brush_cast_shadows=1,
            drawinfastreflection=1,
        ).solids = rail_temp_solids

    if teleport_to_start:
        # Link back to the first track..
        last_track['target'] = track_name.format(1) + '-track'

    # Generate an env_beam pointing from the start to the end of the track.
    beam_keys = res.find_key('BeamKeys', [])
    if beam_keys.value:
        beam = vmf.create_ent(classname='env_beam')

        # 3 offsets - x = distance from walls, y = side, z = height
        beam_off = beam_keys.vec('origin', 0, 63, 56)

        for prop in beam_keys:
            beam[prop.real_name] = prop.value

        # Localise the targetname so it can be triggered..
        beam['LightningStart'] = beam['targetname'] = conditions.local_name(
            inst,
            beam['targetname', 'beam']
        )
        del beam['LightningEnd']
        beam['origin'] = start_pos + Vec(
            -beam_off.x, beam_off.y, beam_off.z,
        ).rotate(*angles)
        beam['TargetPoint'] = end_pos + Vec(
            +beam_off.x, beam_off.y, beam_off.z,
        ).rotate(*angles)

    # Allow adding outputs to the last path_track.
    for prop in res.find_all('EndOutput'):
        output = Output.parse(prop)
        output.output = 'OnPass'
        output.inst_out = None
        output.comma_sep = False
        output.target = conditions.local_name(inst, output.target)
        last_track.add_out(output)

    if motion_filter is not None:
        motion_trig = vmf.create_ent(
            classname='trigger_multiple',
            targetname=conditions.local_name(inst, 'enable_motion_trig'),
            origin=start_pos,
            filtername=motion_filter,
            startDisabled=1,
            wait=0.1,
        )
        motion_trig.add_out(Output('OnStartTouch', '!activator', 'ExitDisabledState'))
        # Match the size of the original...
        motion_trig.solids.append(vmf.make_prism(
            start_pos + Vec(72, -56, 58).rotate(*angles),
            end_pos + Vec(-72, 56, 144).rotate(*angles),
            mat='tools/toolstrigger',
        ).solid)

    if res.bool('NoPortalFloor'):
        # Block portals on the floor..
        floor_noportal = vmf.create_ent(
            classname='func_noportal_volume',
            origin=beam_start,
        )
        floor_noportal.solids.append(vmf.make_prism(
            start_pos + Vec(-60, -60, -66).rotate(*angles),
            end_pos + Vec(60, 60, -60).rotate(*angles),
            mat='tools/toolsinvisible',
        ).solid)

    # A brush covering under the platform.
    base_trig = vmf.make_prism(
        start_pos + Vec(-64, -64, 48).rotate(*angles),
        end_pos + Vec(64, 64, 56).rotate(*angles),
        mat='tools/toolsinvisible',
    ).solid

    vmf.add_brush(base_trig)

    # Make a paint_cleanser under the belt..
    if res.bool('PaintFizzler'):
        pfizz = vmf.create_ent(
            classname='trigger_paint_cleanser',
            origin=start_pos,
        )
        pfizz.solids.append(base_trig.copy())
        for face in pfizz.sides():
            face.mat = 'tools/toolstrigger'