def get_config(prop_block, zip_file, folder, pak_id='', prop_name='config'): """Extract a config file refered 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, []) path = os.path.join(folder, prop_block.value) + '.cfg' try: with zip_file.open(path) as f: return Property.parse( f, pak_id + ':' + path, ) except KeyError: print('"{}:{}" not in zip!'.format(pak_id, path)) return Property(None, [])
def __init__( self, style_id, selitem_data: 'SelitemData', editor, config=None, base_style=None, suggested=None, has_video=True, corridor_names=utils.EmptyMapping, ): self.id = style_id self.selitem_data = selitem_data self.editor = editor self.base_style = base_style self.bases = [] # Set by setup_style_tree() self.suggested = suggested or {} self.has_video = has_video self.corridor_names = { 'sp_entry': corridor_names.get('sp_entry', Property('', [])), 'sp_exit': corridor_names.get('sp_exit', Property('', [])), 'coop': corridor_names.get('coop', Property('', [])), } if config is None: self.config = Property(None, []) else: self.config = config
def parse_item_folder(folders, zip_file, pak_id): for fold in folders: prop_path = 'items/' + fold + '/properties.txt' editor_path = 'items/' + fold + '/editoritems.txt' config_path = 'items/' + fold + '/vbsp_config.cfg' try: with zip_file.open(prop_path, 'r') as prop_file: props = Property.parse( prop_file, pak_id + ':' + prop_path, ).find_key('Properties') with zip_file.open(editor_path, 'r') as editor_file: editor = Property.parse(editor_file, pak_id + ':' + editor_path) except KeyError as err: # Opening the files failed! raise IOError('"' + pak_id + ':items/' + fold + '" not valid!' 'Folder likely missing! ') from err editor_iter = Property.find_all(editor, 'Item') folders[fold] = { 'auth': sep_values(props['authors', '']), 'tags': sep_values(props['tags', '']), 'desc': list(desc_parse(props)), 'ent': 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': props['all_icon', None], 'vbsp': Property(None, []), # The first Item block found 'editor': next(editor_iter), # Any extra blocks (offset catchers, extent items) 'editor_extra': list(editor_iter), } if LOG_ENT_COUNT and folders[fold]['ent'] == '??': print('Warning: "{}:{}" has missing entity count!'.format( pak_id, 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: print('Warning: "{}:{}" has incomplete grouping icon ' 'definition!'.format(pak_id, prop_path)) try: with zip_file.open(config_path, 'r') as vbsp_config: folders[fold]['vbsp'] = Property.parse( vbsp_config, pak_id + ':' + config_path, ) except KeyError: folders[fold]['vbsp'] = Property(None, [])
def __init__( self, music_id, selitem_data: 'SelitemData', config=None, inst=None, sound=None, ): self.id = music_id self.config = config or Property(None, []) self.inst = inst self.sound = sound self.selitem_data = selitem_data
def __init__(self, item_id, versions, def_version, needs_unlock=False, all_conf=None, unstyled=False, glob_desc=(), desc_last=False): self.id = item_id self.versions = versions self.def_ver = def_version self.def_data = def_version['def_style'] self.needs_unlock = needs_unlock self.all_conf = all_conf or Property(None, []) self.unstyled = unstyled self.glob_desc = glob_desc self.glob_desc_last = desc_last
from datetime import datetime from zipfile import ZipFile from io import BytesIO import os import os.path import stat import shutil import sys import subprocess from property_parser import Property from BSP import BSP, BSP_LUMPS import utils CONF = Property('Config') SCREENSHOT_DIR = os.path.join( '..', 'portal2', # This is hardcoded into P2, it won't change for mods. 'puzzles', # Then the <random numbers> folder ) # Locations of resources we need to pack RES_ROOT = [ os.path.join('..', loc) for loc in ('bee2', 'bee2_dev', 'portal2_dlc2') ] def quote(txt): return '"' + txt + '"'
def export( self, style, all_items, music, skybox, voice, style_vars, elevator, pack_list, editor_sounds, should_refresh=False, ): """Generate the editoritems.txt and vbsp_config. - If no backup is present, the original editoritems is backed up - We unlock the mandatory items if specified - """ print('-' * 20) print('Exporting Items and Style for "' + self.name + '"!') print('Style =', style) print('Music =', music) print('Voice =', voice) print('Skybox =', skybox) print('Elevator = ', elevator) print('Style Vars:\n {') for key, val in style_vars.items(): print(' {} = {!s}'.format(key, val)) print(' }') print(len(pack_list), 'Pack Lists!') print(len(editor_sounds), 'Editor Sounds!') print('-' * 20) # VBSP, VRAD, editoritems export_screen.set_length('BACK', len(FILES_TO_BACKUP)) export_screen.set_length( 'CONF', # VBSP_conf, Editoritems, instances, gameinfo, pack_lists, # editor_sounds 6 + # Don't add the voicelines to the progress bar if not selected (0 if voice is None else len(VOICE_PATHS)), ) # files in compiler/ export_screen.set_length('COMP', len(os.listdir('../compiler'))) if should_refresh: export_screen.set_length('RES', extract_packages.res_count) else: export_screen.skip_stage('RES') export_screen.show() export_screen.grab_set_global() # Stop interaction with other windows vbsp_config = style.config.copy() # Editoritems.txt is composed of a "ItemData" block, holding "Item" and # "Renderables" sections. editoritems = Property("ItemData", list(style.editor.find_all('Item'))) for item in sorted(all_items): item_block, editor_parts, config_part = all_items[item].export() editoritems += item_block editoritems += editor_parts vbsp_config += config_part if voice is not None: vbsp_config += voice.config if skybox is not None: vbsp_config.set_key( ('Textures', 'Special', 'Sky'), skybox.material, ) vbsp_config += skybox.config if style.has_video: if elevator is None: # Use a randomised video vbsp_config.set_key( ('Elevator', 'type'), 'RAND', ) elif elevator.id == 'VALVE_BLUESCREEN': # This video gets a special script and handling vbsp_config.set_key( ('Elevator', 'type'), 'BSOD', ) else: # Use the particular selected video vbsp_config.set_key( ('Elevator', 'type'), 'FORCE', ) vbsp_config.set_key( ('Elevator', 'horiz'), elevator.horiz_video, ) vbsp_config.set_key( ('Elevator', 'vert'), elevator.vert_video, ) else: # No elevator video for this style vbsp_config.set_key( ('Elevator', 'type'), 'NONE', ) if music is not None: if music.sound is not None: vbsp_config.set_key( ('Options', 'music_SoundScript'), music.sound, ) if music.inst is not None: vbsp_config.set_key( ('Options', 'music_instance'), music.inst, ) vbsp_config.set_key(('Options', 'music_ID'), music.id) vbsp_config += music.config if voice is not None: vbsp_config.set_key( ('Options', 'voice_pack'), voice.id, ) vbsp_config.set_key(('Options', 'voice_char'), ','.join(voice.chars)) vbsp_config.set_key( ('Options', 'BEE2_loc'), os.path.dirname( os.getcwd()) # Go up one dir to our actual location ) vbsp_config.ensure_exists('StyleVars') vbsp_config['StyleVars'] += [ Property(key, utils.bool_as_int(val)) for key, val in style_vars.items() ] pack_block = Property('PackList', []) # A list of materials which will casue a specific packlist to be used. pack_triggers = Property('PackTriggers', []) for key, pack in pack_list.items(): pack_block.append( Property(key, [Property('File', file) for file in pack.files])) for trigger_mat in pack.trigger_mats: pack_triggers.append( Property('Material', [ Property('Texture', trigger_mat), Property('PackList', pack.id), ])) if pack_triggers.value: vbsp_config.append(pack_triggers) # If there are multiple of these blocks, merge them together # They will end up in this order. vbsp_config.merge_children( 'Textures', 'Fizzler', 'Options', 'StyleVars', 'Conditions', 'Voice', 'PackTriggers', ) for name, file, ext in FILES_TO_BACKUP: item_path = self.abs_path(file + ext) backup_path = self.abs_path(file + '_original' + ext) if os.path.isfile(item_path) and not os.path.isfile(backup_path): print('Backing up original ' + name + '!') shutil.copy(item_path, backup_path) export_screen.step('BACK') # This is the connections "heart" icon and "error" icon editoritems += style.editor.find_key("Renderables", []) # Build a property tree listing all of the instances for each item all_instances = Property("AllInstances", []) for item in editoritems.find_all("Item"): item_prop = Property(item['Type'], []) all_instances.append(item_prop) for inst_block in item.find_all("Exporting", "instances"): for inst in inst_block: item_prop.append(Property('Instance', inst['Name'])) if style_vars.get('UnlockDefault', False): print('Unlocking Items!') for item in editoritems.find_all('Item'): # If the Unlock Default Items stylevar is enabled, we # want to force the corridors and obs room to be # deletable and copyable # Also add DESIRES_UP, so they place in the correct orientation if item['type', ''] in _UNLOCK_ITEMS: editor_section = item.find_key("Editor", []) editor_section['deletable'] = '1' editor_section['copyable'] = '1' editor_section['DesiredFacing'] = 'DESIRES_UP' print('Editing Gameinfo!') self.edit_gameinfo(True) export_screen.step('CONF') print('Writing Editoritems!') os.makedirs(self.abs_path('portal2_dlc2/scripts/'), exist_ok=True) with open(self.abs_path('portal2_dlc2/scripts/editoritems.txt'), 'w') as editor_file: for line in editoritems.export(): editor_file.write(line) export_screen.step('CONF') print('Writing VBSP Config!') os.makedirs(self.abs_path('bin/bee2/'), exist_ok=True) with open(self.abs_path('bin/bee2/vbsp_config.cfg'), 'w') as vbsp_file: for line in vbsp_config.export(): vbsp_file.write(line) export_screen.step('CONF') print('Writing instance list!') with open(self.abs_path('bin/bee2/instances.cfg'), 'w') as inst_file: for line in all_instances.export(): inst_file.write(line) export_screen.step('CONF') print('Writing packing list!') with open(self.abs_path('bin/bee2/pack_list.cfg'), 'w') as pack_file: for line in pack_block.export(): pack_file.write(line) export_screen.step('CONF') print('Editing game_sounds!') self.add_editor_sounds(editor_sounds.values()) export_screen.step('CONF') if voice is not None: for prefix, dest, pretty in VOICE_PATHS: path = os.path.join( os.getcwd(), '..', 'config', 'voice', prefix + voice.id + '.cfg', ) print(path) if os.path.isfile(path): shutil.copy( path, self.abs_path('bin/bee2/{}voice.cfg'.format(dest))) print('Written "{}voice.cfg"'.format(dest)) else: print('No ' + pretty + ' voice config!') export_screen.step('CONF') print('Copying Custom Compiler!') for file in os.listdir('../compiler'): print('\t* compiler/{0} -> bin/{0}'.format(file)) shutil.copy(os.path.join('../compiler', file), self.abs_path('bin/')) export_screen.step('COMP') if should_refresh: print('Copying Resources!') self.refresh_cache() export_screen.grab_release() export_screen.reset() # Hide loading screen, we're done