Exemple #1
0
def save_load_signage(props: Property = None) -> Optional[Property]:
    """Save or load the signage info."""
    if props is None:  # Save to properties.
        props = Property('Signage', [])
        for timer, slot in SLOTS_SELECTED.items():
            props.append(
                Property(
                    str(timer),
                    '' if slot.contents is None else slot.contents.id,
                ))
        return props
    else:  # Load from provided properties.
        for child in props:
            try:
                slot = SLOTS_SELECTED[int(child.name)]
            except (ValueError, TypeError):
                LOGGER.warning('Non-numeric timer value "{}"!', child.name)
                continue
            except KeyError:
                LOGGER.warning('Invalid timer value {}!', child.name)
                continue

            if child.value:
                try:
                    slot.contents = Signage.by_id(child.value)
                except KeyError:
                    LOGGER.warning('No signage with id "{}"!', child.value)
            else:
                slot.contents = None
        return None
Exemple #2
0
def save_handler() -> Property:
    """Save the compiler pane to the palette.

    Note: We specifically do not save/load the following:
        - packfile dumping
        - compile counts
    This is because these are more system-dependent than map dependent.
    """
    corr_prop = Property('corridor', [])
    props = Property('', [
        Property('sshot_type', chosen_thumb.get()),
        Property('sshot_cleanup', str(cleanup_screenshot.get())),
        Property('spawn_elev', str(start_in_elev.get())),
        Property('player_model', PLAYER_MODELS_REV[player_model_var.get()]),
        Property('voiceline_priority', str(VOICE_PRIORITY_VAR.get())),
        corr_prop,
    ])
    for group, win in CORRIDOR.items():
        corr_prop[group] = win.chosen_id or '<NONE>'

    # Embed the screenshot in so we can load it later.
    if chosen_thumb.get() == 'CUST':
        # encodebytes() splits it into multiple lines, which we write
        # in individual blocks to prevent having a massively long line
        # in the file.
        with open(SCREENSHOT_LOC, 'rb') as f:
            screenshot_data = base64.encodebytes(f.read())
        props.append(
            Property('sshot_data', [
                Property('b64', data)
                for data in screenshot_data.decode('ascii').splitlines()
            ]))

    return props
Exemple #3
0
def apply_replacements(conf: Property) -> Property:
    """Apply a set of replacement values to a config file, returning a new copy.

    The replacements are found in a 'Replacements' block in the property.
    These replace %values% starting and ending with percents. A double-percent
    allows literal percents. Unassigned values are an error.
    """
    replace = {}
    new_conf = Property(conf.real_name, [])

    # Strip the replacement blocks from the config, and save the values.
    for prop in conf:
        if prop.name == 'replacements':
            for rep_prop in prop:
                replace[rep_prop.name.strip('%')] = rep_prop.value
        else:
            new_conf.append(prop)

    def rep_func(match: Match):
        """Does the replacement."""
        var = match.group(1)
        if not var:  # %% becomes %.
            return '%'
        try:
            return replace[var.casefold()]
        except KeyError:
            raise ValueError('Unresolved variable: {!r}\n{}'.format(
                var, replace))

    for prop in new_conf.iter_tree(blocks=True):
        prop.name = RE_PERCENT_VAR.sub(rep_func, prop.real_name)
        if not prop.has_children():
            prop.value = RE_PERCENT_VAR.sub(rep_func, prop.value)

    return new_conf
Exemple #4
0
    def save(self, ignore_readonly=False):
        """Save the palette file into the specified location.

        If ignore_readonly is true, this will ignore the `prevent_overwrite`
        property of the palette (allowing resaving those properties over old
        versions). Otherwise those palettes always create a new file.
        """
        LOGGER.info('Saving "{}"!', self.name)
        props = Property(None, [
            Property('Name', self.name),
            Property('TransName', self.trans_name),
            Property('ReadOnly', srctools.bool_as_int(self.prevent_overwrite)),
            Property('Items', [
                Property(item_id, str(subitem))
                for item_id, subitem in self.pos
            ])
        ])
        # If default, don't include in the palette file.
        # Remove the translated name, in case it's not going to write
        # properly to the file.
        if self.trans_name:
            props['Name'] = ''
        else:
            del props['TransName']

        if not self.prevent_overwrite:
            del props['ReadOnly']

        if self.settings is not None:
            self.settings.name = 'Settings'
            props.append(self.settings.copy())

        # We need to write a new file, determine a valid path.
        # Use a hash to ensure it's a valid path (without '-' if negative)
        # If a conflict occurs, add ' ' and hash again to get a different
        # value.
        if self.filename is None or (self.prevent_overwrite
                                     and not ignore_readonly):
            hash_src = self.name
            while True:
                hash_filename = str(abs(hash(hash_src))) + PAL_EXT
                if os.path.isfile(hash_filename):
                    # Add a random character to iterate the hash.
                    hash_src += chr(random.randrange(0x10ffff))
                else:
                    file = open(os.path.join(PAL_DIR, hash_filename),
                                'w',
                                encoding='utf8')
                    self.filename = os.path.join(PAL_DIR, hash_filename)
                    break
        else:
            file = open(os.path.join(PAL_DIR, self.filename),
                        'w',
                        encoding='utf8')
        with file:
            for line in props.export():
                file.write(line)
Exemple #5
0
def save_signage() -> Property:
    """Save the signage info to settings or a palette."""
    props = Property('Signage', [])
    for timer, slot in SLOTS_SELECTED.items():
        props.append(Property(
            str(timer),
            '' if slot.contents is None
            else slot.contents.id,
        ))
    return props
Exemple #6
0
def get_curr_settings() -> Property:
    """Return a property tree defining the current options."""
    props = Property('', [])

    for opt_id, opt_func in option_handler.items():
        opt_prop = opt_func()  # type: Property
        opt_prop.name = opt_id.title()
        props.append(opt_prop)

    return props
Exemple #7
0
def get_curr_settings() -> Property:
    """Return a property tree defining the current options."""
    props = Property('', [])

    for opt_id, opt_func in option_handler.items():
        opt_prop = opt_func()  # type: Property
        opt_prop.name = opt_id.title()
        props.append(opt_prop)

    return props
Exemple #8
0
    def save(self, ignore_readonly=False):
        """Save the palette file into the specified location.

        If ignore_readonly is true, this will ignore the `prevent_overwrite`
        property of the palette (allowing resaving those properties over old
        versions). Otherwise those palettes always create a new file.
        """
        LOGGER.info('Saving "{}"!', self.name)
        props = Property(None, [
            Property('Name', self.name),
            Property('TransName', self.trans_name),
            Property('ReadOnly', srctools.bool_as_int(self.prevent_overwrite)),
            Property('Items', [
                Property(item_id, str(subitem))
                for item_id, subitem in self.pos
            ])
        ])
        # If default, don't include in the palette file.
        # Remove the translated name, in case it's not going to write
        # properly to the file.
        if self.trans_name:
            props['Name'] = ''
        else:
            del props['TransName']

        if not self.prevent_overwrite:
            del props['ReadOnly']

        if self.settings is not None:
            self.settings.name = 'Settings'
            props.append(self.settings.copy())

        # We need to write a new file, determine a valid path.
        # Use a hash to ensure it's a valid path (without '-' if negative)
        # If a conflict occurs, add ' ' and hash again to get a different
        # value.
        if self.filename is None or (self.prevent_overwrite and not ignore_readonly):
            hash_src = self.name
            while True:
                hash_filename = str(abs(hash(hash_src))) + PAL_EXT
                if os.path.isfile(hash_filename):
                    # Add a random character to iterate the hash.
                    hash_src += chr(random.randrange(0x10ffff))
                else:
                    file = open(os.path.join(PAL_DIR, hash_filename), 'w', encoding='utf8')
                    self.filename = os.path.join(PAL_DIR, hash_filename)
                    break
        else:
            file = open(os.path.join(PAL_DIR, self.filename), 'w', encoding='utf8')
        with file:
            for line in props.export():
                file.write(line)
Exemple #9
0
    def export(exp_data: ExportData):
        """Export the selected music."""
        selected = exp_data.selected  # type: Dict[MusicChannel, Optional[Music]]

        base_music = selected[MusicChannel.BASE]

        vbsp_config = exp_data.vbsp_conf

        if base_music is not None:
            vbsp_config += base_music.config.copy()

        music_conf = Property('MusicScript', [])
        vbsp_config.append(music_conf)
        to_pack = set()

        for channel, music in selected.items():
            if music is None:
                continue

            sounds = music.sound[channel]
            if len(sounds) == 1:
                music_conf.append(Property(channel.value, sounds[0]))
            else:
                music_conf.append(
                    Property(channel.value,
                             [Property('snd', snd) for snd in sounds]))

            to_pack.update(music.packfiles)

        if base_music is not None:
            vbsp_config.set_key(
                ('Options', 'music_looplen'),
                str(base_music.len),
            )

            vbsp_config.set_key(
                ('Options', 'music_sync_tbeam'),
                srctools.bool_as_int(base_music.has_synced_tbeam),
            )
            vbsp_config.set_key(
                ('Options', 'music_instance'),
                base_music.inst or '',
            )

        # If we need to pack, add the files to be unconditionally
        # packed.
        if to_pack:
            vbsp_config.set_key(
                ('PackTriggers', 'Forced'),
                [Property('File', file) for file in to_pack],
            )
Exemple #10
0
    def export(exp_data: ExportData) -> None:
        """Export the selected signage to the config, and produce the legend."""
        # Timer value -> sign ID.
        sel_ids: list[tuple[str, str]] = exp_data.selected

        # Special case, arrow is never selectable.
        sel_ids.append(('arrow', 'SIGN_ARROW'))

        sel_icons: dict[int, ImgHandle] = {}

        conf = Property('Signage', [])

        for tim_id, sign_id in sel_ids:
            try:
                sign = Signage.by_id(sign_id)
            except KeyError:
                LOGGER.warning('Signage "{}" does not exist!', sign_id)
                continue
            prop_block = Property(str(tim_id), [])

            sty_sign = sign._serialise(prop_block, exp_data.selected_style)

            for sub_name, sub_id in [
                ('primary', sign.prim_id),
                ('secondary', sign.sec_id),
            ]:
                if sub_id:
                    try:
                        sub_sign = Signage.by_id(sub_id)
                    except KeyError:
                        LOGGER.warning(
                            'Signage "{}"\'s {} "{}" '
                            'does not exist!', sign_id, sub_name, sub_id)
                    else:
                        sub_block = Property(sub_name, [])
                        sub_sign._serialise(sub_block, exp_data.selected_style)
                        if sub_block:
                            prop_block.append(sub_block)

            if prop_block:
                conf.append(prop_block)

            # Valid timer number, store to be placed on the texture.
            if tim_id.isdigit() and sty_sign is not None:
                sel_icons[int(tim_id)] = sty_sign.icon

        exp_data.vbsp_conf.append(conf)
        exp_data.resources[SIGN_LOC] = build_texture(
            exp_data.game, exp_data.selected_style,
            sel_icons,
        )
Exemple #11
0
    def export(exp_data: ExportData) -> None:
        """Export the selected signage to the config."""
        # Timer value -> sign ID.
        sel_ids: List[Tuple[str, str]] = exp_data.selected

        # Special case, arrow is never selectable.
        sel_ids.append(('arrow', 'SIGN_ARROW'))

        conf = Property('Signage', [])

        for tim_id, sign_id in sel_ids:
            try:
                sign = Signage.by_id(sign_id)
            except KeyError:
                LOGGER.warning('Signage "{}" does not exist!', sign_id)
                continue
            prop_block = Property(str(tim_id), [])

            sign._serialise(prop_block, exp_data.selected_style)

            for sub_name, sub_id in [
                ('primary', sign.prim_id),
                ('secondary', sign.sec_id),
            ]:
                if sub_id:
                    try:
                        sub_sign = Signage.by_id(sub_id)
                    except KeyError:
                        LOGGER.warning(
                            'Signage "{}"\'s {} "{}" '
                            'does not exist!', sign_id, sub_name, sub_id)
                    else:
                        sub_block = Property(sub_name, [])
                        sub_sign._serialise(sub_block, exp_data.selected_style)
                        if sub_block:
                            prop_block.append(sub_block)

            if prop_block:
                conf.append(prop_block)

        exp_data.vbsp_conf.append(conf)
Exemple #12
0
    def export(exp_data: ExportData) -> None:
        """Export all the packlists."""

        pack_block = Property('PackList', [])

        for pack in PackList.all():  # type: PackList
            # Build a
            # "Pack_id"
            # {
            # "File" "filename"
            # "File" "filename"
            # }
            # block for each packlist
            files = [Property('File', file) for file in pack.files]
            pack_block.append(Property(
                pack.id,
                files,
            ))

        LOGGER.info('Writing packing list!')
        with open(exp_data.game.abs_path('bin/bee2/pack_list.cfg'),
                  'w') as pack_file:
            for line in pack_block.export():
                pack_file.write(line)
Exemple #13
0
def save_itemvar() -> Property:
    """Save item variables into the palette."""
    prop = Property('', [])
    for group in CONFIG_ORDER:
        conf = Property(group.id, [])
        for widget in group.widgets:
            if widget.has_values:
                conf.append(Property(widget.id, widget.values.get()))
        for widget in group.multi_widgets:
            conf.append(
                Property(widget.id, [
                    Property(str(tim_val), var.get())
                    for tim_val, var in widget.values
                ]))
        prop.append(conf)
    return prop
Exemple #14
0
    def _parse_block(tok: Tokenizer, name: str) -> Property:
        """Parse a block into a block of properties."""
        prop = Property(name, [])

        for token, param_name in tok:
            # End of our block
            if token is Tok.BRACE_CLOSE:
                return prop
            elif token is Tok.NEWLINE:
                continue
            elif token is not Tok.STRING:
                raise tok.error(token)

            token, param_value = tok()

            if token is Tok.STRING:
                # We have the value.
                pass
            elif token is Tok.NEWLINE:
                # Name by itself: '%compilenodraw' etc...
                # We need to check there's a newline after that - for subblocks.
                token, ignored = tok()
                while token is Tok.NEWLINE:
                    token, ignored = tok()

                if token is Tok.BRACE_OPEN:
                    prop.append(Material._parse_block(tok, param_name))
                    continue
                elif token is Tok.NEWLINE:
                    pass
                elif token is Tok.BRACE_CLOSE:
                    # End of us after single name.
                    prop.append(Property(param_name, ''))
                    break
                else:
                    raise tok.error(token)
            else:
                raise tok.error(token)
            prop.append(Property(param_name, param_value))

        raise tok.error('EOF without closed block!')
Exemple #15
0
 def _serialise(self, parent: Property, style: Style) -> None:
     """Write this sign's data for the style to the provided property."""
     for potential_style in style.bases:
         try:
             data = self.styles[potential_style.id.upper()]
             break
         except KeyError:
             pass
     else:
         LOGGER.warning(
             'No valid "{}" style for "{}" signage!',
             style.id,
             self.id,
         )
         try:
             data = self.styles['BEE2_CLEAN']
         except KeyError:
             return
     parent.append(Property('world', data.world))
     parent.append(Property('overlay', data.overlay))
     parent.append(Property('type', data.type))
Exemple #16
0
def save_load_itemvar(prop: Property = None) -> Optional[Property]:
    """Save or load item variables into the palette."""
    if prop is None:
        prop = Property('', [])
        for group in CONFIG_ORDER:
            conf = Property(group.id, [])
            for widget in group.widgets:  # ItemVariant special case.
                if widget.values is not None:
                    conf.append(Property(widget.id, widget.values.get()))
            for widget in group.multi_widgets:
                conf.append(
                    Property(widget.id, [
                        Property(str(tim_val), var.get())
                        for tim_val, var in widget.values
                    ]))
            prop.append(conf)
        return prop
    else:
        # Loading.
        for group in CONFIG_ORDER:
            conf = prop.find_key(group.id, [])
            for widget in group.widgets:
                if widget.values is not None:  # ItemVariants
                    try:
                        widget.values.set(conf[widget.id])
                    except LookupError:
                        pass

            for widget in group.multi_widgets:
                time_conf = conf.find_key(widget.id, [])
                for tim_val, var in widget.values:
                    try:
                        var.set(time_conf[str(tim_val)])
                    except LookupError:
                        pass
        return None
Exemple #17
0
def save_load_itemvar(prop: Property=None) -> Optional[Property]:
    """Save or load item variables into the palette."""
    if prop is None:
        prop = Property('', [])
        for group in CONFIG_ORDER:
            conf = Property(group.id, [])
            for widget in group.widgets:  # ItemVariant special case.
                if widget.values is not None:
                    conf.append(Property(widget.id, widget.values.get()))
            for widget in group.multi_widgets:
                conf.append(Property(widget.id, [
                    Property(str(tim_val), var.get())
                    for tim_val, var in
                    widget.values
                ]))
            prop.append(conf)
        return prop
    else:
        # Loading.
        for group in CONFIG_ORDER:
            conf = prop.find_key(group.id, [])
            for widget in group.widgets:
                if widget.values is not None:  # ItemVariants
                    try:
                        widget.values.set(conf[widget.id])
                    except LookupError:
                        pass

            for widget in group.multi_widgets:
                time_conf = conf.find_key(widget.id, [])
                for tim_val, var in widget.values:
                    try:
                        var.set(time_conf[str(tim_val)])
                    except LookupError:
                        pass
        return None
Exemple #18
0
class mat:
    ''' A source material file. This can be used on models or on brushes.'''
    def __init__(self,pth,diffuse,mat_type='LightmappedGeneric',normal=None,bump=None,glossy=None,tags=[]):
        if (not mat_type in ['LightmappedGeneric','VertexLitGeneric','UnlitGeneric']):
            raise Exception('mat_type should be LightmapperGeneric, VertexLitGeneric, or UnlitGeneric.')
        self.mat_type = mat_type
        
        for a,x in enumerate([diffuse,normal,bump,glossy]):
            if (not isinstance(x,tex) and not x is None):
                raise Exception(['diffuse','normal','bump','glossy'][a]+' must be of type tex.')
        self.path = pth
        self.vmt = Property(mat_type,[])
        self.__mats = [x for x in [diffuse,normal,bump,glossy] if x]
        ''' do image processing here '''
        diffuse = get_path_relative(pth,diffuse.path)
        self.vmt.append(Property('$basetexture',diffuse))
        if normal:
            normal = get_path_relative(pth,normal.path)
            self.vmt.append(Property('$normalmap',normal))
        if bump:
            bump = get_path_relative(pth,bump.path)
            self.vmt.append(Property('$bumpmap',bump))
        if glossy:
            glossy = get_path_relative(pth,glossy.path)
            self.vmt.append('$envmap','1')
            self.vmt.append(Property('$envmapmask',glossy))
        if len(tags):
            self.vmt.append(Property('%keywords',",".join(tags)))
    def compile(self):
        ''' output the material to a vmt '''
        for a,x in enumerate(self.__mats):
            y = ['diffuse','normal','bump','glossy'][a]
            if (os.path.isfile(x.__path)):
                print(f'VTF texture for {y} already exists. Copying to material location.')
                #copyfile(x.__path,
                return
                print('This failed horribly. Yikes.')
            if not os.path.isfile(x.__img):
                raise Exception(f'Unable to find texture file for {y}.')
Exemple #19
0
    def build_instance_data(editoritems: Property):
        """Build a property tree listing all of the instances for each item.
        as well as another listing the input and output commands.
        VBSP uses this to reduce duplication in VBSP_config files.

        This additionally strips custom instance definitions from the original
        list.
        """
        instance_locs = Property("AllInstances", [])
        cust_inst = Property("CustInstances", [])
        commands = Property("Connections", [])
        item_classes = Property("ItemClasses", [])
        root_block = Property(None, [
            instance_locs,
            item_classes,
            cust_inst,
            commands,
        ])

        for item in editoritems.find_all("Item"):
            instance_block = Property(item['Type'], [])
            instance_locs.append(instance_block)

            comm_block = Property(item['Type'], [])

            for inst_block in item.find_all("Exporting", "instances"):
                for inst in inst_block.value[:]:  # type: Property
                    if inst.name.isdigit():
                        # Direct Portal 2 value
                        instance_block.append(
                            Property('Instance', inst['Name'])
                        )
                    else:
                        # It's a custom definition, remove from editoritems
                        inst_block.value.remove(inst)

                        # Allow the name to start with 'bee2_' also to match
                        # the <> definitions - it's ignored though.
                        name = inst.name
                        if name[:5] == 'bee2_':
                            name = name[5:]

                        cust_inst.set_key(
                            (item['type'], name),
                            # Allow using either the normal block format,
                            # or just providing the file - we don't use the
                            # other values.
                            inst['name'] if inst.has_children() else inst.value,
                        )

            # Look in the Inputs and Outputs blocks to find the io definitions.
            # Copy them to property names like 'Input_Activate'.
            for io_type in ('Inputs', 'Outputs'):
                for block in item.find_all('Exporting', io_type, CONN_NORM):
                    for io_prop in block:
                        comm_block[
                            io_type[:-1] + '_' + io_prop.real_name
                        ] = io_prop.value

            # The funnel item type is special, having the additional input type.
            # Handle that specially.
            if item['type'].casefold() == 'item_tbeam':
                for block in item.find_all('Exporting', 'Inputs', CONN_FUNNEL):
                    for io_prop in block:
                        comm_block['TBeam_' + io_prop.real_name] = io_prop.value

            # Fizzlers don't work correctly with outputs. This is a signal to
            # conditions.fizzler, but it must be removed in editoritems.
            if item['ItemClass', ''].casefold() == 'itembarrierhazard':
                for block in item.find_all('Exporting', 'Outputs'):
                    if CONN_NORM in block:
                        del block[CONN_NORM]

            # Record the itemClass for each item type.
            item_classes[item['type']] = item['ItemClass', 'ItemBase']

            # Only add the block if the item actually has IO.
            if comm_block.value:
                commands.append(comm_block)

        return root_block.export()
Exemple #20
0
    def build_instance_data(editoritems: Property):
        """Build a property tree listing all of the instances for each item.
        as well as another listing the input and output commands.
        VBSP uses this to reduce duplication in VBSP_config files.

        This additionally strips custom instance definitions from the original
        list.
        """
        instance_locs = Property("AllInstances", [])
        cust_inst = Property("CustInstances", [])
        commands = Property("Connections", [])
        item_classes = Property("ItemClasses", [])
        root_block = Property(None, [instance_locs, item_classes, cust_inst, commands])

        for item in editoritems.find_all("Item"):
            instance_block = Property(item["Type"], [])
            instance_locs.append(instance_block)

            comm_block = Property(item["Type"], [])

            for inst_block in item.find_all("Exporting", "instances"):
                for inst in inst_block.value[:]:  # type: Property
                    if inst.name.isdigit():
                        # Direct Portal 2 value
                        instance_block.append(Property("Instance", inst["Name"]))
                    else:
                        # It's a custom definition, remove from editoritems
                        inst_block.value.remove(inst)

                        # Allow the name to start with 'bee2_' also to match
                        # the <> definitions - it's ignored though.
                        name = inst.name
                        if name[:5] == "bee2_":
                            name = name[5:]

                        cust_inst.set_key(
                            (item["type"], name),
                            # Allow using either the normal block format,
                            # or just providing the file - we don't use the
                            # other values.
                            inst["name"] if inst.has_children() else inst.value,
                        )

            # Look in the Inputs and Outputs blocks to find the io definitions.
            # Copy them to property names like 'Input_Activate'.
            for io_type in ("Inputs", "Outputs"):
                for block in item.find_all("Exporting", io_type, CONN_NORM):
                    for io_prop in block:
                        comm_block[io_type[:-1] + "_" + io_prop.real_name] = io_prop.value

            # The funnel item type is special, having the additional input type.
            # Handle that specially.
            if item["type"] == "item_tbeam":
                for block in item.find_all("Exporting", "Inputs", CONN_FUNNEL):
                    for io_prop in block:
                        comm_block["TBEAM_" + io_prop.real_name] = io_prop.value

            # Fizzlers don't work correctly with outputs. This is a signal to
            # conditions.fizzler, but it must be removed in editoritems.
            if item["ItemClass", ""].casefold() == "itembarrierhazard":
                for block in item.find_all("Exporting", "Outputs"):
                    if CONN_NORM in block:
                        del block[CONN_NORM]

            # Record the itemClass for each item type.
            item_classes[item["type"]] = item["ItemClass", "ItemBase"]

            # Only add the block if the item actually has IO.
            if comm_block.value:
                commands.append(comm_block)

        return root_block.export()