Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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