Ejemplo n.º 1
0
def pose(f, rot):
    global FRAME
       
    # Calculate the piston's rotation.
    
    # First find the real position of the piston hinge.
    hinge_pos = Vec(-43, 0, 10.5)
    hinge_pos.x -= 64
    hinge_pos.rotate(float(rot), 0, 0)
    hinge_pos.x += 64
    
	# Where we want the end of the piston to be.
    anchor_point = Vec(z=-96, x=rot*1.5 + 96)
    
    piston_off = hinge_pos - anchor_point
    print(piston_off)
    piston_rot = math.degrees(math.atan2(piston_off.z, -piston_off.x))
    
    f.write(frame_temp.format(
        time=FRAME,
        rot=-round(math.radians(rot), 6),
        # Cancel the effect of rot on pist_rot
        pist_rot=round(math.radians((piston_rot + rot) % 360), 6),
        len=-piston_off.mag(),
        marker=Vec(z=anchor_point.z, y=-anchor_point.x),
    ))
    FRAME += 1
Ejemplo n.º 2
0
    def _texture_fit(
        self,
        side: Side,
        tex_size: float,
        field_length: float,
        fizz: Fizzler,
        neg: Vec,
        pos: Vec,
        is_laserfield=False,
    ) -> None:
        """Calculate the texture offsets required for fitting a texture."""
        if side.vaxis.vec() != -fizz.up_axis:
            # Rotate it
            rot_angle = side.normal().rotation_around()
            for _ in range(4):
                side.uaxis = side.uaxis.rotate(rot_angle)
                side.vaxis = side.vaxis.rotate(rot_angle)
                if side.vaxis.vec() == -fizz.up_axis:
                    break
            else:
                LOGGER.warning("Can't fix rotation for {} -> {}", side.vaxis, fizz.up_axis)

        side.uaxis.offset = -(tex_size / field_length) * neg.dot(side.uaxis.vec())
        side.vaxis.offset = -(tex_size / 128) * neg.dot(side.vaxis.vec())

        #  The above fits it correctly, except it's vertically half-offset.
        # For laserfields that's what we want, for fizzlers we want it normal.
        if not is_laserfield:
            side.vaxis.offset += tex_size / 2

        side.uaxis.scale = field_length / tex_size
        side.vaxis.scale = 128 / tex_size

        side.uaxis.offset %= tex_size
        side.vaxis.offset %= tex_size
Ejemplo n.º 3
0
def find_glass_items(config, vmf: VMF) -> Iterator[Tuple[str, Vec, Vec, Vec, dict]]:
    """Find the bounding boxes for all the glass items matching a config.

    This yields (targetname, min, max, normal, config) tuples.
    """
    # targetname -> min, max, normal, config
    glass_items = {}
    for inst in vmf.by_class['func_instance']:  # type: Entity
        try:
            conf = config[inst['file'].casefold()]
        except KeyError:
            continue
        targ = inst['targetname']
        norm = Vec(x=1).rotate_by_str(inst['angles'])
        origin = Vec.from_str(inst['origin']) - 64 * norm
        try:
            bbox_min, bbox_max, group_norm, group_conf = glass_items[targ]
        except KeyError:
            # First of this group..
            bbox_min, bbox_max = origin.copy(), origin.copy()
            group_norm = norm.copy()
            glass_items[targ] = bbox_min, bbox_max, group_norm, conf
        else:
            bbox_min.min(origin)
            bbox_max.max(origin)
            assert group_norm == norm, '"{}" is inconsistently rotated!'.format(targ)
            assert group_conf is conf, '"{}" has multiple configs!'.format(targ)
        inst.remove()

    for targ, (bbox_min, bbox_max, norm, conf) in glass_items.items():
        yield targ, bbox_min, bbox_max, norm, conf
Ejemplo n.º 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 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']
Ejemplo n.º 5
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()
Ejemplo n.º 6
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(' ')
Ejemplo n.º 7
0
def res_sendificator(vmf: VMF, inst: Entity):
    """Implement Sendificators."""
    # For our version, we know which sendtor connects to what laser,
    # so we can couple the logic together (avoiding @sendtor_mutex).

    sendtor_name = inst['targetname']
    sendtor = connections.ITEMS[sendtor_name]

    sendtor.enable_cmd += (Output(
        '',
        '@{}_las_relay_*'.format(sendtor_name),
        'Trigger',
        delay=0.01,
    ), )

    for ind, conn in enumerate(list(sendtor.outputs), start=1):
        las_item = conn.to_item
        conn.remove()
        try:
            targ_offset, targ_normal = SENDTOR_TARGETS[las_item.name]
        except KeyError:
            LOGGER.warning('"{}" is not a Sendificator target!', las_item.name)
            continue

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

        targ_offset = targ_offset.copy()
        targ_normal = targ_normal.copy().rotate(*angles)

        targ_offset.localise(
            Vec.from_str(las_item.inst['origin']),
            angles,
        )

        relay_name = '@{}_las_relay_{}'.format(sendtor_name, ind)

        relay = vmf.create_ent(
            'logic_relay',
            targetname=relay_name,
            origin=targ_offset,
            angles=targ_normal.to_angle(),
        )
        relay.add_out(
            Output('OnTrigger', '!self', 'RunScriptCode', '::sendtor_source <- self;'),
            Output('OnTrigger', '@sendtor_fire', 'Trigger'),
        )
        if not las_item.inputs:
            # No other inputs, make it on always. PeTI automatically turns
            # it off when inputs are connected, which is annoying.
            las_item.inst.fixup['$start_enabled'] = '1'
            is_on = True
        else:
            is_on = las_item.inst.fixup.bool('$start_enabled')

        relay['StartDisabled'] = not is_on
        las_item.enable_cmd += (Output('', relay_name, 'Enable'),)
        las_item.disable_cmd += (Output('', relay_name, 'Disable'),)
        LOGGER.info('Relay: {}', relay)
Ejemplo n.º 8
0
def make_straight(
        origin: Vec,
        normal: Vec,
        dist: int,
        config: dict,
        is_start=False,
    ):
    """Make a straight line of instances from one point to another."""

    # 32 added to the other directions, plus extended dist in the direction
    # of the normal - 1
    p1 = origin + (normal * ((dist // 128 * 128) - 96))
    # The starting brush needs to
    # stick out a bit further, to cover the
    # point_push entity.
    p2 = origin - (normal * (96 if is_start else 32))

    # bbox before +- 32 to ensure the above doesn't wipe it out
    p1, p2 = Vec.bbox(p1, p2)

    solid = vbsp.VMF.make_prism(
        # Expand to 64x64 in the other two directions
        p1 - 32, p2 + 32,
        mat='tools/toolstrigger',
    ).solid

    motion_trigger(solid.copy())

    push_trigger(origin, normal, [solid])

    angles = normal.to_angle()

    support_file = config['support']
    straight_file = config['straight']
    support_positions = (
        SUPPORT_POS[normal.as_tuple()]
        if support_file else
        []
    )

    for off in range(0, int(dist), 128):
        position = origin + off * normal
        vbsp.VMF.create_ent(
            classname='func_instance',
            origin=position,
            angles=angles,
            file=straight_file,
        )

        for supp_ang, supp_off in support_positions:
            if (position + supp_off).as_tuple() in SOLIDS:
                vbsp.VMF.create_ent(
                    classname='func_instance',
                    origin=position,
                    angles=supp_ang,
                    file=support_file,
                )
Ejemplo n.º 9
0
def res_unst_scaffold_setup(res: Property):
    group = res['group', 'DEFAULT_GROUP']

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

    for block in res.find_all("Instance"):
        conf = {
            # If set, adjusts the offset appropriately
            'is_piston': srctools.conv_bool(block['isPiston', '0']),
            'rotate_logic': srctools.conv_bool(block['AlterAng', '1'], True),
            'off_floor': Vec.from_str(block['FloorOff', '0 0 0']),
            'off_wall': Vec.from_str(block['WallOff', '0 0 0']),

            'logic_start': block['startlogic', ''],
            'logic_end': block['endLogic', ''],
            'logic_mid': block['midLogic', ''],

            'logic_start_rev': block['StartLogicRev', None],
            'logic_end_rev': block['EndLogicRev', None],
            'logic_mid_rev': block['EndLogicRev', None],

            'inst_wall': block['wallInst', ''],
            'inst_floor': block['floorInst', ''],
            'inst_offset': block['offsetInst', None],
            # Specially rotated to face the next track!
            'inst_end': block['endInst', None],
        }
        for logic_type in ('logic_start', 'logic_mid', 'logic_end'):
            if conf[logic_type + '_rev'] is None:
                conf[logic_type + '_rev'] = conf[logic_type]

        for inst in instanceLocs.resolve(block['file']):
            targ_inst[inst] = conf

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

    return group  # We look up the group name to find the values.
Ejemplo n.º 10
0
def flag_blockpos_type(inst: Entity, flag: Property):
    """Determine the type of a grid position.

    If the value is single value, that should be the type.
    Otherwise, the value should be a block with 'offset' and 'type' values.
    The offset is in block increments, with 0 0 0 equal to the mounting surface.
    If 'offset2' is also provided, all positions in the bounding box will
    be checked.

    The type should be a space-seperated list of locations:
    * `VOID` (Outside the map)
    * `SOLID` (Full wall cube)
    * `EMBED` (Hollow wall cube)
    * `AIR` (Inside the map, may be occupied by items)
    * `OCCUPIED` (Known to be occupied by items)
    * `PIT` (Bottomless pits, any)
      * `PIT_SINGLE` (one-high)
      * `PIT_TOP`
      * `PIT_MID`
      * `PIT_BOTTOM`
    * `GOO`
      * `GOO_SINGLE` (one-deep goo)
      * `GOO_TOP` (goo surface)
      * `GOO_MID`
      * `GOO_BOTTOM` (floor)
    """
    pos2 = None

    if flag.has_children():
        pos1 = resolve_offset(inst, flag['offset', '0 0 0'], scale=128, zoff=-128)
        types = flag['type'].split()
        if 'offset2' in flag:
            pos2 = resolve_offset(inst, flag.value, scale=128, zoff=-128)
    else:
        types = flag.value.split()
        pos1 = Vec()

    if pos2 is not None:
        bbox = Vec.iter_grid(*Vec.bbox(pos1, pos2), stride=128)
    else:
        bbox = [pos1]

    for pos in bbox:
        block = brushLoc.POS['world': pos]
        for block_type in types:
            try:
                allowed = brushLoc.BLOCK_LOOKUP[block_type.casefold()]
            except KeyError:
                raise ValueError('"{}" is not a valid block type!'.format(block_type))
            if block in allowed:
                break  # To next position
        else:
            return False  # Didn't match any in this list.
    return True  # Matched all positions.
Ejemplo n.º 11
0
def res_camera_setup(res: Property):
    return {
        'cam_off': Vec.from_str(res['CamOff', '']),
        'yaw_off': Vec.from_str(res['YawOff', '']),
        'pitch_off': Vec.from_str(res['PitchOff', '']),

        'yaw_inst': instanceLocs.resolve_one(res['yawInst', '']),
        'pitch_inst': instanceLocs.resolve_one(res['pitchInst', '']),

        'yaw_range': srctools.conv_int(res['YawRange', ''], 90),
        'pitch_range': srctools.conv_int(res['YawRange', ''], 90),
    }
Ejemplo n.º 12
0
def res_unst_scaffold_setup(res: Property):
    group = res["group", "DEFAULT_GROUP"]

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

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

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

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

    return group  # We look up the group name to find the values.
Ejemplo n.º 13
0
def flag_goo_at_loc(inst: Entity, flag: Property):
    """Check to see if a given location is submerged in goo.

    0 0 0 is the origin of the instance, values are in 128 increments.
    """
    pos = Vec.from_str(flag.value).rotate_by_str(inst['angles', '0 0 0'])
    pos *= 128
    pos += Vec.from_str(inst['origin'])

    # Round to 128 units, then offset to the center
    pos = pos // 128 * 128 + 64  # type: Vec
    val = pos.as_tuple() in GOO_LOCS
    return val
Ejemplo n.º 14
0
def res_make_funnel_light(inst: Entity):
    """Place a light for Funnel items."""
    oran_on = inst.fixup.bool('$start_reversed')
    need_blue = need_oran = False
    name = ''
    if inst.fixup['$connectioncount_polarity'] != '0':
        import vbsp
        if not vbsp.settings['style_vars']['funnelallowswitchedlights']:
            # Allow disabling adding switchable lights.
            return
        name = conditions.local_name(inst, 'light')
        need_blue = need_oran = True
    else:
        if oran_on:
            need_oran = True
        else:
            need_blue = True

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

    if need_blue:
        inst.map.create_ent(
            classname='light',
            targetname=name + '_b' if name else '',
            spawnflags=int(oran_on),  # 1 = Initially Dark
            origin=loc,
            _light='50 120 250 50',
            _lightHDR='-1 -1 -1 1',
            _lightscaleHDR=2,
            _fifty_percent_distance=48,
            _zero_percent_distance=96,
            _hardfalloff=1,
            _distance=0,
            style=0,
        )
    if need_oran:
        inst.map.create_ent(
            classname='light',
            targetname=name + '_o' if name else '',
            spawnflags=int(not oran_on),
            origin=loc,
            _light='250 120 50 50',
            _lightHDR='-1 -1 -1 1',
            _lightscaleHDR=2,
            _fifty_percent_distance=48,
            _zero_percent_distance=96,
            _hardfalloff=1,
            _distance=0,
            style=0,
        )
Ejemplo n.º 15
0
def _calc_fizz_angles() -> None:
    """Generate FIZZ_ANGLES."""
    it = itertools.product('xyz', (-1, 1), 'xyz', (-1, 1))
    for norm_axis, norm_mag, roll_axis, roll_mag in it:
        if norm_axis == roll_axis:
            # They can't both be the same...
            continue
        norm = Vec.with_axes(norm_axis, norm_mag)
        roll = Vec.with_axes(roll_axis, roll_mag)

        # Norm is Z, roll is X,  we want y.
        angle = roll.to_angle_roll(norm)
        up_dir = norm.cross(roll)
        FIZZ_ANGLES[norm.as_tuple(), up_dir.as_tuple()] = angle
Ejemplo n.º 16
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)
Ejemplo n.º 17
0
def get_config(
    node: item_chain.Node,
) -> Tuple[str, Vec]:
    """Compute the config values for a node."""

    orient = (
        'floor' if
        Vec(0, 0, 1).rotate_by_str(node.inst['angles']) == (0, 0, 1)
        else 'wall'
    )
    # Find the offset used for the platform.
    offset = (node.conf['off_' + orient]).copy()  # type: Vec
    if node.conf['is_piston']:
        # Adjust based on the piston position
        offset.z += 128 * srctools.conv_int(
            node.inst.fixup[
                '$top_level' if
                node.inst.fixup[
                    '$start_up'] == '1'
                else '$bottom_level'
            ]
        )
    offset.rotate_by_str(node.inst['angles'])
    offset += Vec.from_str(node.inst['origin'])
    return orient, offset
Ejemplo n.º 18
0
def track_scan(
    tr_set: Set[Entity],
    track_inst: Dict[Tuple[float, float, float], Entity],
    start_track: Entity,
    middle_file: str,
    x_dir: int,
):
    """Build a set of track instances extending from a point.
    :param track_inst: A dictionary mapping origins to track instances
    :param start_track: The instance we start on
    :param middle_file: The file for the center track piece
    :param x_dir: The direction to look (-1 or 1)
    """
    track = start_track
    move_dir = Vec(x_dir*128, 0, 0).rotate_by_str(track['angles'])
    while track:
        tr_set.add(track)

        next_pos = Vec.from_str(track['origin']) + move_dir
        track = track_inst.get(next_pos.as_tuple(), None)
        if track is None:
            return
        if track['file'].casefold() != middle_file:
            # If the next piece is an end section, add it then quit
            tr_set.add(track)
            return
Ejemplo n.º 19
0
def calc_fizzler_orient(fizzler: Fizzler):
    # Figure out how to compare for this fizzler.

    s, l = Vec.bbox(itertools.chain.from_iterable(fizzler.emitters))

    # If it's horizontal, signs should point to the center:
    if abs(s.z - l.z) == 2:
        return (
            'z',
            s.x + l.x / 2,
            s.y + l.y / 2,
            s.z + 1,
        )
    # For the vertical directions, we want to compare based on the line segment.
    if abs(s.x - l.x) == 2:  # Y direction
        return (
            'y',
            s.y,
            l.y,
            s.x + 1,
        )
    else:  # Extends in X direction
        return (
            'x',
            s.x,
            l.x,
            s.y + 1,
        )
Ejemplo n.º 20
0
def res_make_tag_coop_spawn(vmf: VMF, inst: Entity, res: Property):
    """Create the spawn point for ATLAS in the entry corridor.

    It produces either an instance or the normal spawn entity. This is required since ATLAS may need to have the paint gun logic.
    The two parameters `origin` and `facing` must be set to determine the required position.
    If `global` is set, the spawn point will be absolute instead of relative to the current instance.
    """
    if vbsp.GAME_MODE != 'COOP':
        return RES_EXHAUSTED

    is_tag = vbsp_options.get(str, 'game_id') == utils.STEAM_IDS['TAG']

    origin = res.vec('origin')
    normal = res.vec('facing', z=1)

    # Some styles might want to ignore the instance we're running on.
    if not res.bool('global'):
        origin = origin.rotate_by_str(inst['angles'])
        normal = normal.rotate_by_str(inst['angles'])
        origin += Vec.from_str(inst['origin'])

    angles = normal.to_angle()

    if is_tag:
        vmf.create_ent(
            classname='func_instance',
            targetname='paint_gun',
            origin=origin - (0, 0, 16),
            angles=angles,
            # Generated by the BEE2 app.
            file='instances/bee2/tag_coop_gun.vmf',
        )
        # Blocks ATLAS from having a gun
        vmf.create_ent(
            classname='info_target',
            targetname='supress_blue_portalgun_spawn',
            origin=origin,
            angles='0 0 0',
        )
        # Allows info_target to work
        vmf.create_ent(
            classname='env_global',
            targetname='no_spawns',
            globalstate='portalgun_nospawn',
            initialstate=1,
            spawnflags=1,  # Use initial state
            origin=origin,
        )
    vmf.create_ent(
        classname='info_coop_spawn',
        targetname='@coop_spawn_blue',
        ForceGunOnSpawn=int(not is_tag),
        origin=origin,
        angles=angles,
        enabled=1,
        StartingTeam=3,  # ATLAS
    )
    return RES_EXHAUSTED
Ejemplo n.º 21
0
def res_water_splash_setup(res: Property):
    parent = res['parent']
    name = res['name']
    scale = srctools.conv_float(res['scale', ''], 8.0)
    pos1 = Vec.from_str(res['position', ''])
    calc_type = res['type', '']
    pos2 = res['position2', '']
    fast_check = srctools.conv_bool(res['fast_check', ''])

    return name, parent, scale, pos1, pos2, calc_type, fast_check
Ejemplo n.º 22
0
def gen_rotated_squarebeams(p1: Vec, p2: Vec, skin, max_rot: int):
    """Generate broken/rotated squarebeams in a region.

    They will be rotated around their centers, not the model origin.
    """
    z = min(p1.z, p2.z) + 3  # The center of the beams
    for x, y in utils.iter_grid(min_x=int(p1.x),
                                min_y=int(p1.y),
                                max_x=int(p2.x),
                                max_y=int(p2.y),
                                stride=64):
        rand_x = random.randint(-max_rot, max_rot) / BEAM_ROT_PRECISION
        rand_z = random.randint(-max_rot, max_rot) / BEAM_ROT_PRECISION
        # Don't rotate around yaw - the vertical axis.

        # Squarebeams are offset 5 units from their real center
        offset = Vec(0, 0, 5).rotate(rand_x, 0, rand_z)
        prop = _make_squarebeam(Vec(x + 32, y + 32, z) + offset, skin=skin)
        prop['angles'] = '{} 0 {}'.format(rand_x, rand_z)
Ejemplo n.º 23
0
def res_create_entity(vmf: VMF, inst: Entity, res: Property):
    """Create an entity.

    * `keys` and `localkeys` defines the new keyvalues used.
    * `Origin` will be used to offset the given amount from the current location.
    """

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

    new_ent = vmf.create_ent(
        # Ensure there's a classname, just in case.
        classname='info_null')

    conditions.set_ent_keys(new_ent, inst, res)

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

    new_ent['origin'] = origin
    new_ent['angles'] = inst['angles']
Ejemplo n.º 24
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]
Ejemplo n.º 25
0
def res_import_template_setup(res: Property):
    temp_id = res['id'].casefold()

    face = Vec.from_str(res['face_pos', '0 0 -64'])
    norm = Vec.from_str(res['normal', '0 0 1'])

    replace_tex = defaultdict(list)
    for prop in res.find_key('replace', []):
        replace_tex[prop.name].append(prop.value)

    offset = Vec.from_str(res['offset', '0 0 0'])

    return (
        temp_id,
        dict(replace_tex),
        face,
        norm,
        offset,
    )
def res_import_template_setup(res: Property):
    temp_id = res['id'].casefold()

    face = Vec.from_str(res['face_pos', '0 0 -64'])
    norm = Vec.from_str(res['normal', '0 0 1'])

    replace_tex = defaultdict(list)
    for prop in res.find_key('replace', []):
        replace_tex[prop.name].append(prop.value)

    offset = Vec.from_str(res['offset', '0 0 0'])

    return (
        temp_id,
        dict(replace_tex),
        face,
        norm,
        offset,
    )
Ejemplo n.º 27
0
def mode_handle(comp_ent: Entity, ent: Entity) -> str:
    """Compute and return a handle to tis entity."""
    if ent['targetname']:
        return 'Entities.FindByName(null, "{}")'.format(ent['targetname'])
    else:
        # No name, use classname and position.
        return 'Entities.FindByClassnameWithin(null, "{}", {}, 1)'.format(
            ent['classname'],
            vs_vec(Vec.from_str(ent['origin']))
        )
Ejemplo n.º 28
0
def _fill_norm_rotations() -> Dict[
    Tuple[Tuple[float, float, float], Tuple[float, float, float]],
    Tuple[float, float, float]
]:
    """Given a norm->norm rotation, return the angles producing that."""
    rotations = {}
    for norm_ax in 'xyz':
        for norm_mag in [-1, +1]:
            norm = Vec.with_axes(norm_ax, norm_mag)
            for angle_ax in 'xyz':
                for angle_mag in (-90, 90):
                    angle = Vec.with_axes(angle_ax, angle_mag)
                    new_norm = norm.copy().rotate(*angle)
                    if new_norm != norm:
                        rotations[tuple(norm), tuple(new_norm)] = angle.as_tuple()
            # Assign a null rotation as well.
            rotations[tuple(norm), tuple(norm)] = (0.0, 0.0, 0.0)
            rotations[tuple(norm), tuple(-norm)] = (0.0, 0.0, 0.0)
    return rotations
def res_hollow_brush(inst: Entity, res: Property):
    """Hollow out the attached brush, as if EmbeddedVoxel was set.

    This just removes the surface if it's already an embeddedVoxel. This allows
    multiple items to embed thinly in the same block without affecting each
    other.
    """
    loc = Vec(0, 0, -64).rotate_by_str(inst['angles'])
    loc += Vec.from_str(inst['origin'])

    try:
        group = SOLIDS[loc.as_tuple()]
    except KeyError:
        LOGGER.warning('No brush for hollowing at ({})', loc)
        return  # No brush here?

    conditions.hollow_block(group,
                            remove_orig_face=srctools.conv_bool(
                                res['RemoveFace', False]))
def res_water_splash_setup(res: Property):
    parent = res['parent']
    name = res['name']
    scale = srctools.conv_float(res['scale', ''], 8.0)
    pos1 = Vec.from_str(res['position', ''])
    calc_type = res['type', '']
    pos2 = res['position2', '']
    fast_check = srctools.conv_bool(res['fast_check', ''])

    return name, parent, scale, pos1, pos2, calc_type, fast_check
Ejemplo n.º 31
0
def load(opt_blocks: Iterator[Property]) -> None:
    """Read settings from the given property block."""
    SETTINGS.clear()
    set_vals = {}
    for opt_block in opt_blocks:
        for prop in opt_block:
            set_vals[prop.name] = prop.value

    options = {opt.id: opt for opt in DEFAULTS}
    if len(options) != len(DEFAULTS):
        from collections import Counter
        # Find ids used more than once..
        raise Exception('Duplicate option(s)! ({})'.format(', '.join(
            k for k, v in
            Counter(opt.id for opt in DEFAULTS).items()
            if v > 1
        )))

    fallback_opts = []

    for opt in DEFAULTS:
        try:
            val = set_vals.pop(opt.id)
        except KeyError:
            if opt.fallback is not None:
                fallback_opts.append(opt)
                assert opt.fallback in options, 'Invalid fallback in ' + opt.id
            else:
                SETTINGS[opt.id] = opt.default
            continue
        if opt.type is TYPE.VEC:
            # Pass nones so we can check if it failed.. 
            parsed_vals = parse_vec_str(val, x=None)
            if parsed_vals[0] is None:
                SETTINGS[opt.id] = opt.default
            else:
                SETTINGS[opt.id] = Vec(*parsed_vals)
        elif opt.type is TYPE.BOOL:
            SETTINGS[opt.id] = srctools.conv_bool(val, opt.default)
        else:  # int, float, str - no special handling...
            try:
                SETTINGS[opt.id] = opt.type.convert(val)
            except (ValueError, TypeError):
                SETTINGS[opt.id] = opt.default

    for opt in fallback_opts:
        try:
            SETTINGS[opt.id] = SETTINGS[opt.fallback]
        except KeyError:
            raise Exception('Bad fallback for "{}"!'.format(opt.id))
        # Check they have the same type.
        assert opt.type is options[opt.fallback].type

    if set_vals:
        LOGGER.warning('Extra config options: {}', set_vals)
Ejemplo n.º 32
0
def res_make_tag_coop_spawn(vmf: VMF, inst: Entity, res: Property):
    """Create the spawn point for ATLAS, in Aperture Tag.

    This creates an instance with the desired orientation.
    The two parameters 'origin' and 'angles' must be set.
    """
    if vbsp.GAME_MODE != 'COOP':
        return RES_EXHAUSTED

    is_tag = vbsp_options.get(str, 'game_id') == utils.STEAM_IDS['TAG']

    offset = res.vec('origin').rotate_by_str(inst['angles'])
    normal = res.vec('facing', z=1).rotate_by_str(
        inst['angles'],
    )

    origin = Vec.from_str(inst['origin'])
    origin += offset
    angles = normal.to_angle()

    if is_tag:
        vmf.create_ent(
            classname='func_instance',
            targetname='paint_gun',
            origin=origin - (0, 0, 16),
            angles=angles,
            # Generated by the BEE2 app.
            file='instances/bee2/tag_coop_gun.vmf',
        )
        # Blocks ATLAS from having a gun
        vmf.create_ent(
            classname='info_target',
            targetname='supress_blue_portalgun_spawn',
            origin=origin,
            angles='0 0 0',
        )
        # Allows info_target to work
        vmf.create_ent(
            classname='env_global',
            targetname='no_spawns',
            globalstate='portalgun_nospawn',
            initialstate=1,
            spawnflags=1,  # Use initial state
            origin=origin,
        )
    vmf.create_ent(
        classname='info_coop_spawn',
        targetname='@coop_spawn_blue',
        ForceGunOnSpawn=int(not is_tag),
        origin=origin,
        angles=angles,
        enabled=1,
        StartingTeam=3,  # ATLAS
    )
    return RES_EXHAUSTED
Ejemplo n.º 33
0
def res_set_block(inst: Entity, res: Property) -> None:
    """Set a block to the given value, overwriting the existing value.

    - `type` is the type of block to set:
        * `VOID` (Outside the map)
        * `SOLID` (Full wall cube)
        * `EMBED` (Hollow wall cube)
        * `AIR` (Inside the map, may be occupied by items)
        * `OCCUPIED` (Known to be occupied by items)
        * `PIT_SINGLE` (one-high)
        * `PIT_TOP`
        * `PIT_MID`
        * `PIT_BOTTOM`
        * `GOO_SINGLE` (one-deep goo)
        * `GOO_TOP` (goo surface)
        * `GOO_MID`
        * `GOO_BOTTOM` (floor)
    - `offset` is in block increments, with `0 0 0` equal to the mounting surface.
    - If 'offset2' is also provided, all positions in the bounding box will be set.
    """
    try:
        new_vals = brushLoc.BLOCK_LOOKUP[res['type'].casefold()]
    except KeyError:
        raise ValueError('"{}" is not a valid block type!'.format(res['type']))

    try:
        [new_val] = new_vals
    except ValueError:
        # TODO: This could spread top/mid/bottom through the bbox...
        raise ValueError(
            f'Can\'t use compound block type "{res["type"]}", specify '
            "_SINGLE/TOP/MID/BOTTOM"
        )

    pos1 = resolve_offset(inst, res['offset', '0 0 0'], scale=128, zoff=-128)

    if 'offset2' in res:
        pos2 = resolve_offset(inst, res['offset2', '0 0 0'], scale=128, zoff=-128)
        for pos in Vec.iter_grid(*Vec.bbox(pos1, pos2), stride=128):
            brushLoc.POS['world': pos] = new_val
    else:
        brushLoc.POS['world': pos1] = new_val
Ejemplo n.º 34
0
def test_ang_matrix_roundtrip(py_c_vec):
    """Check converting to and from a Matrix does not change values."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    for p, y, r in iter_vec(range(0, 360, 90)):
        vert = Vec(x=1).rotate(p, y, r).z
        if vert < 0.99 or vert > 0.99:
            # If nearly vertical, gimbal lock prevents roundtrips.
            continue
        mat = Matrix.from_angle(Angle(p, y, r))
        assert_ang(mat.to_angle(), p, y, r)
Ejemplo n.º 35
0
def res_create_entity(vmf: VMF, inst: Entity, res: Property):
    """Create an entity.

    'keys' and 'localkeys' defines the new keyvalues used.
    'Origin' will be used to offset the given amount from the current location.
    """

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

    new_ent = vmf.create_ent(
        # Ensure there's a classname, just in case.
        classname='info_null'
    )

    conditions.set_ent_keys(new_ent, inst, res)

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

    new_ent['origin'] = origin
    new_ent['angles'] = inst['angles']
Ejemplo n.º 36
0
def res_import_template_setup(
    res: Property, ) -> Tuple[str, Dict[str, List[str]], Vec, Vec, Vec]:
    temp_id = res['id'].casefold()

    face = Vec.from_str(res['face_pos', '0 0 -64'])
    norm = Vec.from_str(res['normal', '0 0 1'])

    replace_tex = defaultdict(list)  # type: Dict[str, List[str]]
    for prop in res.find_key('replace', []):
        replace_tex[prop.name.replace('\\', '/')].append(prop.value)

    offset = Vec.from_str(res['offset', '0 0 0'])

    return (
        temp_id,
        dict(replace_tex),
        face,
        norm,
        offset,
    )
Ejemplo n.º 37
0
def push_trigger(vmf: VMF, loc: Vec, normal: Vec, solids: list[Solid]) -> None:
    """Generate the push trigger for these solids."""
    # We only need one trigger per direction, for now.
    try:
        ent = PUSH_TRIGS[normal.as_tuple()]
    except KeyError:
        ent = PUSH_TRIGS[normal.as_tuple()] = vmf.create_ent(
            classname='trigger_push',
            origin=loc,
            # The z-direction is reversed..
            pushdir=normal.to_angle(),
            speed=(
                UP_PUSH_SPEED if normal.z > 1e-6 else
                DN_PUSH_SPEED if normal.z < -1e-6 else
                PUSH_SPEED
            ),
            spawnflags='1103',  # Clients, Physics, Everything
        )

    ent.solids.extend(solids)
Ejemplo n.º 38
0
    def __init__(self, ent: Entity):
        """Convert the entity to have the right logic."""
        self.scanner = None
        self.persist_tv = conv_bool(ent.keys.pop('persist_tv', False))

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

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

        super(Straight, self).__init__(ent)
Ejemplo n.º 39
0
def res_hollow_brush(inst: Entity, res: Property):
    """Hollow out the attached brush, as if EmbeddedVoxel was set.

    This just removes the surface if it's already an embeddedVoxel. This allows
    multiple items to embed thinly in the same block without affecting each
    other.
    """
    loc = Vec(0, 0, -64).rotate_by_str(inst['angles'])
    loc += Vec.from_str(inst['origin'])

    try:
        group = SOLIDS[loc.as_tuple()]
    except KeyError:
        LOGGER.warning('No brush for hollowing at ({})', loc)
        return  # No brush here?

    conditions.hollow_block(
        group,
        remove_orig_face=srctools.conv_bool(res['RemoveFace', False])
    )
Ejemplo n.º 40
0
def res_force_upright(inst: Entity):
    """Position an instance to orient upwards while keeping the normal.

    The result angle will have pitch and roll set to 0. Vertical
    instances are unaffected.
    """
    normal = Vec(0, 0, 1).rotate_by_str(inst['angles'])
    if normal.z != 0:
        return
    ang = math.degrees(math.atan2(normal.y, normal.x))
    inst['angles'] = '0 {:g} 0'.format(ang % 360)  # Don't use negatives
Ejemplo n.º 41
0
 def _conv_key(pos: _grid_keys) -> Vec_tuple:
     """Convert the key given in [] to a grid-position, as a x,y,z tuple."""
     if isinstance(pos, slice):
         system, pos = pos.start, pos.stop
         pos = Grid._conv_key(pos)
         if system == 'world':
             return tuple(world_to_grid(Vec(pos)))
         else:
             return pos
     x, y, z = pos
     return x, y, z
Ejemplo n.º 42
0
def res_sendificator(vmf: VMF, inst: Entity):
    """Implement Sendificators."""
    # For our version, we know which sendtor connects to what laser,
    # so we can couple the logic together (avoiding @sendtor_mutex).

    sendtor_name = inst['targetname']
    sendtor = connections.ITEMS[sendtor_name]

    sendtor.enable_cmd += (Output(
        '',
        f'@{sendtor_name}_las_relay_*',
        'Trigger',
        delay=0.01,
    ), )

    for ind, conn in enumerate(list(sendtor.outputs), start=1):
        las_item = conn.to_item
        conn.remove()
        try:
            targ_offset, targ_normal = SENDTOR_TARGETS[las_item.name]
        except KeyError:
            LOGGER.warning('"{}" is not a Sendificator target!', las_item.name)
            continue

        orient = Matrix.from_angle(Angle.from_str(las_item.inst['angles']))

        targ_offset = Vec.from_str(
            las_item.inst['origin']) + targ_offset @ orient
        targ_normal = targ_normal @ orient

        relay_name = f'@{sendtor_name}_las_relay_{ind}'

        relay = vmf.create_ent(
            'logic_relay',
            targetname=relay_name,
            origin=targ_offset,
            angles=targ_normal.to_angle(),
        )
        relay.add_out(
            Output('OnTrigger', '!self', 'RunScriptCode',
                   '::sendtor_source <- self;'),
            Output('OnTrigger', '@sendtor_fire', 'Trigger'),
        )
        if not las_item.inputs:
            # No other inputs, make it on always. PeTI automatically turns
            # it off when inputs are connected, which is annoying.
            las_item.inst.fixup['$start_enabled'] = '1'
            is_on = True
        else:
            is_on = las_item.inst.fixup.bool('$start_enabled')

        relay['StartDisabled'] = not is_on
        las_item.enable_cmd += (Output('', relay_name, 'Enable'), )
        las_item.disable_cmd += (Output('', relay_name, 'Disable'), )
Ejemplo n.º 43
0
    def raycast(
        self,
        pos: _grid_keys,
        direction: Vec,
        collide: Iterable[Block]=frozenset({
            Block.SOLID, Block.EMBED,
            Block.PIT_BOTTOM, Block.PIT_SINGLE,
        }),
    ) -> Vec:
        """Move in a direction until hitting a block of a certain type.

        This returns the position just before hitting a block (which might
        be the start position.)

        The direction vector should be integer numbers (1/0 usually).
        collide is the set of position types to stop at. The default is all
        "solid" walls.

        ValueError is raised if VOID is encountered, or this moves outside the
        map.
        """
        start_pos = pos = Vec(*_conv_key(pos))
        direction = Vec(direction)
        collide_set = frozenset(collide)
        # 50x50x50 diagonal = 86, so that's the largest distance
        # you could possibly move.
        for i in range(90):
            next_pos = pos + direction
            block = super().get(next_pos.as_tuple(), Block.VOID)
            if block is Block.VOID:
                raise ValueError(
                    'Reached VOID at ({}) when '
                    'raycasting from {} with direction {}!'.format(
                        next_pos, start_pos, direction
                    )
                )
            if block in collide_set:
                return pos
            pos = next_pos
        else:
            raise ValueError('Moved too far! (> 90)')
Ejemplo n.º 44
0
def make_color_swatch(parent: tk.Frame,
                      var: tk.StringVar,
                      size=16) -> ttk.Label:
    """Make a single swatch."""
    # Note: tkinter requires RGB as ints, not float!

    color = var.get()
    if color.startswith('#'):
        try:
            r, g, b = int(var[0:2], base=16), int(var[2:4],
                                                  base=16), int(var[4:],
                                                                base=16)
        except ValueError:
            LOGGER.warning('Invalid RGB value: "{}"!', color)
            r = g = b = 128
    else:
        r, g, b = map(int, Vec.from_str(color, 128, 128, 128))

    def open_win(e):
        """Display the color selection window."""
        nonlocal r, g, b
        widget_sfx()
        new_color, tk_color = askcolor(
            color=(r, g, b),
            parent=parent.winfo_toplevel(),
            title=_('Choose a Color'),
        )
        if new_color is not None:
            r, g, b = map(int,
                          new_color)  # Returned as floats, which is wrong.
            var.set('{} {} {}'.format(int(r), int(g), int(b)))
            swatch['image'] = img.color_square(round(Vec(r, g, b)), size)

    swatch = ttk.Label(
        parent,
        relief='raised',
        image=img.color_square(Vec(r, g, b), size),
    )
    utils.bind_leftclick(swatch, open_win)

    return swatch
Ejemplo n.º 45
0
def save_occupiedvoxel(item: Item, vmf: VMF) -> None:
    """Save occupied voxel volumes."""
    for voxel in item.occupy_voxels:
        pos = Vec(voxel.pos) * 128

        if voxel.subpos is not None:
            pos += Vec(voxel.subpos) * 32 - (48, 48, 48)
            p1 = pos - (16.0, 16.0, 16.0)
            p2 = pos + (16.0, 16.0, 16.0)
            norm_dist = 32.0 - 4.0
        else:
            p1 = pos - (64.0, 64.0, 64.0)
            p2 = pos + (64.0, 64.0, 64.0)
            norm_dist = 128.0 - 4.0

        if voxel.normal is not None:
            for axis in ['x', 'y', 'z']:
                val = getattr(voxel.normal, axis)
                if val == +1:
                    p2[axis] -= norm_dist
                elif val == -1:
                    p1[axis] += norm_dist

        if voxel.against is not None:
            against = str(voxel.against).replace('COLLIDE_', '')
        else:
            against = ''

        vmf.create_ent(
            'bee2_editor_occupiedvoxel',
            coll_type=str(voxel.type).replace('COLLIDE_', ''),
            coll_against=against,
        ).solids.append(
            vmf.make_prism(
                p1,
                p2,
                # Use clip for voxels, invisible for normals.
                # Entirely ignored, but makes it easier to use.
                'tools/toolsclip'
                if voxel.normal is None else 'tools/toolsinvisible',
            ).solid)
Ejemplo n.º 46
0
def flag_angles(inst: Entity, flag: Property):
    """Check that a instance is pointed in a direction.

    The value should be either just the angle to check, or a block of
    options:
    - `Angle`: A unit vector (XYZ value) pointing in a direction, or some
        keywords: `+z`, `-y`, `N`/`S`/`E`/`W`, `up`/`down`, `floor`/`ceiling`, or `walls` for any wall side.
    - `From_dir`: The direction the unrotated instance is pointed in.
        This lets the flag check multiple directions
    - `Allow_inverse`: If true, this also returns True if the instance is
        pointed the opposite direction .
    """
    angle = inst['angles', '0 0 0']

    if flag.has_children():
        targ_angle = flag['direction', '0 0 0']
        from_dir = flag['from_dir', '0 0 1']
        if from_dir.casefold() in DIRECTIONS:
            from_dir = Vec(DIRECTIONS[from_dir.casefold()])
        else:
            from_dir = Vec.from_str(from_dir, 0, 0, 1)
        allow_inverse = srctools.conv_bool(flag['allow_inverse', '0'])
    else:
        targ_angle = flag.value
        from_dir = Vec(0, 0, 1)
        allow_inverse = False

    normal = DIRECTIONS.get(targ_angle.casefold(), None)
    if normal is None:
        return False  # If it's not a special angle,
        # so it failed the exact match

    inst_normal = from_dir.rotate_by_str(angle)

    if normal == 'WALL':
        # Special case - it's not on the floor or ceiling
        return not (inst_normal == (0, 0, 1) or inst_normal == (0, 0, -1))
    else:
        return inst_normal == normal or (
            allow_inverse and -inst_normal == normal
        )
def res_make_tag_coop_spawn(vmf: VMF, inst: Entity, res: Property):
    """Create the spawn point for ATLAS, in Aperture Tag.

    This creates an instance with the desired orientation.
    The two parameters 'origin' and 'angles' must be set.
    """
    if vbsp.GAME_MODE != 'COOP':
        return RES_EXHAUSTED

    is_tag = vbsp_options.get(str, 'game_id') == utils.STEAM_IDS['TAG']

    offset = res.vec('origin').rotate_by_str(inst['angles'])
    normal = res.vec('facing', z=1).rotate_by_str(inst['angles'], )

    origin = Vec.from_str(inst['origin'])
    origin += offset
    angles = normal.to_angle()

    if is_tag:
        vmf.create_ent(
            classname='func_instance',
            targetname='paint_gun',
            origin=origin - (0, 0, 16),
            angles=angles,
            # Generated by the BEE2 app.
            file='instances/bee2/tag_coop_gun.vmf',
        )
        # Blocks ATLAS from having a gun
        vmf.create_ent(
            classname='info_target',
            targetname='supress_blue_portalgun_spawn',
            origin=origin,
            angles='0 0 0',
        )
        # Allows info_target to work
        vmf.create_ent(
            classname='env_global',
            targetname='no_spawns',
            globalstate='portalgun_nospawn',
            initialstate=1,
            spawnflags=1,  # Use initial state
            origin=origin,
        )
    vmf.create_ent(
        classname='info_coop_spawn',
        targetname='@coop_spawn_blue',
        ForceGunOnSpawn=int(not is_tag),
        origin=origin,
        angles=angles,
        enabled=1,
        StartingTeam=3,  # ATLAS
    )
    return RES_EXHAUSTED
Ejemplo n.º 48
0
def _conv_key(pos: _grid_keys) -> Tuple[float, float, float]:
    """Convert the key given in [] to a grid-position, as a x,y,z tuple."""
    # TODO: Slices are assumed to be int by typeshed.
    # type: ignore
    if isinstance(pos, slice):
        system, slice_pos = pos.start, pos.stop
        if system == 'world':
            return tuple(world_to_grid(Vec(slice_pos)))
        else:
            return tuple(slice_pos)
    x, y, z = pos
    return x, y, z
Ejemplo n.º 49
0
def gen_faithplates(vmf: VMF) -> None:
    """Place the targets and catapults into the map."""
    # Target positions -> list of triggers wanting to aim there.
    pos_to_trigs: Dict[Union[Tuple[float, float, float], tiling.TileDef],
                       List[Entity]] = collections.defaultdict(list)

    for plate in PLATES.values():
        if isinstance(plate, (AngledPlate, PaintDropper)):
            targ_pos: Union[Tuple[float, float, float], tiling.TileDef]
            if isinstance(plate.target, tiling.TileDef):
                targ_pos = plate.target  # Use the ID directly.
            else:
                targ_pos = plate.target.as_tuple()
            pos_to_trigs[targ_pos].append(plate.trig)

        if isinstance(plate, StraightPlate):
            trigs = [plate.trig, plate.helper_trig]
        else:
            trigs = [plate.trig]

        for trig in trigs:
            trig_origin = trig.get_origin()
            if plate.template is not None:
                trig.solids = template_brush.import_template(
                    vmf,
                    plate.template,
                    trig_origin + plate.trig_offset,
                    Angle.from_str(plate.inst['angles']),
                    force_type=template_brush.TEMP_TYPES.world,
                    add_to_map=False,
                ).world
            elif plate.trig_offset:
                for solid in trig.solids:
                    solid.translate(plate.trig_offset)

    # Now, generate each target needed.
    for pos_or_tile, trigs in pos_to_trigs.items():
        target = vmf.create_ent(
            'info_target',
            angles='0 0 0',
            spawnflags='3',  # Transmit to PVS and always transmit.
        )

        if isinstance(pos_or_tile, tiling.TileDef):
            pos_or_tile.position_bullseye(target)
        else:
            # Static target.
            target['origin'] = Vec(pos_or_tile)

        target.make_unique('faith_target')

        for trig in trigs:
            trig['launchTarget'] = target['targetname']
Ejemplo n.º 50
0
def test_bbox_rotation(
    pitch: float, yaw: float, roll: float,
) -> None:
    """Test the rotation logic against the slow direct approach."""
    ang = Angle(pitch, yaw, roll)
    bb_start = BBox(100, 200, 300, 300, 450, 600, contents=CollideType.ANTLINES, tags='blah')
    # Directly compute, by rotating all the angles,
    points = [
        Vec(x, y, z)
        for x in [100, 300]
        for y in [200, 450]
        for z in [300, 600]
    ]
    result_ang = bb_start @ ang
    result_mat = bb_start @ Matrix.from_angle(ang)
    assert result_ang == result_mat

    bb_min, bb_max = Vec.bbox(
        point @ ang for point in points
    )
    assert_bbox(result_mat, round(bb_min, 0), round(bb_max, 0), CollideType.ANTLINES, {'blah'})
Ejemplo n.º 51
0
 def contains(self, point: Vec) -> bool:
     """Check if the given position is inside the volume."""
     for convex in self.collision:
         for pos, norm in convex:
             off = pos - point
             # This is the actual distance, so we'll use a rather large
             # "epsilon" to catch objects close to the edges.
             if Vec.dot(off, norm) < -0.1:
                 break  # Outside a plane, it doesn't match this convex.
         else:  # Inside all these planes, it's inside.
             return True
     return False  # All failed, not present.
Ejemplo n.º 52
0
def res_create_entity(vmf: VMF, inst: Entity, res: Property):
    """Create an entity.

    * `keys` and `localkeys` defines the new keyvalues used.
    * `origin` and `angles` are local to the instance.
    """

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

    new_ent = vmf.create_ent(
        # Ensure there's these critical values.
        classname='info_null',
        origin='0 0 0',
        angles='0 0 0',
    )

    conditions.set_ent_keys(new_ent, inst, res)

    new_ent['origin'] = Vec.from_str(new_ent['origin']) @ orient + origin
    new_ent['angles'] = Angle.from_str(new_ent['angles']) @ orient
Ejemplo n.º 53
0
def test_minmax():
    """Test Vec.min() and Vec.max()."""
    vec_a = Vec()
    vec_b = Vec()

    for a, b in MINMAX_VALUES:
        max_val = max(a, b)
        min_val = min(a, b)
        for axis in 'xyz':
            vec_a.x = vec_a.y = vec_a.z = 0
            vec_b.x = vec_b.y = vec_b.z = 0

            vec_a[axis] = a
            vec_b[axis] = b
            assert vec_a.min(vec_b) is None, (a, b, axis, min_val)
            assert vec_a[axis] == min_val, (a, b, axis, min_val)

            vec_a[axis] = a
            vec_b[axis] = b
            assert vec_a.max(vec_b) is None, (a, b, axis, max_val)
            assert vec_a[axis] == max_val, (a, b, axis, max_val)
Ejemplo n.º 54
0
def test_bad_from_basis(py_c_vec) -> None:
    """Test invalid arguments to Matrix.from_basis()"""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec
    v = Vec(0, 1, 0)
    with pytest.raises(TypeError):
        Matrix.from_basis()
    with pytest.raises(TypeError):
        Matrix.from_basis(x=v)
    with pytest.raises(TypeError):
        Matrix.from_basis(y=v)
    with pytest.raises(TypeError):
        Matrix.from_basis(z=v)
Ejemplo n.º 55
0
def vactube_gen(vmf: VMF) -> None:
    """Generate the vactubes, after most conditions have run."""
    if not VAC_TRACKS:
        return
    LOGGER.info('Generating vactubes...')
    for start, all_markers in VAC_TRACKS:
        start_normal = Vec(-1, 0, 0).rotate_by_str(start.ent['angles'])

        # First create the start section..
        start_logic = start.ent.copy()
        vbsp.VMF.add_ent(start_logic)

        start_logic['file'] = start.conf['entry',
                                         ('ceiling' if
                                          (start_normal.z > 0) else 'floor' if
                                          (start_normal.z < 0) else 'wall')]

        end = start

        for inst, end in start.follow_path(all_markers):
            join_markers(inst, end, inst is start)

        end_loc = Vec.from_str(end.ent['origin'])
        end_norm = Vec(-1, 0, 0).rotate_by_str(end.ent['angles'])

        # join_markers creates straight parts up-to the marker, but not at it's
        # location - create the last one.
        make_straight(
            end_loc,
            end_norm,
            128,
            end.conf,
        )

        # If the end is placed in goo, don't add logic - it isn't visible, and
        # the object is on a one-way trip anyway.
        if BLOCK_POS['world':end_loc].is_goo and end_norm == (0, 0, -1):
            end_logic = end.ent.copy()
            vbsp.VMF.add_ent(end_logic)
            end_logic['file'] = end.conf['exit']
Ejemplo n.º 56
0
def test_old_rotation(py_c_vec) -> None:
    """Verify that the code matches the results from the earlier Vec.rotate code."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    for pitch in range(0, 360, 15):
        for yaw in range(0, 360, 15):
            for roll in range(0, 360, 15):
                ang = Angle(pitch, yaw, roll)
                mat = Matrix.from_angle(ang)

                # Construct a matrix directly from 3 vector rotations.
                old_mat = Matrix()
                old_mat[0, 0], old_mat[0, 1], old_mat[0, 2] = old_rotate(
                    Vec(x=1), pitch, yaw, roll)
                old_mat[1, 0], old_mat[1, 1], old_mat[1, 2] = old_rotate(
                    Vec(y=1), pitch, yaw, roll)
                old_mat[2, 0], old_mat[2, 1], old_mat[2, 2] = old_rotate(
                    Vec(z=1), pitch, yaw, roll)

                assert_rot(mat, old_mat, ang)
                old = old_rotate(Vec(128, 0, 0), pitch, yaw, roll)

                by_ang = Vec(128, 0, 0) @ ang
                by_mat = Vec(128, 0, 0) @ mat
                assert_vec(by_ang, old.x, old.y, old.z, ang, tol=1e-1)
                assert_vec(by_mat, old.x, old.y, old.z, ang, tol=1e-1)
Ejemplo n.º 57
0
def beam_hole_split(axis: str, min_pos: Vec, max_pos: Vec):
    """Break up floor beams to fit around holes."""

    # Go along the shape. For each point, check if a hole is present,
    # and split at that.
    # Our positions are centered, but we return ones at the ends.

    # Inset in 4 units from each end to not overlap with the frames.
    start_pos = min_pos - Vec.with_axes(axis, 60)
    if HOLES:
        hole_size_large = vbsp_options.get(float, 'glass_hole_size_large') / 2
        hole_size_small = vbsp_options.get(float, 'glass_hole_size_small') / 2

        # Extract normal from the z-axis.
        grid_height = min_pos.z // 128 * 128 + 64
        if grid_height < min_pos.z:
            normal = (0, 0, 1)
        else:
            normal = (0, 0, -1)
        import vbsp
        for pos in min_pos.iter_line(max_pos, 128):
            try:
                hole_type = HOLES[(pos.x, pos.y, grid_height), normal]
            except KeyError:
                continue
            else:
                if hole_type is HoleType.SMALL:
                    size = hole_size_small
                elif hole_type is HoleType.LARGE:
                    size = hole_size_large
                else:
                    raise AssertionError(hole_type)

                yield start_pos, pos - Vec.with_axes(axis, size)
                start_pos = pos + Vec.with_axes(axis, size)

    # Last segment, or all if no holes.
    yield start_pos, max_pos + Vec.with_axes(axis, 60)
Ejemplo n.º 58
0
def join_markers(inst_a, inst_b, is_start=False):
    """Join two marker ents together with corners.

    This returns a list of solids used for the vphysics_motion trigger.
    """
    origin_a = Vec.from_str(inst_a['ent']['origin'])
    origin_b = Vec.from_str(inst_b['ent']['origin'])

    norm_a = Vec(-1, 0, 0).rotate_by_str(inst_a['ent']['angles'])
    norm_b = Vec(-1, 0, 0).rotate_by_str(inst_b['ent']['angles'])

    config = inst_a['conf']

    if norm_a == norm_b:
        # Either straight-line, or s-bend.
        dist = (origin_a - origin_b).mag()

        if origin_a + (norm_a * dist) == origin_b:
            make_straight(
                origin_a,
                norm_a,
                dist,
                config,
                is_start,
            )
        # else: S-bend, we don't do the geometry for this..
        return

    if norm_a == -norm_b:
        # U-shape bend..
        make_ubend(
            origin_a,
            origin_b,
            norm_a,
            config,
            max_size=inst_a['size'],
        )
        return

    try:
        corner_ang, flat_angle = CORNER_ANG[norm_a.as_tuple(), norm_b.as_tuple()]

        if origin_a[flat_angle] != origin_b[flat_angle]:
            # It needs to be flat in this angle!
            raise ValueError
    except ValueError:
        # The tubes need two corners to join together - abort for that.
        return
    else:
        make_bend(
            origin_a,
            origin_b,
            norm_a,
            norm_b,
            corner_ang,
            config,
            max_size=inst_a['size'],
        )
Ejemplo n.º 59
0
 def get_color():
     """Parse out the color."""
     color = var.get()
     if color.startswith('#'):
         try:
             r = int(color[1:3], base=16)
             g = int(color[3:5], base=16)
             b = int(color[5:], base=16)
         except ValueError:
             LOGGER.warning('Invalid RGB value: "{}"!', color)
             r = g = b = 128
     else:
         r, g, b = map(int, Vec.from_str(color, 128, 128, 128))
     return r, g, b
Ejemplo n.º 60
0
def test_hole_spot(origin: Vec, normal: Vec, hole_type: HoleType):
    """Check if the given position is valid for holes.

    We need to check that it's actually placed on glass/grating, and that
    all the parts are the same. Otherwise it'd collide with the borders.
    """

    try:
        center_type = BARRIERS[origin.as_tuple(), normal.as_tuple()]
    except KeyError:
        LOGGER.warning('No center barrier at {}, {}', origin, normal)
        return False

    if hole_type is HoleType.SMALL:
        return True

    u, v = Vec.INV_AXIS[normal.axis()]
    # The corners don't matter, but all 4 neighbours must be there.
    for u_off, v_off in [
        (-128, 0),
        (0, -128),
        (128, 0),
        (0, 128),
    ]:
        pos = origin + Vec.with_axes(u, u_off, v, v_off)
        try:
            off_type = BARRIERS[pos.as_tuple(), normal.as_tuple()]
        except KeyError:
            # No side
            LOGGER.warning('No offset barrier at {}, {}', pos, normal)
            return False
        if off_type is not center_type:
            # Different type.
            LOGGER.warning('Wrong barrier type at {}, {}', pos, normal)
            return False
    return True