def parse_item_folder( folders_to_parse: set[str], filesystem: FileSystem, pak_id: str, ) -> dict[str, ItemVariant]: """Parse through the data in item/ folders. folders is a dict, with the keys set to the folder names we want. The values will be filled in with itemVariant values """ folders: dict[str, ItemVariant] = {} for fold in folders_to_parse: prop_path = 'items/' + fold + '/properties.txt' editor_path = 'items/' + fold + '/editoritems.txt' config_path = 'items/' + fold + '/vbsp_config.cfg' first_item: EditorItem | None = None extra_items: list[EditorItem] = [] try: props = filesystem.read_prop(prop_path).find_key('Properties') f = filesystem[editor_path].open_str() except FileNotFoundError as err: raise IOError('"' + pak_id + ':items/' + fold + '" not valid! ' 'Folder likely missing! ') from err with f: tok = Tokenizer(f, editor_path) for tok_type, tok_value in tok: if tok_type is Token.STRING: if tok_value.casefold() != 'item': raise tok.error('Unknown item option "{}"!', tok_value) if first_item is None: first_item = EditorItem.parse_one(tok) else: extra_items.append(EditorItem.parse_one(tok)) elif tok_type is not Token.NEWLINE: raise tok.error(tok_type) if first_item is None: raise ValueError(f'"{pak_id}:items/{fold}/editoritems.txt has no ' '"Item" block!') try: editor_vmf = VMF.parse( filesystem.read_prop(editor_path[:-3] + 'vmf')) except FileNotFoundError: pass else: editoritems_vmf.load(first_item, editor_vmf) first_item.generate_collisions() # extra_items is any extra blocks (offset catchers, extent items). # These must not have a palette section - it'll override any the user # chooses. for extra_item in extra_items: extra_item.generate_collisions() for subtype in extra_item.subtypes: if subtype.pal_pos is not None: LOGGER.warning( f'"{pak_id}:items/{fold}/editoritems.txt has ' f'palette set for extra item blocks. Deleting.') subtype.pal_icon = subtype.pal_pos = subtype.pal_name = None # In files this is specified as PNG, but it's always really VTF. try: all_icon = FSPath(props['all_icon']).with_suffix('.vtf') except LookupError: all_icon = None folders[fold] = ItemVariant( editoritems=first_item, editor_extra=extra_items, # Add the folder the item definition comes from, # so we can trace it later for debug messages. source=f'<{pak_id}>/items/{fold}', pak_id=pak_id, vbsp_config=lazy_conf.BLANK, authors=sep_values(props['authors', '']), tags=sep_values(props['tags', '']), desc=desc_parse(props, f'{pak_id}:{prop_path}', pak_id), ent_count=props['ent_count', ''], url=props['infoURL', None], icons={ prop.name: img.Handle.parse( prop, pak_id, 64, 64, subfolder='items', ) for prop in props.find_children('icon') }, all_name=props['all_name', None], all_icon=all_icon, ) if Item.log_ent_count and not folders[fold].ent_count: LOGGER.warning( '"{id}:{path}" has missing entity count!', id=pak_id, path=prop_path, ) # If we have one of the grouping icon definitions but not both required # ones then notify the author. has_name = folders[fold].all_name is not None has_icon = folders[fold].all_icon is not None if (has_name or has_icon or 'all' in folders[fold].icons) and (not has_name or not has_icon): LOGGER.warning( 'Warning: "{id}:{path}" has incomplete grouping icon ' 'definition!', id=pak_id, path=prop_path, ) folders[fold].vbsp_config = lazy_conf.from_file( utils.PackagePath(pak_id, config_path), missing_ok=True, source=folders[fold].source, ) return folders
def parse_item_folder( folders_to_parse: Set[str], filesystem: FileSystem, pak_id: str, ) -> Dict[str, ItemVariant]: """Parse through the data in item/ folders. folders is a dict, with the keys set to the folder names we want. The values will be filled in with itemVariant values """ folders: Dict[str, ItemVariant] = {} for fold in folders_to_parse: prop_path = 'items/' + fold + '/properties.txt' editor_path = 'items/' + fold + '/editoritems.txt' config_path = 'items/' + fold + '/vbsp_config.cfg' first_item: Optional[Item] = None extra_items: List[EditorItem] = [] with filesystem: try: props = filesystem.read_prop(prop_path).find_key('Properties') f = filesystem[editor_path].open_str() except FileNotFoundError as err: raise IOError('"' + pak_id + ':items/' + fold + '" not valid!' 'Folder likely missing! ') from err with f: tok = Tokenizer(f, editor_path) for tok_type, tok_value in tok: if tok_type is Token.STRING: if tok_value.casefold() != 'item': raise tok.error('Unknown item option "{}"!', tok_value) if first_item is None: first_item = EditorItem.parse_one(tok) else: extra_items.append(EditorItem.parse_one(tok)) elif tok_type is not Token.NEWLINE: raise tok.error(tok_type) if first_item is None: raise ValueError('"{}:items/{}/editoritems.txt has no ' '"Item" block!'.format(pak_id, fold)) # extra_items is any extra blocks (offset catchers, extent items). # These must not have a palette section - it'll override any the user # chooses. for extra_item in extra_items: for subtype in extra_item.subtypes: if subtype.pal_pos is not None: LOGGER.warning( '"{}:items/{}/editoritems.txt has palette set for extra' ' item blocks. Deleting.'.format(pak_id, fold)) subtype.pal_icon = subtype.pal_pos = subtype.pal_name = None try: all_icon = FSPath(props['all_icon']) except LookupError: all_icon = None folders[fold] = ItemVariant( editoritems=first_item, editor_extra=extra_items, # Add the folder the item definition comes from, # so we can trace it later for debug messages. source='<{}>/items/{}'.format(pak_id, fold), vbsp_config=Property(None, []), authors=sep_values(props['authors', '']), tags=sep_values(props['tags', '']), desc=desc_parse(props, pak_id + ':' + prop_path), ent_count=props['ent_count', ''], url=props['infoURL', None], icons={p.name: p.value for p in props['icon', []]}, all_name=props['all_name', None], all_icon=all_icon, ) if Item.log_ent_count and not folders[fold].ent_count: LOGGER.warning( '"{id}:{path}" has missing entity count!', id=pak_id, path=prop_path, ) # If we have at least 1, but not all of the grouping icon # definitions then notify the author. num_group_parts = ((folders[fold].all_name is not None) + (folders[fold].all_icon is not None) + ('all' in folders[fold].icons)) if 0 < num_group_parts < 3: LOGGER.warning( 'Warning: "{id}:{path}" has incomplete grouping icon ' 'definition!', id=pak_id, path=prop_path, ) try: with filesystem: folders[fold].vbsp_config = conf = filesystem.read_prop( config_path, ) except FileNotFoundError: folders[fold].vbsp_config = conf = Property(None, []) set_cond_source(conf, folders[fold].source) return folders
async def modify(self, pak_id: str, props: Property, source: str) -> ItemVariant: """Apply a config to this item variant. This produces a copy with various modifications - switching out palette or instance values, changing the config, etc. """ vbsp_config: lazy_conf.LazyConf if 'config' in props: # Item.parse() has resolved this to the actual config. vbsp_config = get_config( props, 'items', pak_id, ) else: vbsp_config = self.vbsp_config if 'replace' in props: # Replace property values in the config via regex. vbsp_config = lazy_conf.replace( vbsp_config, [(re.compile(prop.real_name, re.IGNORECASE), prop.value) for prop in props.find_children('Replace')]) vbsp_config = lazy_conf.concat( vbsp_config, get_config( props, 'items', pak_id, prop_name='append', )) if 'description' in props: desc = desc_parse(props, source, pak_id) else: desc = self.desc.copy() if 'appenddesc' in props: desc = tkMarkdown.join( desc, desc_parse(props, source, pak_id, prop_name='appenddesc'), ) if 'authors' in props: authors = sep_values(props['authors', '']) else: authors = self.authors if 'tags' in props: tags = sep_values(props['tags', '']) else: tags = self.tags.copy() variant = ItemVariant( pak_id, self.editor, vbsp_config, self.editor_extra.copy(), authors=authors, tags=tags, desc=desc, icons=self.icons.copy(), ent_count=props['ent_count', self.ent_count], url=props['url', self.url], all_name=self.all_name, all_icon=self.all_icon, source=f'{source} from {self.source}', ) [variant.editor] = variant._modify_editoritems( props, [variant.editor], pak_id, source, is_extra=False, ) if 'extra' in props: variant.editor_extra = variant._modify_editoritems( props.find_key('extra'), variant.editor_extra, pak_id, source, is_extra=True) return variant
def modify(self, fsys: FileSystem, props: Property, source: str) -> 'ItemVariant': """Apply a config to this item variant. This produces a copy with various modifications - switching out palette or instance values, changing the config, etc. """ if 'config' in props: # Item.parse() has resolved this to the actual config. vbsp_config = get_config( props, fsys, 'items', pak_id=fsys.path, ) else: vbsp_config = self.vbsp_config.copy() if 'replace' in props: # Replace property values in the config via regex. replace_vals = [(re.compile(prop.real_name, re.IGNORECASE), prop.value) for prop in props.find_children('Replace')] for prop in vbsp_config.iter_tree(): for regex, sub in replace_vals: prop.name = regex.sub(sub, prop.real_name) prop.value = regex.sub(sub, prop.value) vbsp_config += list( get_config( props, fsys, 'items', prop_name='append', pak_id=fsys.path, )) if 'description' in props: desc = desc_parse(props, source) else: desc = self.desc.copy() if 'appenddesc' in props: desc = tkMarkdown.join( desc, desc_parse(props, source, prop_name='appenddesc'), ) if 'authors' in props: authors = sep_values(props['authors', '']) else: authors = self.authors if 'tags' in props: tags = sep_values(props['tags', '']) else: tags = self.tags.copy() variant = ItemVariant( self.editor, vbsp_config, self.editor_extra.copy(), authors=authors, tags=tags, desc=desc, icons=self.icons.copy(), ent_count=props['ent_count', self.ent_count], url=props['url', self.url], all_name=self.all_name, all_icon=self.all_icon, source='{} from {}'.format(source, self.source), ) [variant.editor] = variant._modify_editoritems( props, [variant.editor], source, is_extra=False, ) if 'extra' in props: variant.editor_extra = variant._modify_editoritems( props.find_key('extra'), variant.editor_extra, source, is_extra=True) return variant