Exemple #1
0
    def strip_quote_data(prop: Property, _depth=0) -> Property:
        """Strip unused property blocks from the config files.

        This removes data like the captions which the compiler doesn't need.
        The returned property tree is a deep-copy of the original.
        """
        children = []
        for sub_prop in prop:
            # Make sure it's in the right nesting depth - flags might
            # have arbitrary props in lower depths..
            if _depth == 3:  # 'Line' blocks
                if sub_prop.name == 'trans':
                    continue
                elif sub_prop.name == 'name' and 'id' in prop:
                    continue  # The name isn't needed if an ID is available
            elif _depth == 2 and sub_prop.name == 'name':
                # In the "quote" section, the name isn't used in the compiler.
                continue

            if sub_prop.has_children():
                children.append(
                    QuotePack.strip_quote_data(sub_prop, _depth + 1))
            else:
                children.append(Property(sub_prop.real_name, sub_prop.value))
        return Property(prop.real_name, children)
Exemple #2
0
def add_meta(func, priority: Decimal | int, only_once=True):
    """Add a metacondition, which executes a function at a priority level.

    Used to allow users to allow adding conditions before or after a
    transformation like the adding of quotes.
    """
    dec_priority = Decimal(priority)
    # This adds a condition result like "func" (with quotes), which cannot
    # be entered into property files.
    # The qualified name will be unique across modules.
    name = '"' + func.__qualname__ + '"'
    LOGGER.debug(
        "Adding metacondition ({}) with priority {!s}!",
        name,
        dec_priority,
    )

    # We don't care about setup functions for this.
    RESULT_LOOKUP[name] = wrapper = CondCall(func, _get_cond_group(func))

    cond = Condition(
        results=[Property(name, '')],
        priority=dec_priority,
        source='MetaCondition {}'.format(name)
    )

    if only_once:
        cond.results.append(
            Property('endCondition', '')
        )
    conditions.append(cond)
    ALL_META.append((name, dec_priority, wrapper))
Exemple #3
0
def add_meta(func, priority, only_once=True):
    """Add a metacondtion, which executes a function at a priority level.

    Used to allow users to allow adding conditions before or after a
    transformation like the adding of quotes.
    """
    # This adds a condition result like "func" (with quotes), which cannot
    # be entered into property files.
    # The qualname will be unique across modules.
    name = '"' + func.__qualname__ + '"'
    LOGGER.debug(
        "Adding metacondition ({}) with priority {!s}!",
        name,
        priority,
    )

    RESULT_LOOKUP[name] = annotation_caller(func, srctools.VMF, Entity,
                                            Property)

    cond = Condition(results=[Property(name, '')],
                     priority=priority,
                     source='MetaCondition {}'.format(name))

    if only_once:
        cond.results.append(Property('endCondition', ''))
    conditions.append(cond)
    ALL_META.append((name, priority, func))
Exemple #4
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 #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 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 #7
0
    def export(exp_data: ExportData) -> None:
        """Export style var selections into the config.

        The .selected attribute is a dict mapping ids to the boolean value.
        """
        # Add the StyleVars block, containing each style_var.
        exp_data.vbsp_conf.append(
            Property('StyleVars', [
                Property(key, bool_as_int(val))
                for key, val in exp_data.selected.items()
            ]))
Exemple #8
0
def gen_sound_manifest(additional, excludes):
    """Generate a new game_sounds_manifest.txt file.

    This includes all the current scripts defined, plus any custom ones.
    Excludes is a list of scripts to remove from the listing - this allows
    overriding the sounds without VPK overrides.
    """
    if not additional:
        return  # Don't pack, there aren't any new sounds..

    orig_manifest = os.path.join(
        '..',
        SOUND_MAN_FOLDER.get(CONF['game_id', ''], 'portal2'),
        'scripts',
        'game_sounds_manifest.txt',
    )

    try:
        with open(orig_manifest) as f:
            props = Property.parse(f, orig_manifest).find_key(
                'game_sounds_manifest',
                [],
            )
    except FileNotFoundError:  # Assume no sounds
        props = Property('game_sounds_manifest', [])

    scripts = [prop.value for prop in props.find_all('precache_file')]

    for script in additional:
        scripts.append(script)

    for script in excludes:
        try:
            scripts.remove(script)
        except ValueError:
            LOGGER.warning(
                '"{}" should be excluded, but it\'s'
                ' not in the manifest already!',
                script,
            )

    # Build and unbuild it to strip other things out - Valve includes a bogus
    # 'new_sound_scripts_must_go_below_here' entry..
    new_props = Property('game_sounds_manifest',
                         [Property('precache_file', file) for file in scripts])

    inject_loc = os.path.join('bee2', 'inject', 'soundscript_manifest.txt')
    with open(inject_loc, 'w') as f:
        for line in new_props.export():
            f.write(line)
    LOGGER.info('Written new soundscripts_manifest..')
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 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 #12
0
 def __init__(
     self,
     item_id: str,
     versions: Dict[str, Version],
     def_version: Version,
     needs_unlock: bool = False,
     all_conf: Optional[Property] = None,
     unstyled: bool = False,
     isolate_versions: bool = False,
     glob_desc: tkMarkdown.MarkdownData = (),
     desc_last: bool = False,
     folders: Dict[Tuple[FileSystem, str], ItemVariant] = EmptyMapping,
 ) -> None:
     self.id = item_id
     self.versions = versions
     self.def_ver = def_version
     self.needs_unlock = needs_unlock
     self.all_conf = all_conf or Property(None, [])
     # If set or set on a version, don't look at the first version
     # for unstyled items.
     self.isolate_versions = isolate_versions
     self.unstyled = unstyled
     self.glob_desc = glob_desc
     self.glob_desc_last = desc_last
     # Dict of folders we need to have decoded.
     self.folders = folders
Exemple #13
0
    def __init__(
            self,
            music_id,
            selitem_data: 'SelitemData',
            sound: Dict[MusicChannel, List[str]],
            children: Dict[MusicChannel, str],
            config: Property = None,
            inst=None,
            sample: Dict[MusicChannel, Optional[str]] = None,
            pack=(),
            loop_len=0,
            synch_tbeam=False,
    ):
        self.id = music_id
        self.config = config or Property(None, [])
        self.children = children
        set_cond_source(config, 'Music <{}>'.format(music_id))
        self.inst = inst
        self.sound = sound
        self.packfiles = list(pack)
        self.len = loop_len
        self.sample = sample

        self.selitem_data = selitem_data

        self.has_synced_tbeam = synch_tbeam
Exemple #14
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 #15
0
    def __init__(
        self,
        name: str,
        sounds: List[str],
        volume: Union[Tuple[Union[float, VOLUME], Union[float, VOLUME]], float,
                      VOLUME] = VOL_NORM,
        channel: Channel = Channel.DEFAULT,
        level: Union[Tuple[Union[float, Level], Union[float, Level]], float,
                     Level] = Level.SNDLVL_NORM,
        pitch: Union[Tuple[Union[float, Pitch], Union[float, Pitch]], float,
                     Pitch] = Pitch.PITCH_NORM,

        # Operator stacks
        stack_start: Optional[Property] = None,
        stack_update: Optional[Property] = None,
        stack_stop: Optional[Property] = None,
        use_v2: bool = False,
    ) -> None:
        """Create a soundscript."""
        self.name = name
        self.sounds = sounds
        self.channel = channel
        self.force_v2 = use_v2

        if isinstance(volume, tuple):
            self.volume = volume
        else:
            self.volume = volume, volume

        if isinstance(level, tuple):
            self.level = level
        else:
            self.level = level, level

        if isinstance(pitch, tuple):
            self.pitch = pitch
        else:
            self.pitch = pitch, pitch

        self.stack_start = Property(
            '', []) if stack_start is None else stack_start  # type: Property
        self.stack_update = Property(
            '', []) if stack_update is None else stack_update  # type: Property
        self.stack_stop = Property(
            '', []) if stack_stop is None else stack_stop  # type: Property
Exemple #16
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 #17
0
def res_add_global_inst(vmf: VMF, res: Property):
    """Add one instance in a specific location.

    Options:

    - `allow_multiple`: Allow multiple copies of this instance. If 0, the
        instance will not be added if it was already added.
    - `name`: The targetname of the instance. If blank, the instance will
          be given a name of the form `inst_1234`.
    - `file`: The filename for the instance.
    - `angles`: The orientation of the instance (defaults to `0 0 0`).
    - `fixup_style`: The Fixup style for the instance. `0` (default) is
        Prefix, `1` is Suffix, and `2` is None.
    - `position`: The location of the instance. If not set, it will be placed
        in a 128x128 nodraw room somewhere in the map. Objects which can
        interact with nearby object should not be placed there.
    """
    if not res.has_children():
        res = Property('AddGlobal', [Property('File', res.value)])
    file = instanceLocs.resolve_one(res['file'], error=True)

    if res.bool('allow_multiple') or file.casefold() not in conditions.GLOBAL_INSTANCES:
        # By default we will skip adding the instance
        # if was already added - this is helpful for
        # items that add to original items, or to avoid
        # bugs.
        new_inst = vmf.create_ent(
            classname="func_instance",
            targetname=res['name', ''],
            file=file,
            angles=res['angles', '0 0 0'],
            fixup_style=res['fixup_style', '0'],
        )
        try:
            new_inst['origin'] = res['position']
        except IndexError:
            new_inst['origin'] = options.get(Vec, 'global_ents_loc')
        conditions.GLOBAL_INSTANCES.add(file.casefold())
        conditions.ALL_INST.add(file.casefold())
        if new_inst['targetname'] == '':
            new_inst['targetname'] = "inst_"
            new_inst.make_unique()
    return conditions.RES_EXHAUSTED
Exemple #18
0
    def func(vmf: srctools.VMF, prop: Property):
        """Replacement function which performs the legacy behaviour."""
        # The old system for setup functions - smuggle them in by
        # setting Property.value to an arbitrary object.
        smuggle = Property(prop.real_name, setup_wrap(vmf, prop))

        def closure(ent: Entity) -> object:
            """Use the closure to store the smuggled setup data."""
            return result_wrap(vmf, ent, smuggle)

        return closure
Exemple #19
0
    def export(
        self
    ) -> tuple[list[EditorItem], dict[RenderableType, Renderable], Property]:
        """Export this style, returning the vbsp_config and editoritems.

        This is a special case, since styles should go first in the lists.
        """
        vbsp_config = Property(None, [])
        vbsp_config += self.config()

        return self.items, self.renderables, vbsp_config
Exemple #20
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 #21
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 #22
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 #23
0
    def get(self, expected_type: type, name: str) -> Any:
        """Get the given option.
        expected_type should be the class of the value that's expected.
        The value can be None if unset, except for Property types (which
        will always have an empty block).

        If expected_type is an Enum, this will be used to convert the output.
        If it fails, a warning is produced and the first value in the enum is
        returned.
        """
        try:
            val = self.settings[name.casefold()]
        except KeyError:
            raise TypeError(
                'Option "{}" does not exist!'.format(name)) from None

        if val is None:
            if expected_type is Property:
                return Property(name, [])
            else:
                return None

        if issubclass(expected_type, Enum):
            enum_type = expected_type  # type: Optional[Type[Enum]]
            expected_type = str
        else:
            enum_type = None

        # Don't allow subclasses (bool/int)
        if type(val) is not expected_type:
            raise ValueError('Option "{}" is {} (code expected {})'.format(
                name,
                type(val),
                expected_type,
            ))

        if enum_type is not None:
            try:
                return enum_type(val)  # type: ignore
            except ValueError:
                LOGGER.warning(
                    'Option "{}" is not a valid value. '
                    'Allowed values are:\n{}', name,
                    '\n'.join([mem.value for mem in enum_type]))
                return next(iter(enum_type))  # type: ignore

        # Vec is mutable, don't allow modifying the original.
        if expected_type is Vec or expected_type is Property:
            assert isinstance(val, Vec) or isinstance(val, Property)
            return val.copy()
        else:
            assert isinstance(val, expected_type)
            return val
Exemple #24
0
def res_add_global_inst(res: Property):
    """Add one instance in a specific location.

    Options:
        allow_multiple: Allow multiple copies of this instance. If 0, the
            instance will not be added if it was already added.
        name: The targetname of the instance. IF blank, the instance will
              be given a name of the form 'inst_1234'.
        file: The filename for the instance.
        Angles: The orientation of the instance (defaults to '0 0 0').
        Fixup_style: The Fixup style for the instance. '0' (default) is
            Prefix, '1' is Suffix, and '2' is None.
        Position: The location of the instance. If not set, it will be placed
            in a 128x128 nodraw room somewhere in the map. Objects which can
            interact with nearby object should not be placed there.
    """
    if not res.has_children():
        res = Property('AddGlobal', [Property('File', res.value)])

    if res.bool('allow_multiple') or res['file'] not in GLOBAL_INSTANCES:
        # By default we will skip adding the instance
        # if was already added - this is helpful for
        # items that add to original items, or to avoid
        # bugs.
        new_inst = vbsp.VMF.create_ent(
            classname="func_instance",
            targetname=res['name', ''],
            file=instanceLocs.resolve_one(res['file'], error=True),
            angles=res['angles', '0 0 0'],
            fixup_style=res['fixup_style', '0'],
        )
        try:
            new_inst['origin'] = res['position']
        except IndexError:
            new_inst['origin'] = vbsp_options.get(Vec, 'global_ents_loc')
        GLOBAL_INSTANCES.add(res['file'])
        if new_inst['targetname'] == '':
            new_inst['targetname'] = "inst_"
            new_inst.make_unique()
    return RES_EXHAUSTED
Exemple #25
0
 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)))
Exemple #26
0
    def save(self, ignore_readonly: bool = False) -> None:
        """Save the palette file into the specified location.

        If ignore_readonly is true, this will ignore the `readonly`
        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.root(
            Property('Name', self.name), Property('TransName',
                                                  self.trans_name),
            Property('Group', self.group),
            Property('ReadOnly', srctools.bool_as_int(self.readonly)),
            Property('UUID', self.uuid.hex),
            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 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.readonly 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 #27
0
def gen_part_manifest(additional):
    """Generate a new particle system manifest file.

    This includes all the current ones defined, plus any custom ones.
    """
    if not additional:
        return  # Don't pack, there aren't any new particles..

    orig_manifest = os.path.join(
        '..',
        GAME_FOLDER.get(CONF['game_id', ''], 'portal2'),
        'particles',
        'particles_manifest.txt',
    )

    try:
        with open(orig_manifest) as f:
            props = Property.parse(f, orig_manifest).find_key(
                'particles_manifest',
                [],
            )
    except FileNotFoundError:  # Assume no particles
        props = Property('particles_manifest', [])

    parts = [prop.value for prop in props.find_all('file')]

    for particle in additional:
        parts.append(particle)

    # Build and unbuild it to strip comments and similar lines.
    new_props = Property('particles_manifest',
                         [Property('file', file) for file in parts])

    inject_loc = os.path.join('bee2', 'inject', 'particles_manifest.txt')
    with open(inject_loc, 'w') as f:
        for line in new_props.export():
            f.write(line)

    LOGGER.info('Written new particles_manifest..')
Exemple #28
0
def get_config(
    prop_block: Property,
    fsys: FileSystem,
    folder: str,
    pak_id='',
    prop_name='config',
    extension='.cfg',
):
    """Extract a config file referred to by the given property block.

    Looks for the prop_name key in the given prop_block.
    If the keyvalue has a value of "", an empty tree is returned.
    If it has children, a copy of them is returned.
    Otherwise the value is a filename in the zip which will be parsed.
    """
    prop_block = prop_block.find_key(prop_name, "")
    if prop_block.has_children():
        prop = prop_block.copy()
        prop.name = None
        return prop

    if prop_block.value == '':
        return Property(None, [])

    # Zips must use '/' for the separator, even on Windows!
    path = folder + '/' + prop_block.value
    if len(path) < 3 or path[-4] != '.':
        # Add extension
        path += extension
    try:
        return fsys.read_prop(path)
    except FileNotFoundError:
        LOGGER.warning('"{id}:{path}" not in zip!', id=pak_id, path=path)
        return Property(None, [])
    except UnicodeDecodeError:
        LOGGER.exception('Unable to read "{id}:{path}"', id=pak_id, path=path)
        raise
Exemple #29
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 #30
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)