Пример #1
0
def res_change_io_type_parse(props: Property):
    """Switch an item to use different inputs or outputs.

    Must be done before priority level -250.
    The contents are the same as that allowed in the input BEE2 block in
    editoritems.
    """
    conf = Config.parse('<ChangeIOType: {:X}>'.format(id(props)), props)

    def change_item(inst: Entity) -> None:
        try:
            item = ITEMS[inst['targetname']]
        except KeyError:
            raise ValueError('No item with name "{}"!'.format(
                inst['targetname']))

        item.config = conf

        # Overwrite these as well.
        item.enable_cmd = conf.enable_cmd
        item.disable_cmd = conf.disable_cmd

        item.sec_enable_cmd = conf.sec_enable_cmd
        item.sec_disable_cmd = conf.sec_disable_cmd

    return change_item
Пример #2
0
def read_configs(all_items: Iterable[editoritems.Item]) -> None:
    """Load our connection configuration from the config files."""
    for item in all_items:
        if item.id.casefold() in ITEM_TYPES:
            raise ValueError('Duplicate item type "{}"'.format(item.id))
        if item.conn_config is None and (item.force_input or item.force_output):
            # The item has no config, but it does force input/output.
            # Generate a blank config so the Item is created.
            ITEM_TYPES[item.id.casefold()] = Config(item.id)
        else:
            ITEM_TYPES[item.id.casefold()] = item.conn_config

    if ITEM_TYPES.get('item_indicator_panel') is None:
        raise ValueError('No I/O for checkmark panel item type!')

    if ITEM_TYPES.get('item_indicator_panel_timer') is None:
        raise ValueError('No I/O for timer panel item type!')
Пример #3
0
def res_make_tag_fizzler_setup(res: Property):
    """We need this to pre-parse the fizzler type."""
    if 'ioconf' in res:
        fizz_conn = Config.parse('<TAG_FIZZER>', res.find_key('ioconf'))
    else:
        fizz_conn = None

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

    return (
        sign_offset,
        fizz_conn,
        res['frame_double'],
        res['frame_single'],
        res['blue_sign', ''],
        res['blue_off_sign', ''],
        res['oran_sign', ''],
        res['oran_off_sign', ''],
    )
Пример #4
0
def res_make_tag_fizzler(vmf: VMF, 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.
    """
    if 'ioconf' in res:
        fizz_conn_conf = Config.parse('<TAG_FIZZER>', res.find_key('ioconf'))
    else:
        fizz_conn_conf = None

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

    inst_frame_double = res['frame_double']
    inst_frame_single = res['frame_single']
    blue_sign_on = res['blue_sign', '']
    blue_sign_off = res['blue_off_sign', '']
    oran_sign_on = res['oran_sign', '']
    oran_sign_off = res['oran_off_sign', '']

    import vbsp
    if options.get(str, 'game_id') != utils.STEAM_IDS['TAG']:
        # Abort - TAG fizzlers shouldn't appear in any other game!
        # So simply remove the fizzler.
        return Entity.remove

    def make_tag_fizz(inst: Entity) -> None:
        """Create the Tag fizzler."""
        fizzler: Optional[Fizzler] = None
        fizzler_item: Optional[Item] = None

        # Look for the fizzler instance we want to replace.
        sign_item = connections.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.
        if fizz_conn_conf is not None:
            fizzler_item.config = fizz_conn_conf
            fizzler_item.enable_cmd = fizz_conn_conf.enable_cmd
            fizzler_item.disable_cmd = fizz_conn_conf.disable_cmd
            fizzler_item.sec_enable_cmd = fizz_conn_conf.sec_enable_cmd
            fizzler_item.sec_disable_cmd = fizz_conn_conf.sec_disable_cmd

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

        # The actual location of the sign - on the wall
        sign_loc = Vec.from_str(inst['origin']) + Vec(0, 0, -64) @ inst_orient
        fizz_norm_axis = round(fizzler.normal(), 3).axis()

        # 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_normal = inst_orient.up()
        loc = Vec.from_str(inst['origin'])

        if disable_other or (blue_enabled and oran_enabled):
            inst['file'] = inst_frame_double
            conditions.ALL_INST.add(inst_frame_double.casefold())
            # On a wall, and pointing vertically
            if abs(inst_normal.z) < 0.01 and abs(inst_orient.left().z) > 0.01:
                # 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) @ inst_orient
                blue_loc = loc + offset
                oran_loc = loc - offset
        else:
            inst['file'] = inst_frame_single
            conditions.ALL_INST.add(inst_frame_single.casefold())
            # 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.

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

            if fizz_norm_axis == 'z':
                # For z-axis, just compare to the center point of the emitters.
                sign_dir = ((s.x + l.x) / 2,
                            (s.y + l.y) / 2, 0) - sign_floor_loc
            else:
                # For the other two, we compare to the line,
                # or compare to the closest side (in line with the fizz)

                if fizz_norm_axis == 'x':  #  Extends in Y direction
                    other_axis = 'y'
                    side_min = s.y
                    side_max = l.y
                    normal = s.x
                else:  # Extends in X direction
                    other_axis = 'x'
                    side_min = s.x
                    side_max = l.x
                    normal = s.y

                # Right in line with the fizzler. Point at the closest emitter.
                if abs(sign_floor_loc[other_axis] - normal) < 32:
                    # Compare to the closest side.
                    sign_dir = min([
                        sign_floor_loc - Vec.with_axes(
                            fizz_norm_axis,
                            side_min,
                            other_axis,
                            normal,
                        ), sign_floor_loc - Vec.with_axes(
                            fizz_norm_axis,
                            side_max,
                            other_axis,
                            normal,
                        )
                    ],
                                   key=Vec.mag)
                else:
                    # Align just based on whether we're in front or behind.
                    sign_dir = Vec.with_axes(
                        fizz_norm_axis,
                        normal - sign_floor_loc[fizz_norm_axis]).norm()

            sign_yaw = 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_yaw = (sign_yaw + 45) // 90 * 90

            # Rotate to fit the instances - south is down
            sign_yaw = int(sign_yaw - 90) % 360

            if inst_normal.z > 0:
                sign_angle = '0 {} 0'.format(sign_yaw)
            elif inst_normal.z < 0:
                # Flip upside-down for ceilings
                sign_angle = '0 {} 180'.format(sign_yaw)
            else:
                raise AssertionError('Cannot be zero here!')
        else:
            # On a wall, face upright
            sign_angle = conditions.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:
            conditions.add_inst(
                vmf,
                file=blue_sign,
                targetname=inst['targetname'],
                angles=sign_angle,
                origin=blue_loc,
            )

        if oran_sign:
            conditions.add_inst(
                vmf,
                file=oran_sign,
                targetname=inst['targetname'],
                angles=sign_angle,
                origin=oran_loc,
            )

        # Now modify the fizzler...

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

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

        bbox_min, bbox_max = fizzler.emitters[0]

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

        # Figure out what the sides will set values to...
        pos_blue = pos_oran = False
        neg_blue = 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')
            output = 'OnStartTouch'

        pos_trig['origin'] = neg_trig['origin'] = fizzler.base_inst['origin']
        pos_trig['spawnflags'] = neg_trig['spawnflags'] = '1'  # Clients Only

        pos_trig['targetname'] = conditions.local_name(fizzler.base_inst,
                                                       'trig_pos')
        neg_trig['targetname'] = conditions.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',
                    neg_blue,
                ))
            pos_trig.outputs.append(
                Output(
                    output,
                    '@BlueIsEnabled',
                    'SetValue',
                    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'] = conditions.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, )

    return make_tag_fizz
Пример #5
0
    def _modify_editoritems(
        self,
        props: Property,
        editor: list[EditorItem],
        pak_id: str,
        source: str,
        is_extra: bool,
    ) -> list[EditorItem]:
        """Modify either the base or extra editoritems block."""
        # We can share a lot of the data, if it isn't changed and we take
        # care to copy modified parts.
        editor = list(map(copy.copy, editor))

        # Create a list of subtypes in the file, in order to edit.
        subtype_lookup = [(item, i, subtype) for item in editor
                          for i, subtype in enumerate(item.subtypes)]

        # Implement overriding palette items
        for item in props.find_children('Palette'):
            try:
                pal_icon = FSPath(item['icon'])
            except LookupError:
                pal_icon = None
            pal_name = item['pal_name', None]  # Name for the palette icon
            try:
                bee2_icon = img.Handle.parse(
                    item.find_key('BEE2'),
                    pak_id,
                    64,
                    64,
                    subfolder='items',
                )
            except LookupError:
                bee2_icon = None

            if item.name == 'all':
                if is_extra:
                    raise Exception('Cannot specify "all" for hidden '
                                    f'editoritems blocks in {source}!')
                if pal_icon is not None:
                    self.all_icon = pal_icon
                    # If a previous BEE icon was present, remove so we use the VTF.
                    self.icons.pop('all', None)
                if pal_name is not None:
                    self.all_name = pal_name
                if bee2_icon is not None:
                    self.icons['all'] = bee2_icon
                continue

            try:
                subtype_ind = int(item.name)
                subtype_item, subtype_ind, subtype = subtype_lookup[
                    subtype_ind]
            except (IndexError, ValueError, TypeError):
                raise Exception(f'Invalid index "{item.name}" when modifying '
                                f'editoritems for {source}')
            subtype_item.subtypes = subtype_item.subtypes.copy()
            subtype_item.subtypes[subtype_ind] = subtype = copy.deepcopy(
                subtype)

            # Overriding model data.
            if 'models' in item or 'model' in item:
                subtype.models = []
                for prop in item:
                    if prop.name in ('models', 'model'):
                        if prop.has_children():
                            subtype.models.extend(
                                [FSPath(subprop.value) for subprop in prop])
                        else:
                            subtype.models.append(FSPath(prop.value))

            if item['name', None]:
                subtype.name = item['name']  # Name for the subtype

            if bee2_icon:
                if is_extra:
                    raise ValueError('Cannot specify BEE2 icons for hidden '
                                     f'editoritems blocks in {source}!')
                self.icons[item.name] = bee2_icon
            elif pal_icon is not None:
                # If a previous BEE icon was present, remove so we use the VTF.
                self.icons.pop(item.name, None)

            if pal_name is not None:
                subtype.pal_name = pal_name
            if pal_icon is not None:
                subtype.pal_icon = pal_icon

        if 'Instances' in props:
            if len(editor) != 1:
                raise ValueError('Cannot specify instances for multiple '
                                 f'editoritems blocks in {source}!')
            editor[0].instances = editor[0].instances.copy()
            editor[0].cust_instances = editor[0].cust_instances.copy()

        for inst in props.find_children('Instances'):
            if inst.has_children():
                inst_data = InstCount(
                    FSPath(inst['name']),
                    inst.int('entitycount'),
                    inst.int('brushcount'),
                    inst.int('brushsidecount'),
                )
            else:  # Allow just specifying the file.
                inst_data = InstCount(FSPath(inst.value), 0, 0, 0)

            if inst.real_name.isdecimal():  # Regular numeric
                try:
                    ind = int(inst.real_name)
                except IndexError:
                    # This would likely mean there's an extra definition or
                    # something.
                    raise ValueError(f'Invalid index {inst.real_name} for '
                                     f'instances in {source}') from None
                editor[0].set_inst(ind, inst_data)
            else:  # BEE2 named instance
                inst_name = inst.name
                if inst_name.startswith('bee2_'):
                    inst_name = inst_name[5:]
                editor[0].cust_instances[inst_name] = inst_data.inst

        # Override IO commands.
        try:
            io_props = props.find_key('IOConf')
        except LookupError:
            pass
        else:
            if len(editor) != 1:
                raise ValueError('Cannot specify I/O for multiple '
                                 f'editoritems blocks in {source}!')
            force = io_props['force', '']
            editor[0].conn_config = ConnConfig.parse(editor[0].id, io_props)
            editor[0].force_input = 'in' in force
            editor[0].force_output = 'out' in force

        return editor
Пример #6
0
def res_change_io_type_parse(props: Property):
    """Pre-parse all item types into an anonymous block."""
    return Config.parse('<ChangeIOType: {:X}>'.format(id(props)), props)