Exemplo n.º 1
0
    def browse_for_iso(self):
        clean_iso_path, selected_filter = QFileDialog.getOpenFileName(
            self, "Select Clean Skyward Sword NTSC-U 1.0 ISO", None,
            "Wii ISO Files (*.iso)")
        if not clean_iso_path:
            return
        self.progress_dialog = ProgressDialog("Extracting Game Files",
                                              "Initializing...", 100)
        self.progress_dialog.setAutoClose(True)
        self.extract_thread = ExtractSetupThread(self.wit_manager,
                                                 clean_iso_path, None)
        self.extract_thread.update_total_steps.connect(
            lambda total_steps: self.progress_dialog.setMaximum(total_steps))
        self.extract_thread.update_progress.connect(self.ui_progress_callback)

        def on_complete():
            self.progress_dialog.reset()
            if self.randomize_after_iso_extract:
                self.randomize()

        self.extract_thread.extract_complete.connect(on_complete)

        def on_error(msg):
            self.progress_dialog.reset()
            self.error_msg = QMessageBox.critical(self, "Error", msg)

        self.extract_thread.error_abort.connect(on_error)
        self.extract_thread.start()
Exemplo n.º 2
0
    def randomize(self):
        if not self.randothread is None:
            print('ERROR: tried to randomize multiple times at once!')
            return
        dry_run = self.options['dry-run']
        if not (dry_run or self.wit_manager.actual_extract_already_exists()):
            self.randomize_after_iso_extract = True
            self.ask_for_clean_iso()
            return
        # make sure user can't mess with the options now
        self.rando = Randomizer(self.options.copy())

        if dry_run:
            extra_steps = 1  # done
        else:
            extra_steps = 101  # wit create wbfs + done

        self.progress_dialog = ProgressDialog(
            "Randomizing", "Initializing...",
            self.rando.get_total_progress_steps() + extra_steps)
        self.randomizer_thread = RandomizerThread(
            self.rando, self.wit_manager, self.options['output-folder'])
        self.randomizer_thread.update_progress.connect(
            self.ui_progress_callback)
        self.randomizer_thread.randomization_complete.connect(
            self.randomization_complete)
        self.randomizer_thread.error_abort.connect(self.on_error)
        self.randomizer_thread.start()
Exemplo n.º 3
0
class RandoGUI(QMainWindow):
    def __init__(self, options: Options):
        super().__init__()

        self.wit_manager = WitManager(Path('.').resolve())
        self.randothread = None
        self.error_msg = None
        self.progress_dialog = None
        self.randomize_after_iso_extract = False

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.setWindowTitle("Skyward Sword Randomizer v" + VERSION)

        self.options = options

        self.option_map = {}
        for option_key, option in OPTIONS.items():
            if option["name"] != "Banned Types" and option["name"] != "Seed":
                ui_name = option.get('ui', None)
                self.option_map[ui_name] = option
                if not ui_name:
                    continue
                widget = getattr(self.ui, ui_name)
                widget.installEventFilter(self)
                if isinstance(widget, QAbstractButton):
                    widget.setChecked(self.options[option_key])
                    widget.clicked.connect(self.update_settings)
                elif isinstance(widget, QComboBox):
                    for option_val in option['choices']:
                        widget.addItem(str(option_val))
                    widget.setCurrentIndex(option['choices'].index(
                        self.options[option_key]))
                    widget.currentIndexChanged.connect(self.update_settings)
                elif isinstance(widget, QListView):
                    pass
                elif isinstance(widget, QSpinBox):
                    if 'min' in option:
                        widget.setMinimum(option['min'])
                    if 'max' in option:
                        widget.setMaximum(option['max'])
                    widget.setValue(self.options[option_key])
                    widget.valueChanged.connect(self.update_settings)

        self.location_descriptions = {
            "skyloft":
            "Enables progression items to appear on Skyloft",
            "sky":
            "Enables progression items to appear in The Sky",
            "thunderhead":
            "Enables progression items to appear in The Thunderhead",
            "faron":
            "Enables progression items to appear in the Faron Province",
            "eldin":
            "Enables progression items to appear in the Eldin Province",
            "lanayru":
            "Enables progression items to appear in the Lanayru Province",
            "dungeon":
            "Enables progression items to appear in dungeons",
            "mini_dungeon":
            "Enables progression items to appear inside Mini Dungeons (i.e. the nodes in "
            "Lanayru Desert)",
            "free_gift":
            "Enables progression items to appear as free gifts from NPCs (i.e. the shield from "
            "Professor Owlan)",
            "freestanding":
            "Enables progression items to appear as freestanding items in the world "
            "(does not include the freestanding gratitude crystals)",
            "miscellaneous":
            "Enables progression items to appear in miscellaneous locations that don't fit into "
            "any other category (i.e. overworld chests) ",
            "silent_realm":
            "Enables progression items to appear as rewards for completing Silent Realm trials",
            "digging":
            "Enables progression items to appear in digging spots in the world (does not include Mogma "
            "Mitts checks, such as the one in Volcano Summit or in Fire Sanctuary)",
            "bombable":
            "Enables progression items to appear behind bombable walls or other bombable structures",
            "combat":
            "Enables progression items to appear as rewards for combat or completing a quest involving "
            "combat (i.e. Digging Mitts fight, Kikwi rescue). Does not impact combat within dungeons",
            "song":
            "Enables progression items to appear in place of learning songs (from Isle of Song, Ballad of the "
            "Goddess in Sealed Temple, Song of the Hero from Levias)",
            "spiral_charge":
            "Enables progression items to appear in the chests in the sky requiring Spiral Charge to"
            " access",
            "minigame":
            "Enables progression items to appear as rewards from winning minigames",
            "crystal":
            "Enables progression items to appear as loose crystals (currently not randomized and must "
            "always be enabled)",
            "short":
            "Enables progression items to appear as rewards for completing short quests (i.e. rescuing"
            " Orielle)",
            "long":
            "Enables progression items to appear as rewards for completing long quests (i.e. Peatrice)",
            "fetch":
            "Enables progression items to appear as rewards for returning items to NPCs ",
            "crystal_quest":
            "Enables progression items to appear as rewards for completing Gratitude Crystal quests",
            "scrapper":
            "Enables progression items to appear as rewards for Scrapper Quests",
            "peatrice":
            "Enables a progression item to appear as the reward for completing the Peatrice side quest",
            "goddess":
            "Enables progression items to appear as items in Goddess Chests",
            "faron_goddess":
            "Enables progression items to appear in the Goddess Chests linked to the Goddess Cubes in "
            "Faron Woods and Deep Woods",
            "eldin_goddess":
            "Enables progression items to appear in the Goddess Chests linked to the Goddess Cubes in "
            "the main part of Eldin Volcano and Mogma Turf",
            "lanayru_goddess":
            "Enables progression items to appear in the Goddess Chests linked to the Goddess Cubes "
            "in the main part of Lanayru Desert, Temple of Time and Lanayru Mines",
            "floria_goddess":
            "Enables progression items to appear in the Goddess Chests linked to the Goddess Cubes "
            "in Lake Floria",
            "summit_goddess":
            "Enables progression items to appear in the Goddess Chests linked to the Goddess Cubes "
            "in Volcano Summit",
            "sand_sea_goddess":
            "Enables progression items to appear in the Goddess Chests linked to the Goddess Cubes "
            "in Sand Sea",
        }
        for check_type in ALL_TYPES:
            widget = getattr(self.ui,
                             "progression_" + check_type.replace(" ", "_"))
            widget.setChecked(not check_type in self.options['banned-types'])
            if check_type == 'crystal':
                widget.setEnabled(False)
            widget.clicked.connect(self.update_settings)
            widget.installEventFilter(self)

        self.ui.ouput_folder_browse_button.clicked.connect(
            self.browse_for_output_dir)
        self.ui.randomize_button.clicked.connect(self.randomize)
        self.ui.permalink.textChanged.connect(self.permalink_updated)
        self.ui.seed.textChanged.connect(self.update_settings)
        self.ui.progression_goddess.clicked.connect(self.goddess_cubes_toggled)
        self.update_ui_for_settings()
        self.set_option_description(None)

        if 'NOGIT' in VERSION:
            self.error_msg = QErrorMessage()
            self.error_msg.showMessage(
                'Running from source without git is not supported!')

        elif not self.wit_manager.actual_extract_already_exists():
            self.ask_for_clean_iso()

    def ask_for_clean_iso(self):
        selected = QMessageBox.question(
            self,
            'Extract now?',
            'For randomizing purposes, a clean NTSC-U 1.00 ISO is needed, browse for it now? This is only needed once',
            defaultButton=QMessageBox.Yes)
        if selected == QMessageBox.Yes:
            self.browse_for_iso()
        else:
            self.randomize_after_iso_extract = False

    def randomize(self):
        if not self.randothread is None:
            print('ERROR: tried to randomize multiple times at once!')
            return
        dry_run = self.options['dry-run']
        if not (dry_run or self.wit_manager.actual_extract_already_exists()):
            self.randomize_after_iso_extract = True
            self.ask_for_clean_iso()
            return
        # make sure user can't mess with the options now
        self.rando = Randomizer(self.options.copy())

        if dry_run:
            extra_steps = 1  # done
        else:
            extra_steps = 101  # wit create wbfs + done

        self.progress_dialog = ProgressDialog(
            "Randomizing", "Initializing...",
            self.rando.get_total_progress_steps() + extra_steps)
        self.randomizer_thread = RandomizerThread(
            self.rando, self.wit_manager, self.options['output-folder'])
        self.randomizer_thread.update_progress.connect(
            self.ui_progress_callback)
        self.randomizer_thread.randomization_complete.connect(
            self.randomization_complete)
        self.randomizer_thread.error_abort.connect(self.on_error)
        self.randomizer_thread.start()

    def ui_progress_callback(self,
                             current_action,
                             completed_steps,
                             total_steps=None):
        self.progress_dialog.setValue(completed_steps)
        self.progress_dialog.setLabelText(current_action)
        if not total_steps is None:
            self.progress_dialog.setMaximum(total_steps)

    def on_error(self, message):
        self.error_msg = QErrorMessage(self)
        self.error_msg.showMessage(message)

    def randomization_complete(self):
        self.progress_dialog.reset()

        if self.options['no-spoiler-log']:
            text = f"""Randomization complete.<br>RANDO HASH: {self.rando.randomizer_hash}"""
        else:
            text = f"""Randomization complete.<br>RANDO HASH: {self.rando.randomizer_hash}<br>
                    If you get stuck, check the progression spoiler log in the output folder."""

        self.complete_dialog = QMessageBox()
        self.complete_dialog.setTextFormat(Qt.TextFormat.RichText)
        self.complete_dialog.setWindowTitle("Randomization complete")
        self.complete_dialog.setText(text)
        self.complete_dialog.setWindowIcon(self.windowIcon())
        self.complete_dialog.show()
        self.randomizer_thread = None

    def browse_for_iso(self):
        clean_iso_path, selected_filter = QFileDialog.getOpenFileName(
            self, "Select Clean Skyward Sword NTSC-U 1.0 ISO", None,
            "Wii ISO Files (*.iso)")
        if not clean_iso_path:
            return
        self.progress_dialog = ProgressDialog("Extracting Game Files",
                                              "Initializing...", 100)
        self.progress_dialog.setAutoClose(True)
        self.extract_thread = ExtractSetupThread(self.wit_manager,
                                                 clean_iso_path, None)
        self.extract_thread.update_total_steps.connect(
            lambda total_steps: self.progress_dialog.setMaximum(total_steps))
        self.extract_thread.update_progress.connect(self.ui_progress_callback)

        def on_complete():
            self.progress_dialog.reset()
            if self.randomize_after_iso_extract:
                self.randomize()

        self.extract_thread.extract_complete.connect(on_complete)

        def on_error(msg):
            self.progress_dialog.reset()
            self.error_msg = QMessageBox.critical(self, "Error", msg)

        self.extract_thread.error_abort.connect(on_error)
        self.extract_thread.start()

    def browse_for_output_dir(self):
        if self.options['output-folder'] and os.path.isfile(
                self.options['output-folder']):
            default_dir = os.path.dirname(self.options['output-folder'])
        else:
            default_dir = None

        output_folder = QFileDialog.getExistingDirectory(
            self, "Select output folder", default_dir)
        if not output_folder:
            return
        self.ui.output_folder.setText(output_folder)
        self.update_settings()

    def update_ui_for_settings(self):
        self.ui.output_folder.setText(str(self.options['output-folder']))
        self.ui.seed.setText(str(self.options["seed"]))
        current_settings = self.options.copy()
        for option_key, option in OPTIONS.items():
            if option["name"] != "Banned Types" and option["name"] != "Seed":
                ui_name = option.get('ui', None)
                if not ui_name:
                    continue
                widget = getattr(self.ui, ui_name)
                if isinstance(widget, QAbstractButton):
                    widget.setChecked(current_settings[option_key])
                elif isinstance(widget, QComboBox):
                    widget.setCurrentIndex(option['choices'].index(
                        current_settings[option_key]))
                elif isinstance(widget, QListView):
                    pass
                elif isinstance(widget, QSpinBox):
                    widget.setValue(current_settings[option_key])
                    getattr(self.ui,
                            f"label_for_{ui_name}").installEventFilter(self)

        for check_type in ALL_TYPES:
            widget = getattr(self.ui,
                             "progression_" + check_type.replace(" ", "_"))
            widget.setChecked(
                not check_type in current_settings['banned-types'])
        self.ui.permalink.setText(current_settings.get_permalink())

    def update_settings(self):
        self.options.set_option('output-folder', self.ui.output_folder.text())
        try:
            self.options.set_option("seed", int(self.ui.seed.text()))
        except ValueError:
            if self.ui.seed.text() == "":
                self.options.set_option("seed", -1)
            else:
                # TODO: give an error dialog or some sort of error message that the seed is invalid
                pass

        for option_command, option in OPTIONS.items():
            if option["name"] != "Banned Types" and option["name"] != "Seed":
                ui_name = option.get('ui', None)
                if not ui_name:
                    continue
                self.options.set_option(option_command,
                                        self.get_option_value(ui_name))

        self.options.set_option("banned-types", self.get_banned_types())
        self.ui.permalink.setText(self.options.get_permalink())

    def get_option_value(self, option_name):
        widget = getattr(self.ui, option_name)
        if isinstance(widget, QCheckBox) or isinstance(widget, QRadioButton):
            return widget.isChecked()
        elif isinstance(widget, QComboBox):
            return widget.itemText(widget.currentIndex())
        elif isinstance(widget, QSpinBox):
            return widget.value()
        elif isinstance(widget, QListView):
            pass
        else:
            print("Option widget is invalid: %s" % option_name)

    def get_banned_types(self):
        banned_types = []
        for check_type in ALL_TYPES:
            widget = getattr(self.ui,
                             "progression_" + check_type.replace(" ", "_"))
            if not widget.isChecked():
                banned_types.append(check_type)
        return banned_types

    def eventFilter(self, target, event):
        if event.type() == QEvent.Enter:
            ui_name = target.objectName()

            if ui_name.startswith("progression_"):
                ui_name = ui_name[len("progression_"):]
                self.set_option_description(
                    self.location_descriptions[ui_name])

            else:
                if ui_name.startswith("label_for_"):
                    ui_name = ui_name[len("label_for_"):]

                option = self.option_map[ui_name]
                self.set_option_description(option["help"])

            return True
        elif event.type() == QEvent.Leave:
            self.set_option_description(None)
            return True

        return QMainWindow.eventFilter(self, target, event)

    def set_option_description(self, new_description):
        if new_description is None:
            self.ui.option_description.setText(
                "(Hover over an option to see a description of what it does.)")
            self.ui.option_description.setStyleSheet("color: grey;")
        else:
            self.ui.option_description.setText(new_description)
            self.ui.option_description.setStyleSheet("")

    def permalink_updated(self):
        try:
            self.options.update_from_permalink(self.ui.permalink.text())
        except ValueError as e:
            # Ignore errors from faultly permalinks, with updating ui it gets reset anyways
            print(e)
        except IndexError as e:
            print(e)
        self.update_ui_for_settings()

    def goddess_cubes_toggled(self):
        enabled = self.ui.progression_goddess.isChecked()
        self.ui.progression_faron_goddess.setEnabled(enabled)
        self.ui.progression_eldin_goddess.setEnabled(enabled)
        self.ui.progression_lanayru_goddess.setEnabled(enabled)
        self.ui.progression_floria_goddess.setEnabled(enabled)
        self.ui.progression_summit_goddess.setEnabled(enabled)
        self.ui.progression_sand_sea_goddess.setEnabled(enabled)