Example #1
0
async def parse_template(pak_id: str, file: File) -> None:
    """Parse the specified template file, extracting its ID."""
    path = f'{pak_id}:{file.path}'
    temp_id = await trio.to_thread.run_sync(parse_template_fast,
                                            file,
                                            path,
                                            cancellable=True)
    if not temp_id:
        LOGGER.warning('Fast-parse failure on {}!', path)
        with file.open_str() as f:
            props = await trio.to_thread.run_sync(Property.parse,
                                                  f,
                                                  cancellable=True)
        vmf = await trio.to_thread.run_sync(VMF.parse, props, cancellable=True)
        del props
        conf_ents = list(vmf.by_class['bee2_template_conf'])
        if len(conf_ents) > 1:
            raise KeyValError(f'Multiple configuration entities in template!',
                              path, None)
        elif not conf_ents:
            raise KeyValError(f'No configration entity for template!', path,
                              None)
        temp_id = conf_ents[0]['template_id']
        if not temp_id:
            raise KeyValError('No template ID for template!', path, None)
    TEMPLATES[temp_id.casefold()] = PackagePath(pak_id, file.path)
Example #2
0
    def load_soundscript(
        self,
        file: File,
        *,
        always_include: bool=False,
    ) -> Iterable[str]:
        """Read in a soundscript and record which files use it.

        If always_include is True, it will be included in the manifests even
        if it isn't used.

        The sounds registered by this soundscript are returned.
        """
        try:
            with file.sys, file.open_str() as f:
                props = Property.parse(f, file.path, allow_escapes=False)
        except FileNotFoundError:
            # It doesn't exist, complain and pretend it's empty.
            LOGGER.warning('Soundscript "{}" does not exist!', file.path)
            return ()
        except KeyValError:
            LOGGER.warning('Soundscript "{}" could not be parsed:', exc_info=True)
            return ()

        return self._parse_soundscript(props, file.path, always_include)
Example #3
0
    def _parse_file(self, filesys: FileSystem, file: File):
        """Parse one file (recursively if needed)."""

        if file in self._parse_list:
            return

        self._parse_list.append(file)

        with filesys, file.open_str() as f:
            tokeniser = Tokenizer(
                f,
                filename=file.path,
                error=FGDParseError,
                string_bracket=False,
            )
            for token, token_value in tokeniser:
                # The only things at top-level would be bare strings, and empty lines.
                if token is Token.NEWLINE:
                    continue
                if token is not Token.STRING:
                    raise tokeniser.error(token)
                token_value = token_value.casefold()

                if token_value == '@include':
                    include_file = tokeniser.expect(Token.STRING)
                    if not include_file.endswith('.fgd'):
                        include_file += '.fgd'

                    try:
                        include = filesys[include_file]
                    except KeyError:
                        raise FileNotFoundError(file)
                    self._parse_file(filesys, include)

                elif token_value == '@mapsize':
                    # Max/min map size definition
                    mapsize_args = tokeniser.expect(Token.PAREN_ARGS)
                    try:
                        min_size, max_size = mapsize_args.split(',')
                        self.map_size_min = int(min_size.strip())
                        self.map_size_max = int(max_size.strip())
                    except ValueError:
                        raise tokeniser.error(
                            'Invalid @MapSize: ({})',
                            mapsize_args,
                        )
                # Entity definition...
                elif token_value[:1] == '@':
                    try:
                        ent_type = EntityTypes(token_value[1:])
                    except ValueError:
                        raise tokeniser.error(
                            'Invalid Entity type "{}"!',
                            ent_type[1:],
                        )
                    EntityDef.parse(self, tokeniser, ent_type)
                else:
                    raise tokeniser.error('Bad keyword {!r}', token_value)
Example #4
0
    def load_soundscript(
        self,
        file: File,
        *,
        always_include: bool = False,
    ) -> Iterable[str]:
        """Read in a soundscript and record which files use it.

        If always_include is True, it will be included in the manifests even
        if it isn't used.

        The sounds registered by this soundscript are returned.
        """
        with file.sys, file.open_str() as f:
            props = Property.parse(f, file.path)
        return self._parse_soundscript(props, file.path, always_include)
Example #5
0
    def load_soundscript(
        self,
        file: File,
        *,
        always_include: bool=False,
    ) -> Iterable[str]:
        """Read in a soundscript and record which files use it.

        If always_include is True, it will be included in the manifests even
        if it isn't used.

        The sounds registered by this soundscript are returned.
        """
        with file.sys, file.open_str() as f:
            props = Property.parse(f, file.path)
        return self._parse_soundscript(props, file.path, always_include)
Example #6
0
def parse_template_fast(file: File, path: str) -> str:
    """Since we only care about a single KV, fully parsing is a big waste
    of time.

    So first try naively parsing - if we don't find it, fall
    back to full parsing.
    """
    in_entity = False
    nest_counter = 0
    has_classname = False
    found_id = ''
    temp_id = ''

    with file.open_str() as f:
        iterator = enumerate(f, 1)
        for lnum, line in iterator:
            line = line.strip().casefold()
            if not in_entity:
                if line != 'entity':
                    continue
                lnum, line = next(iterator, (None, ''))
                if line.strip() != '{':
                    raise KeyValError('Expected brace in entity definition',
                                      path, lnum)
                in_entity = True
                has_classname = False
                temp_id = ''
            elif line == '{':
                nest_counter += 1
            elif line == '}':
                if nest_counter == 0:
                    in_entity = False
                    if has_classname and found_id:
                        raise KeyValError(
                            'Multiple configuration entities in template!',
                            path, lnum)
                    elif has_classname and temp_id:
                        found_id = temp_id
                else:
                    nest_counter -= 1
            else:  # Inside ent.
                if line == '"classname" "bee2_template_conf"':
                    has_classname = True
                elif line.startswith('"template_id"'):
                    temp_id = line[15:-1]
    return found_id
Example #7
0
    def parse_manifest(fsys: FileSystem, file: File=None) -> Dict[str, 'SurfaceProp']:
        """Load surfaceproperties from a manifest.
        
        "scripts/surfaceproperties_manifest.txt" will be used if a file is
        not specified.
        """  
        with fsys:
            if not file:
                file = fsys['scripts/surfaceproperties_manifest.txt']
            
            with file.open_str() as f:
                manifest = Property.parse(f, file.path)
                
            surf = {}
            
            for prop in manifest.find_all('surfaceproperties_manifest', 'file'):
                surf = SurfaceProp.parse_file(fsys.read_prop(prop.value), surf)

            return surf
Example #8
0
    def parse_manifest(fsys: FileSystem, file: File=None) -> Dict[str, 'SurfaceProp']:
        """Load surfaceproperties from a manifest.
        
        "scripts/surfaceproperties_manifest.txt" will be used if a file is
        not specified.
        """  
        with fsys:
            if not file:
                file = fsys['scripts/surfaceproperties_manifest.txt']
            
            with file.open_str() as f:
                manifest = Property.parse(f, file.path)
                
            surf = {}
            
            for prop in manifest.find_all('surfaceproperties_manifest', 'file'):
                surf = SurfaceProp.parse_file(fsys.read_prop(prop.value), surf)

            return surf