def __init__( self, name: str, pak_id: str, icon: str, group: str=None, group_icon: str=None, ) -> None: self.name = name self.dnd_icon = img.Handle.parse_uri(utils.PackagePath(pak_id, ('items/clean/{}.png'.format(icon))), 64, 64) self.dnd_group = group if group_icon: self.dnd_group_icon = img.Handle.parse_uri(utils.PackagePath(pak_id, 'items/clean/{}.png'.format(group_icon)), 64, 64)
async def parse(cls, data: ParseData) -> ItemConfig: """Parse from config files.""" vers: dict[str, dict[str, lazy_conf.LazyConf]] = {} styles: dict[str, lazy_conf.LazyConf] all_config = get_config( data.info, 'items', pak_id=data.pak_id, prop_name='all_conf', source=f'<ItemConfig {data.pak_id}:{data.id} all_conf>', ) for ver in data.info.find_all('Version'): ver_id = ver['ID', 'VER_DEFAULT'] vers[ver_id] = styles = {} for sty_block in ver.find_all('Styles'): for style in sty_block: styles[style.real_name] = lazy_conf.from_file( utils.PackagePath(data.pak_id, f'items/{style.value}.cfg'), source= f'<ItemConfig {data.pak_id}:{data.id} in "{style.real_name}">', ) return ItemConfig( data.id, all_config, vers, )
def get_config( prop_block: Property, folder: str, pak_id: str, prop_name: str = 'config', extension: str = '.cfg', source: str = '', ) -> lazy_conf.LazyConf: """Lazily 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 will be returned. Otherwise the value is a filename in the zip which will be parsed. If source is supplied, set_cond_source() will be run. """ prop_block = prop_block.find_key(prop_name, "") if prop_block.has_children(): prop = prop_block.copy() prop.name = "" return lazy_conf.raw_prop(prop, source=source) if prop_block.value == '': return lazy_conf.BLANK # Zips must use '/' for the separator, even on Windows! path = f'{folder}/{prop_block.value}' if len(path) < 3 or path[-4] != '.': # Add extension path += extension return lazy_conf.from_file(utils.PackagePath(pak_id, path), source=source)
async def parse(cls, data: ParseData) -> Signage: styles: dict[str, SignStyle] = {} for prop in data.info.find_children('styles'): sty_id = prop.name.upper() if not prop.has_children(): # Style lookup. try: prop = next(data.info.find_all('styles', prop.value)) except StopIteration: raise ValueError('No style <{}>!'.format(prop.value)) world_tex = prop['world', ''] overlay_tex = prop['overlay', ''] # Don't warn, we don't actually need this yet. # if not world_tex: # LOGGER.warning( # '"{}" signage has no "world" value ' # 'for the "{}" style!', # data.id, # sty_id # ) if not overlay_tex: raise ValueError( f'"{data.id}"" signage has no "overlay" value ' f'option for the "{sty_id}" style!' ) if 'icon' in prop: img = ImgHandle.parse(prop, data.pak_id, 64, 64, subkey='icon') else: # Use the overlay texture. overlay_path = overlay_tex if not overlay_path.casefold().endswith('.vtf'): overlay_path += '.vtf' img = ImgHandle.file(utils.PackagePath(data.pak_id, overlay_path), 64, 64) styles[sty_id] = SignStyle( world_tex, overlay_tex, img, prop['type', 'square'] ) return cls( data.id, styles, data.info['name'], data.info['primary', None], data.info['secondary', None], data.info.bool('hidden'), )
def sprite(cls, path: str, width: int = 0, height: int = 0) -> Handle[utils.PackagePath]: """Shortcut for getting a handle to a builtin UI image, but with nearest-neighbour rescaling.""" return cls._get(TYP_BUILTIN_SPR, utils.PackagePath('<bee2>', path + '.png'), width, height)
def builtin(cls, path: str, width: int = 0, height: int = 0) -> Handle[utils.PackagePath]: """Shortcut for getting a handle to a builtin UI image.""" return cls._get(TYP_BUILTIN, utils.PackagePath('<bee2>', path + '.png'), width, height)
img = img_list.pop() except IndexError: img = ImageTk.PhotoImage('RGBA', (width, height)) return img def _discard_tk_img(img: ImageTk.PhotoImage | None) -> None: """Store an unused image so it can be reused.""" if img is not None: # Use setdefault and append so each step is atomic. img_list = _unused_tk_img.setdefault((img.width(), img.height()), []) img_list.append(img) # Special paths which map to various images. PATH_BLANK = utils.PackagePath('<special>', 'blank') PATH_ERROR = utils.PackagePath('<special>', 'error') PATH_LOAD = utils.PackagePath('<special>', 'load') PATH_NONE = utils.PackagePath('<special>', 'none') PATH_BG = utils.PackagePath('color', PETI_ITEM_BG_HEX[1:]) PATH_BLACK = utils.PackagePath('<color>', '000') PATH_WHITE = utils.PackagePath('<color>', 'fff') class ImageType(Generic[ArgT]): """Represents a kind of image that can be loaded or generated. This contains callables for generating a PIL image from a specified arg type, width and height. """ def __init__(
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
async def parse(cls, data: ParseData): """Parse a style definition.""" info = data.info selitem_data = SelitemData.parse(info, data.pak_id) base = info['base', ''] has_video = srctools.conv_bool( info['has_video', ''], not data.is_override, # Assume no video for override ) vpk_name = info['vpk_name', ''].casefold() sugg: dict[str, set[str]] = { 'quote': set(), 'music': set(), 'skybox': set(), 'elev': set(), } for prop in info.find_children('suggested'): try: sugg[prop.name].add(prop.value) except KeyError: LOGGER.warning('Unknown suggestion types for style {}: {}', data.id, prop.name) sugg_tup = ( sugg['quote'], sugg['music'], sugg['skybox'], sugg['elev'], ) corr_conf = info.find_key('corridors', or_blank=True) corridors = {} icon_folder = corr_conf['icon_folder', ''] for group, length in CORRIDOR_COUNTS.items(): group_prop = corr_conf.find_key(group, or_blank=True) for i in range(1, length + 1): prop = group_prop.find_key(str(i), '') if icon_folder: icon = utils.PackagePath( data.pak_id, 'corr/{}/{}/{}.jpg'.format(icon_folder, group, i)) else: icon = img.PATH_BLANK if prop.has_children(): corridors[group, i] = CorrDesc( name=prop['name', ''], icon=prop['icon', icon], desc=prop['Desc', ''], ) else: corridors[group, i] = CorrDesc( name=prop.value, icon=icon, desc='', ) if base == '': base = None try: folder = 'styles/' + info['folder'] except LookupError: # It's OK for override styles to be missing their 'folder' # value. if data.is_override: items = [] renderables = {} vbsp = lazy_conf.BLANK else: raise ValueError( f'Style "{data.id}" missing configuration folder!') else: with data.fsys[folder + '/items.txt'].open_str() as f: items, renderables = await trio.to_thread.run_sync( EditorItem.parse, f) vbsp = lazy_conf.from_file( utils.PackagePath(data.pak_id, folder + '/vbsp_config.cfg'), missing_ok=True, source=f'Style <{data.id}>', ) return cls( style_id=data.id, selitem_data=selitem_data, items=items, renderables=renderables, config=vbsp, base_style=base, suggested=sugg_tup, has_video=has_video, corridors=corridors, vpk_name=vpk_name, )