def modify(conf: ConfigFile, game_folder: Path) -> None: """Modify the map's screenshot.""" mod_type = conf.get_val('Screenshot', 'type', 'PETI').lower() if mod_type == 'cust': LOGGER.info('Using custom screenshot!') scr_loc = str(utils.conf_location('screenshot.jpg')) elif mod_type == 'auto': LOGGER.info('Using automatic screenshot!') scr_loc = None # The automatic screenshots are found at this location: auto_path = os.path.join(game_folder, 'screenshots') # We need to find the most recent one. If it's named # "previewcomplete", we want to ignore it - it's a flag # to indicate the map was playtested correctly. try: screens = [ os.path.join(auto_path, path) for path in os.listdir(auto_path) ] except FileNotFoundError: # The screenshot folder doesn't exist! screens = [] screens.sort( key=os.path.getmtime, reverse=True, # Go from most recent to least ) playtested = False for scr_shot in screens: filename = os.path.basename(scr_shot) if filename.startswith('bee2_playtest_flag'): # Previewcomplete is a flag to indicate the map's # been playtested. It must be newer than the screenshot playtested = True continue elif filename.startswith('bee2_screenshot'): continue # Ignore other screenshots # We have a screenshot. Check to see if it's # not too old. (Old is > 2 hours) date = datetime.fromtimestamp(os.path.getmtime(scr_shot)) diff = datetime.now() - date if diff.total_seconds() > 2 * 3600: LOGGER.info( 'Screenshot "{scr}" too old ({diff!s})', scr=scr_shot, diff=diff, ) continue # If we got here, it's a good screenshot! LOGGER.info('Chosen "{}"', scr_shot) LOGGER.info('Map Playtested: {}', playtested) scr_loc = scr_shot break else: # If we get to the end, we failed to find an automatic # screenshot! LOGGER.info('No Auto Screenshot found!') mod_type = 'peti' # Suppress the "None not found" error if conf.get_bool('Screenshot', 'del_old'): LOGGER.info('Cleaning up screenshots...') # Clean up this folder - otherwise users will get thousands of # pics in there! for screen in screens: if screen != scr_loc and os.path.isfile(screen): os.remove(screen) LOGGER.info('Done!') else: # PeTI type, or something else scr_loc = None if scr_loc is not None and os.path.isfile(scr_loc): # We should use a screenshot! for screen in find(): LOGGER.info('Replacing "{}"...', screen) # Allow us to edit the file... utils.unset_readonly(screen) shutil.copy(scr_loc, screen) # Make the screenshot readonly, so P2 can't replace it. # Then it'll use our own utils.set_readonly(screen) else: if mod_type != 'peti': # Error if we were looking for a screenshot LOGGER.warning('"{}" not found!', scr_loc) LOGGER.info('Using PeTI screenshot!') for screen in find(): # Make the screenshot writeable, so P2 will replace it LOGGER.info('Making "{}" replaceable...', screen) utils.unset_readonly(screen)
def export( self, style: packageLoader.Style, selected_objects: dict, should_refresh=False, ) -> Tuple[bool, bool]: """Generate the editoritems.txt and vbsp_config. - If no backup is present, the original editoritems is backed up. - For each object type, run its .export() function with the given - item. - Styles are a special case. """ LOGGER.info('-' * 20) LOGGER.info('Exporting Items and Style for "{}"!', self.name) LOGGER.info('Style = {}', style.id) for obj, selected in selected_objects.items(): # Skip lists and dicts etc - too long if selected is None or isinstance(selected, str): LOGGER.info('{} = {}', obj, selected) # VBSP, VRAD, editoritems export_screen.set_length('BACK', len(FILES_TO_BACKUP)) # files in compiler/ try: num_compiler_files = len(os.listdir('../compiler')) except FileNotFoundError: num_compiler_files = 0 if self.steamID == utils.STEAM_IDS['APERTURE TAG']: # Coop paint gun instance num_compiler_files += 1 if num_compiler_files == 0: LOGGER.warning('No compiler files!') export_screen.skip_stage('COMP') else: export_screen.set_length('COMP', num_compiler_files) LOGGER.info('Should refresh: {}', should_refresh) if should_refresh: # Check to ensure the cache needs to be copied over.. should_refresh = self.cache_invalid() if should_refresh: LOGGER.info("Cache invalid - copying..") else: LOGGER.info("Skipped copying cache!") # The items, plus editoritems, vbsp_config and the instance list. export_screen.set_length('EXP', len(packageLoader.OBJ_TYPES) + 3) # Do this before setting music and resources, # those can take time to compute. export_screen.show() export_screen.grab_set_global() # Stop interaction with other windows if should_refresh: # Count the files. export_screen.set_length( 'RES', sum(1 for file in res_system.walk_folder_repeat()), ) else: export_screen.skip_stage('RES') export_screen.skip_stage('MUS') # Make the folders we need to copy files to, if desired. os.makedirs(self.abs_path('bin/bee2/'), exist_ok=True) # Start off with the style's data. editoritems, vbsp_config = style.export() export_screen.step('EXP') vpk_success = True # Export each object type. for obj_name, obj_data in packageLoader.OBJ_TYPES.items(): if obj_name == 'Style': continue # Done above already LOGGER.info('Exporting "{}"', obj_name) selected = selected_objects.get(obj_name, None) try: obj_data.cls.export(packageLoader.ExportData( game=self, selected=selected, editoritems=editoritems, vbsp_conf=vbsp_config, selected_style=style, )) except packageLoader.NoVPKExport: # Raised by StyleVPK to indicate it failed to copy. vpk_success = False export_screen.step('EXP') vbsp_config.set_key( ('Options', 'BEE2_loc'), os.path.dirname(os.getcwd()) # Go up one dir to our actual location ) vbsp_config.set_key( ('Options', 'Game_ID'), self.steamID, ) # 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): LOGGER.info('Backing up original {}!', name) shutil.copy(item_path, backup_path) export_screen.step('BACK') # Backup puzzles, if desired backup.auto_backup(selected_game, export_screen) # This is the connection "heart" and "error" models. # These have to come last, so we need to special case it. editoritems += style.editor.find_key("Renderables", []).copy() # Special-case: implement the UnlockDefault stlylevar here, # so all items are modified. if selected_objects['StyleVar']['UnlockDefault']: LOGGER.info('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' LOGGER.info('Editing Gameinfo!') self.edit_gameinfo(True) LOGGER.info('Writing instance list!') with open(self.abs_path('bin/bee2/instances.cfg'), 'w', encoding='utf8') as inst_file: for line in self.build_instance_data(editoritems): inst_file.write(line) export_screen.step('EXP') # AtomicWriter writes to a temporary file, then renames in one step. # This ensures editoritems won't be half-written. LOGGER.info('Writing Editoritems!') with srctools.AtomicWriter(self.abs_path( 'portal2_dlc2/scripts/editoritems.txt')) as editor_file: for line in editoritems.export(): editor_file.write(line) export_screen.step('EXP') LOGGER.info('Writing VBSP Config!') os.makedirs(self.abs_path('bin/bee2/'), exist_ok=True) with open(self.abs_path('bin/bee2/vbsp_config.cfg'), 'w', encoding='utf8') as vbsp_file: for line in vbsp_config.export(): vbsp_file.write(line) export_screen.step('EXP') if num_compiler_files > 0: LOGGER.info('Copying Custom Compiler!') for file in os.listdir('../compiler'): src_path = os.path.join('../compiler', file) if not os.path.isfile(src_path): continue dest = self.abs_path('bin/' + file) LOGGER.info('\t* compiler/{0} -> bin/{0}', file) try: if os.path.isfile(dest): # First try and give ourselves write-permission, # if it's set read-only. utils.unset_readonly(dest) shutil.copy( src_path, self.abs_path('bin/') ) except PermissionError: # We might not have permissions, if the compiler is currently # running. export_screen.grab_release() export_screen.reset() messagebox.showerror( title=_('BEE2 - Export Failed!'), message=_('Copying compiler file {file} failed.' 'Ensure the {game} is not running.').format( file=file, game=self.name, ), master=TK_ROOT, ) return False, vpk_success export_screen.step('COMP') if should_refresh: LOGGER.info('Copying Resources!') self.refresh_cache() self.copy_mod_music() if self.steamID == utils.STEAM_IDS['APERTURE TAG']: os.makedirs(self.abs_path('sdk_content/maps/instances/bee2/'), exist_ok=True) with open(self.abs_path('sdk_content/maps/instances/bee2/tag_coop_gun.vmf'), 'w') as f: TAG_COOP_INST_VMF.export(f) export_screen.grab_release() export_screen.reset() # Hide loading screen, we're done return True, vpk_success
def export( self, style: packageLoader.Style, selected_objects: dict, should_refresh=False, ): """Generate the editoritems.txt and vbsp_config. - If no backup is present, the original editoritems is backed up. - For each object type, run its .export() function with the given - item. - Styles are a special case. """ LOGGER.info('-' * 20) LOGGER.info('Exporting Items and Style for "{}"!', self.name) LOGGER.info('Style = {}', style.id) for obj, selected in selected_objects.items(): # Skip lists and dicts etc - too long if selected is None or isinstance(selected, str): LOGGER.info('{} = {}', obj, selected) # VBSP, VRAD, editoritems export_screen.set_length('BACK', len(FILES_TO_BACKUP)) # files in compiler/ export_screen.set_length('COMP', len(os.listdir('../compiler'))) LOGGER.info('Should refresh: {}', should_refresh) if should_refresh: # Check to ensure the cache needs to be copied over.. should_refresh = not self.cache_valid() if should_refresh: export_screen.set_length('RES', extract_packages.res_count) else: export_screen.skip_stage('RES') # The items, plus editoritems, vbsp_config and the instance list. export_screen.set_length('EXP', len(packageLoader.OBJ_TYPES) + 3) export_screen.show() export_screen.grab_set_global() # Stop interaction with other windows # Make the folders we need to copy files to, if desired. os.makedirs(self.abs_path('bin/bee2/'), exist_ok=True) # Start off with the style's data. editoritems, vbsp_config = style.export() export_screen.step('EXP') # Export each object type. for obj_name, obj_data in packageLoader.OBJ_TYPES.items(): if obj_name == 'Style': continue # Done above already LOGGER.info('Exporting "{}"', obj_name) selected = selected_objects.get(obj_name, None) LOGGER.debug('Name: {}, selected: {}', obj_name, selected) obj_data.cls.export(packageLoader.ExportData( game=self, selected=selected, editoritems=editoritems, vbsp_conf=vbsp_config, selected_style=style, )) export_screen.step('EXP') vbsp_config.set_key( ('Options', 'BEE2_loc'), os.path.dirname(os.getcwd()) # Go up one dir to our actual location ) vbsp_config.set_key( ('Options', 'Game_ID'), self.steamID, ) # 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): LOGGER.info('Backing up original {}!', name) shutil.copy(item_path, backup_path) export_screen.step('BACK') # Backup puzzles, if desired backup.auto_backup(selected_game, export_screen) # This is the connection "heart" and "error" models. # These have to come last, so we need to special case it. editoritems += style.editor.find_key("Renderables", []) # Special-case: implement the UnlockDefault stlylevar here, # so all items are modified. if selected_objects['StyleVar']['UnlockDefault']: LOGGER.info('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' LOGGER.info('Editing Gameinfo!') self.edit_gameinfo(True) LOGGER.info('Writing instance list!') with open(self.abs_path('bin/bee2/instances.cfg'), 'w') as inst_file: for line in self.build_instance_data(editoritems): inst_file.write(line) export_screen.step('EXP') # AtomicWriter writes to a temporary file, then renames in one step. # This ensures editoritems won't be half-written. LOGGER.info('Writing Editoritems!') with utils.AtomicWriter(self.abs_path( 'portal2_dlc2/scripts/editoritems.txt')) as editor_file: for line in editoritems.export(): editor_file.write(line) export_screen.step('EXP') LOGGER.info('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('EXP') LOGGER.info('Copying Custom Compiler!') for file in os.listdir('../compiler'): src_path = os.path.join('../compiler', file) if not os.path.isfile(src_path): continue dest = self.abs_path('bin/' + file) LOGGER.info('\t* compiler/{0} -> bin/{0}', file) try: if os.path.isfile(dest): # First try and give ourselves write-permission, # if it's set read-only. utils.unset_readonly(dest) shutil.copy( src_path, self.abs_path('bin/') ) except PermissionError: # We might not have permissions, if the compiler is currently # running. export_screen.grab_release() export_screen.reset() messagebox.showerror( title='BEE2 - Export Failed!', message='Copying compiler file {file} failed.' 'Ensure the {game} is not running.'.format( file=file, game=self.name, ), master=TK_ROOT, ) return False export_screen.step('COMP') if should_refresh: LOGGER.info('Copying Resources!') self.refresh_cache() export_screen.grab_release() export_screen.reset() # Hide loading screen, we're done return True
def mod_screenshots(): """Modify the map's screenshot.""" mod_type = CONF['screenshot_type', 'PETI'].lower() if mod_type == 'cust': LOGGER.info('Using custom screenshot!') scr_loc = CONF['screenshot', ''] elif mod_type == 'auto': LOGGER.info('Using automatic screenshot!') scr_loc = None # The automatic screenshots are found at this location: auto_path = os.path.join( '..', GAME_FOLDER.get(CONF['game_id', ''], 'portal2'), 'screenshots' ) # We need to find the most recent one. If it's named # "previewcomplete", we want to ignore it - it's a flag # to indicate the map was playtested correctly. try: screens = [ os.path.join(auto_path, path) for path in os.listdir(auto_path) ] except FileNotFoundError: # The screenshot folder doesn't exist! screens = [] screens.sort( key=os.path.getmtime, reverse=True, # Go from most recent to least ) playtested = False for scr_shot in screens: filename = os.path.basename(scr_shot) if filename.startswith('bee2_playtest_flag'): # Previewcomplete is a flag to indicate the map's # been playtested. It must be newer than the screenshot playtested = True continue elif filename.startswith('bee2_screenshot'): continue # Ignore other screenshots # We have a screenshot. Check to see if it's # not too old. (Old is > 2 hours) date = datetime.fromtimestamp( os.path.getmtime(scr_shot) ) diff = datetime.now() - date if diff.total_seconds() > 2 * 3600: LOGGER.info( 'Screenshot "{scr}" too old ({diff!s})', scr=scr_shot, diff=diff, ) continue # If we got here, it's a good screenshot! LOGGER.info('Chosen "{}"', scr_shot) LOGGER.info('Map Playtested: {}', playtested) scr_loc = scr_shot break else: # If we get to the end, we failed to find an automatic # screenshot! LOGGER.info('No Auto Screenshot found!') mod_type = 'peti' # Suppress the "None not found" error if utils.conv_bool(CONF['clean_screenshots', '0']): LOGGER.info('Cleaning up screenshots...') # Clean up this folder - otherwise users will get thousands of # pics in there! for screen in screens: if screen != scr_loc: os.remove(screen) LOGGER.info('Done!') else: # PeTI type, or something else scr_loc = None if scr_loc is not None and os.path.isfile(scr_loc): # We should use a screenshot! for screen in find_screenshots(): LOGGER.info('Replacing "{}"...', screen) # Allow us to edit the file... utils.unset_readonly(screen) shutil.copy(scr_loc, screen) # Make the screenshot readonly, so P2 can't replace it. # Then it'll use our own utils.set_readonly(screen) else: if mod_type != 'peti': # Error if we were looking for a screenshot LOGGER.warning('"{}" not found!', scr_loc) LOGGER.info('Using PeTI screenshot!') for screen in find_screenshots(): # Make the screenshot writeable, so P2 will replace it LOGGER.info('Making "{}" replaceable...', screen) utils.unset_readonly(screen)
def export(self, style: packageLoader.Style, selected_objects: dict, should_refresh=False): """Generate the editoritems.txt and vbsp_config. - If no backup is present, the original editoritems is backed up. - For each object type, run its .export() function with the given - item. - Styles are a special case. """ LOGGER.info("-" * 20) LOGGER.info('Exporting Items and Style for "{}"!', self.name) LOGGER.info("Style = {}", style.id) for obj, selected in selected_objects.items(): # Skip lists and dicts etc - too long if selected is None or isinstance(selected, str): LOGGER.info("{} = {}", obj, selected) # VBSP, VRAD, editoritems export_screen.set_length("BACK", len(FILES_TO_BACKUP)) # files in compiler/ try: num_compiler_files = len(os.listdir("../compiler")) except FileNotFoundError: num_compiler_files = 0 if self.steamID == utils.STEAM_IDS["APERTURE TAG"]: # Coop paint gun instance num_compiler_files += 1 if num_compiler_files == 0: LOGGER.warning("No compiler files!") export_screen.skip_stage("COMP") else: export_screen.set_length("COMP", num_compiler_files) LOGGER.info("Should refresh: {}", should_refresh) if should_refresh: # Check to ensure the cache needs to be copied over.. should_refresh = not self.cache_valid() if should_refresh: export_screen.set_length("RES", extract_packages.res_count) else: export_screen.skip_stage("RES") export_screen.skip_stage("MUS") # The items, plus editoritems, vbsp_config and the instance list. export_screen.set_length("EXP", len(packageLoader.OBJ_TYPES) + 3) export_screen.show() export_screen.grab_set_global() # Stop interaction with other windows # Make the folders we need to copy files to, if desired. os.makedirs(self.abs_path("bin/bee2/"), exist_ok=True) # Start off with the style's data. editoritems, vbsp_config = style.export() export_screen.step("EXP") # Export each object type. for obj_name, obj_data in packageLoader.OBJ_TYPES.items(): if obj_name == "Style": continue # Done above already LOGGER.info('Exporting "{}"', obj_name) selected = selected_objects.get(obj_name, None) obj_data.cls.export( packageLoader.ExportData( game=self, selected=selected, editoritems=editoritems, vbsp_conf=vbsp_config, selected_style=style ) ) export_screen.step("EXP") vbsp_config.set_key( ("Options", "BEE2_loc"), os.path.dirname(os.getcwd()) # Go up one dir to our actual location ) vbsp_config.set_key(("Options", "Game_ID"), self.steamID) # 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): LOGGER.info("Backing up original {}!", name) shutil.copy(item_path, backup_path) export_screen.step("BACK") # Backup puzzles, if desired backup.auto_backup(selected_game, export_screen) # This is the connection "heart" and "error" models. # These have to come last, so we need to special case it. editoritems += style.editor.find_key("Renderables", []).copy() # Special-case: implement the UnlockDefault stlylevar here, # so all items are modified. if selected_objects["StyleVar"]["UnlockDefault"]: LOGGER.info("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" LOGGER.info("Editing Gameinfo!") self.edit_gameinfo(True) LOGGER.info("Writing instance list!") with open(self.abs_path("bin/bee2/instances.cfg"), "w") as inst_file: for line in self.build_instance_data(editoritems): inst_file.write(line) export_screen.step("EXP") # AtomicWriter writes to a temporary file, then renames in one step. # This ensures editoritems won't be half-written. LOGGER.info("Writing Editoritems!") with srctools.AtomicWriter(self.abs_path("portal2_dlc2/scripts/editoritems.txt")) as editor_file: for line in editoritems.export(): editor_file.write(line) export_screen.step("EXP") LOGGER.info("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("EXP") if num_compiler_files > 0: LOGGER.info("Copying Custom Compiler!") for file in os.listdir("../compiler"): src_path = os.path.join("../compiler", file) if not os.path.isfile(src_path): continue dest = self.abs_path("bin/" + file) LOGGER.info("\t* compiler/{0} -> bin/{0}", file) try: if os.path.isfile(dest): # First try and give ourselves write-permission, # if it's set read-only. utils.unset_readonly(dest) shutil.copy(src_path, self.abs_path("bin/")) except PermissionError: # We might not have permissions, if the compiler is currently # running. export_screen.grab_release() export_screen.reset() messagebox.showerror( title=_("BEE2 - Export Failed!"), message=_("Copying compiler file {file} failed." "Ensure the {game} is not running.").format( file=file, game=self.name ), master=TK_ROOT, ) return False export_screen.step("COMP") if should_refresh: LOGGER.info("Copying Resources!") self.refresh_cache() self.copy_mod_music() if self.steamID == utils.STEAM_IDS["APERTURE TAG"]: with open(self.abs_path("sdk_content/maps/instances/bee2/tag_coop_gun.vmf"), "w") as f: TAG_COOP_INST_VMF.export(f) export_screen.grab_release() export_screen.reset() # Hide loading screen, we're done return True
def export( self, style: packages.Style, selected_objects: dict, should_refresh=False, ) -> Tuple[bool, bool]: """Generate the editoritems.txt and vbsp_config. - If no backup is present, the original editoritems is backed up. - For each object type, run its .export() function with the given - item. - Styles are a special case. """ LOGGER.info('-' * 20) LOGGER.info('Exporting Items and Style for "{}"!', self.name) LOGGER.info('Style = {}', style.id) for obj, selected in selected_objects.items(): # Skip the massive dict in items if obj == 'Item': selected = selected[0] LOGGER.info('{} = {}', obj, selected) # VBSP, VRAD, editoritems export_screen.set_length('BACK', len(FILES_TO_BACKUP)) # files in compiler/ try: num_compiler_files = sum( 1 for file in utils.install_path('compiler').rglob('*')) except FileNotFoundError: num_compiler_files = 0 if self.steamID == utils.STEAM_IDS['APERTURE TAG']: # Coop paint gun instance num_compiler_files += 1 if num_compiler_files == 0: LOGGER.warning('No compiler files!') export_screen.skip_stage('COMP') else: export_screen.set_length('COMP', num_compiler_files) LOGGER.info('Should refresh: {}', should_refresh) if should_refresh: # Check to ensure the cache needs to be copied over.. should_refresh = self.cache_invalid() if should_refresh: LOGGER.info("Cache invalid - copying..") else: LOGGER.info("Skipped copying cache!") # Each object type # Editoritems # VBSP_config # Instance list # Editor models. # FGD file # Gameinfo export_screen.set_length('EXP', len(packages.OBJ_TYPES) + 6) # Do this before setting music and resources, # those can take time to compute. export_screen.show() try: if should_refresh: # Count the files. export_screen.set_length( 'RES', sum(1 for file in res_system.walk_folder_repeat()), ) else: export_screen.skip_stage('RES') export_screen.skip_stage('MUS') # Make the folders we need to copy files to, if desired. os.makedirs(self.abs_path('bin/bee2/'), exist_ok=True) # Start off with the style's data. vbsp_config = Property(None, []) vbsp_config += style.config.copy() all_items = style.items.copy() renderables = style.renderables.copy() export_screen.step('EXP') vpk_success = True # Export each object type. for obj_name, obj_data in packages.OBJ_TYPES.items(): if obj_name == 'Style': continue # Done above already LOGGER.info('Exporting "{}"', obj_name) selected = selected_objects.get(obj_name, None) try: obj_data.cls.export( packages.ExportData( game=self, selected=selected, all_items=all_items, renderables=renderables, vbsp_conf=vbsp_config, selected_style=style, )) except packages.NoVPKExport: # Raised by StyleVPK to indicate it failed to copy. vpk_success = False export_screen.step('EXP') vbsp_config.set_key(('Options', 'Game_ID'), self.steamID) vbsp_config.set_key( ('Options', 'dev_mode'), srctools.bool_as_int(optionWindow.DEV_MODE.get())) # If there are multiple of these blocks, merge them together. # They will end up in this order. vbsp_config.merge_children( 'Textures', 'Fizzlers', 'Options', 'StyleVars', 'DropperItems', 'Conditions', 'Quotes', '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 not os.path.isfile(item_path): # We can't backup at all. should_backup = False elif name == 'Editoritems': should_backup = not os.path.isfile(backup_path) else: # Always backup the non-_original file, it'd be newer. # But only if it's Valves - not our own. should_backup = should_backup_app(item_path) backup_is_good = should_backup_app(backup_path) LOGGER.info( '{}{}: normal={}, backup={}', file, ext, 'Valve' if should_backup else 'BEE2', 'Valve' if backup_is_good else 'BEE2', ) if not should_backup and not backup_is_good: # It's a BEE2 application, we have a problem. # Both the real and backup are bad, we need to get a # new one. try: os.remove(backup_path) except FileNotFoundError: pass try: os.remove(item_path) except FileNotFoundError: pass export_screen.reset() if messagebox.askokcancel( title=_('BEE2 - Export Failed!'), message=_( 'Compiler file {file} missing. ' 'Exit Steam applications, then press OK ' 'to verify your game cache. You can then ' 'export again.').format(file=file + ext, ), master=TK_ROOT, ): webbrowser.open('steam://validate/' + str(self.steamID)) return False, vpk_success if should_backup: LOGGER.info('Backing up original {}!', name) shutil.copy(item_path, backup_path) export_screen.step('BACK') # Backup puzzles, if desired backup.auto_backup(selected_game, export_screen) # Special-case: implement the UnlockDefault stlylevar here, # so all items are modified. if selected_objects['StyleVar']['UnlockDefault']: LOGGER.info('Unlocking Items!') for i, item in enumerate(all_items): # 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.id in _UNLOCK_ITEMS: all_items[i] = copy.copy(item) item.deletable = item.copiable = True item.facing = editoritems.DesiredFacing.UP LOGGER.info('Editing Gameinfo...') self.edit_gameinfo(True) export_screen.step('EXP') if not GEN_OPTS.get_bool('General', 'preserve_bee2_resource_dir'): LOGGER.info('Adding ents to FGD.') self.edit_fgd(True) export_screen.step('EXP') # AtomicWriter writes to a temporary file, then renames in one step. # This ensures editoritems won't be half-written. LOGGER.info('Writing Editoritems script...') with srctools.AtomicWriter( self.abs_path('portal2_dlc2/scripts/editoritems.txt') ) as editor_file: editoritems.Item.export(editor_file, all_items, renderables) export_screen.step('EXP') LOGGER.info('Writing Editoritems database...') with open(self.abs_path('bin/bee2/editor.bin'), 'wb') as inst_file: pick = pickletools.optimize(pickle.dumps(all_items)) inst_file.write(pick) export_screen.step('EXP') LOGGER.info('Writing VBSP Config!') os.makedirs(self.abs_path('bin/bee2/'), exist_ok=True) with open(self.abs_path('bin/bee2/vbsp_config.cfg'), 'w', encoding='utf8') as vbsp_file: for line in vbsp_config.export(): vbsp_file.write(line) export_screen.step('EXP') if num_compiler_files > 0: LOGGER.info('Copying Custom Compiler!') compiler_src = utils.install_path('compiler') for comp_file in compiler_src.rglob('*'): # Ignore folders. if comp_file.is_dir(): continue dest = self.abs_path('bin' / comp_file.relative_to(compiler_src)) LOGGER.info('\t* {} -> {}', comp_file, dest) folder = Path(dest).parent if not folder.exists(): folder.mkdir(parents=True, exist_ok=True) try: if os.path.isfile(dest): # First try and give ourselves write-permission, # if it's set read-only. utils.unset_readonly(dest) shutil.copy(comp_file, dest) except PermissionError: # We might not have permissions, if the compiler is currently # running. export_screen.reset() messagebox.showerror( title=_('BEE2 - Export Failed!'), message=_('Copying compiler file {file} failed. ' 'Ensure {game} is not running.').format( file=comp_file, game=self.name, ), master=TK_ROOT, ) return False, vpk_success export_screen.step('COMP') if should_refresh: LOGGER.info('Copying Resources!') music_files = self.copy_mod_music() self.refresh_cache(music_files) LOGGER.info('Optimizing editor models...') self.clean_editor_models(all_items) export_screen.step('EXP') self.generate_fizzler_sides(vbsp_config) if self.steamID == utils.STEAM_IDS['APERTURE TAG']: os.makedirs(self.abs_path('sdk_content/maps/instances/bee2/'), exist_ok=True) with open( self.abs_path( 'sdk_content/maps/instances/bee2/tag_coop_gun.vmf' ), 'w') as f: TAG_COOP_INST_VMF.export(f) export_screen.reset() # Hide loading screen, we're done return True, vpk_success except loadScreen.Cancelled: return False, False