Пример #1
0
class FormationExtrapolator(QDialog):

    def update_display(self):
        field = self.app.current_step_state.field()
        preempt_rate = self.app.current_step_state.preempt_rate
        table = self.app.current_step_state.table_index
        old_fmaccum = self.app.current_step_state.formation_value
        last_formation = self.app.current_step_state.last_encounter_formation
        for i in range(10):
            formation_data = formations.encounter_on_formation(field=field, formation=old_fmaccum,
                                                               preempt_rate=preempt_rate, table=table)
            formation_type = formation_data[2]
            if formation_type == "Normal":
                if formation_data[0] == last_formation:
                    formation = formation_data[1]
                    new_fmaccum = old_fmaccum + 3
                else:
                    formation = formation_data[0]
                    new_fmaccum = old_fmaccum + 2
                preemptable = "Yes" if constants.FORMATION_PREEMPTABLE_MAP[formation] == 1 else "No"
            else:
                formation = formation_data[0]
                new_fmaccum = old_fmaccum + 1
                preemptable = "---"
            enemy_names = [constants.ENEMY_DATA[str(en)]["name"] for en in
                           constants.ENCOUNTER_DATA[str(formation)]["enemies"]]
            self.table.cellWidget(i, 0).setText(" " + str(old_fmaccum) + " -> " + str(new_fmaccum))
            self.table.cellWidget(i, 1).setText(" " + str(formation))
            self.table.cellWidget(i, 2).setText(" " + "\n ".join(enemy_names))
            self.table.cellWidget(i, 3).setText(" " + formation_type)
            self.table.cellWidget(i, 4).setText(" " + preemptable)

            old_fmaccum = new_fmaccum
            last_formation = formation
        self.table.resizeRowsToContents()

    def __init__(self, app: "MainWindow", parent=None):
        super(FormationExtrapolator, self).__init__(parent)
        self.app = app

        self.setWindowTitle(self.app.settings.WINDOW_TITLE)
        self.setWindowIcon(QIcon(self.app.settings.WINDOW_ICON))

        layout = QVBoxLayout()

        self.table = QTableWidget(10, 5)
        self.table.setMinimumWidth(500)
        self.table.setMinimumHeight(500)
        self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table.setFocusPolicy(Qt.NoFocus)
        self.table.setSelectionMode(QAbstractItemView.NoSelection)
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        labels = ["Formation\nAccumulator", "Formation ID", "Enemies", "Enemy\nFormation", "Preemptable"]
        for i in range(len(labels)):
            self.table.setHorizontalHeaderItem(i, QTableWidgetItem(labels[i]))
            for j in range(self.table.rowCount()):
                self.table.setCellWidget(j, i, QLabel())
        for i in range(self.table.rowCount()):
            self.table.setVerticalHeaderItem(i, QTableWidgetItem(""))
        layout.addWidget(self.table)

        self.setLayout(layout)
        self.show()
Пример #2
0
class MainWindow(QMainWindow):
    def open_formation_extrapolator(self):
        self.formation_extrapolator_windows.append(
            formation_extrapolator.FormationExtrapolator(self))

    def open_formation_list(self):
        self.formation_list_windows.append(formation_list.FormationList(self))

    def update_formation_windows(self):
        for window in self.formation_extrapolator_windows:
            window.update_display()

    def disconnect(self):
        self.hook.stop()

    def connect_pc(self):
        if self.hook.running:
            box = QMessageBox()
            box.setIcon(QMessageBox.Information)
            box.setWindowTitle("Already Connected")
            box.setText("Already connected. Disconnect first.")
            box.setStandardButtons(QMessageBox.Ok)
            box.exec_()
            return
        pid = hook.get_pc_process_id()
        if pid is None:
            box = QMessageBox()
            box.setIcon(QMessageBox.Information)
            box.setWindowTitle("FF7 PC Not Detected")
            box.setText("FF7 PC was not detected.")
            box.setStandardButtons(QMessageBox.Ok)
            box.exec_()
            return
        self.hook.hooked_platform = hook.Hook.PC_PLATFORM
        self.hook.hooked_process_id = pid
        self.hook.start()

    def connect_emulator(self):
        if self.hook.running:
            box = QMessageBox()
            box.setIcon(QMessageBox.Information)
            box.setWindowTitle("Already Connected")
            box.setText("Already connected. Disconnect first.")
            box.setStandardButtons(QMessageBox.Ok)
            box.exec_()
            return
        pids = hook.get_emu_process_ids()
        if len(pids) == 0:
            box = QMessageBox()
            box.setIcon(QMessageBox.Information)
            box.setWindowTitle("No Emulators Detected")
            box.setText("No emulators that can be connected to were detected.")
            box.setStandardButtons(QMessageBox.Ok)
            box.exec_()
            return
        ConnectEmuDialog(pids, self).exec_()

    def on_close(self):
        self.stepgraph.stop()
        self.disconnect()
        try:
            self.master.destroy()
        except Exception:
            pass

    def __init__(self, _settings: settings.Settings, parent=None):
        super(MainWindow, self).__init__(parent)

        self.formation_extrapolator_windows = []
        self.formation_list_windows = []

        self.settings = _settings

        self.stepgraph = stepgraph.Stepgraph(self)

        self.hook = hook.Hook(self)

        self.current_step_state: State = State(field_id=117,
                                               step=Step(0, 0),
                                               danger=0,
                                               step_fraction=0,
                                               formation_value=0)

        self.setWindowTitle(self.settings.WINDOW_TITLE)
        self.setWindowIcon(QIcon(self.settings.WINDOW_ICON))

        menubar = QMenuBar()

        menu_file = QMenu("File")

        menu_file_exit = QAction("Exit", self)
        menu_file_exit.triggered.connect(exit)
        menu_file.addAction(menu_file_exit)

        menu_connect = QMenu("Connect")

        menu_connect_connect_emulator = QAction("Connect to Emulator", self)
        menu_connect_connect_emulator.triggered.connect(self.connect_emulator)
        menu_connect.addAction(menu_connect_connect_emulator)

        menu_connect_connect_pc = QAction("Connect to PC", self)
        menu_connect_connect_pc.triggered.connect(self.connect_pc)
        menu_connect.addAction(menu_connect_connect_pc)

        menu_connect.addSeparator()

        menu_connect_disconnect = QAction("Disconnect", self)
        menu_connect_disconnect.triggered.connect(self.disconnect)
        menu_connect.addAction(menu_connect_disconnect)

        menu_window = QMenu("Window")

        menu_window_toggle_stepgraph = QAction("Toggle Stepgraph", self)
        menu_window_toggle_stepgraph.triggered.connect(self.stepgraph.toggle)
        menu_window.addAction(menu_window_toggle_stepgraph)

        menu_window_open_formation_window = QAction("Open Formation Window",
                                                    self)
        menu_window_open_formation_window.triggered.connect(
            self.open_formation_extrapolator)
        menu_window.addAction(menu_window_open_formation_window)

        menubar.addMenu(menu_file)
        menubar.addMenu(menu_connect)
        menubar.addMenu(menu_window)

        # self.master.config(menu=menubar)
        self.setMenuBar(menubar)

        main_frame = QFrame()
        layout = QVBoxLayout()

        rows = [
            "Step ID", "Step Fraction", "Offset", "Danger",
            "Formation Accumulator", "Field ID", "Table Index",
            "Danger Divisor Multiplier", "Lure Rate", "Preempt Rate",
            "Last Encounter Formation"
        ]

        self.memory_view = QTableWidget(len(rows), 2)
        self.memory_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.memory_view.setFocusPolicy(Qt.NoFocus)
        self.memory_view.setSelectionMode(QAbstractItemView.NoSelection)
        self.memory_view.setHorizontalHeaderItem(0,
                                                 QTableWidgetItem("Address"))
        self.memory_view.setHorizontalHeaderItem(
            1, QTableWidgetItem("        Value        "))
        self.memory_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.memory_view.verticalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        for rowNum in range(len(rows)):
            self.memory_view.setVerticalHeaderItem(rowNum,
                                                   QTableWidgetItem(""))
            _l = QLabel(" " + rows[rowNum] + " ")
            _l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
            self.memory_view.setCellWidget(rowNum, 0, _l)
            _l = QLabel("")
            _l.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
            self.memory_view.setCellWidget(rowNum, 1, _l)
        self.memory_view.resizeColumnsToContents()
        self.memory_view.setMinimumHeight(350)
        self.memory_view.setMinimumWidth(300)
        layout.addWidget(self.memory_view)

        self.connected_text = QLabel(self.settings.DISCONNECTED_TEXT)
        self.connected_text.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        layout.addWidget(self.connected_text)

        main_frame.setLayout(layout)

        self.setCentralWidget(main_frame)

        self.setMinimumHeight(420)
Пример #3
0
class DownloadWindow(QDialog):
    def __init__(self,
                 parent: Optional[QWidget] = None,
                 url: str = '') -> None:
        super().__init__(parent, )

        if parent:
            self.setWindowTitle('Download Mod')
        else:
            self.setWindowTitle(getTitleString('Download Mod'))
            self.setAttribute(Qt.WA_DeleteOnClose)

        mainLayout = QVBoxLayout(self)
        mainLayout.setContentsMargins(5, 5, 5, 5)

        self.signals = DownloadWindowEvents(self)

        # URL input

        gbUrl = QGroupBox('Mod URL')
        gbUrlLayout = QVBoxLayout()
        gbUrl.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.url = QLineEdit()
        self.url.setPlaceholderText(
            'https://www.nexusmods.com/witcher3/mods/...')
        self.url.setText(url)
        self.url.textChanged.connect(lambda: self.validateUrl(self.url.text()))
        gbUrlLayout.addWidget(self.url)

        self.urlInfo = QLabel('🌐')
        self.urlInfo.setContentsMargins(4, 4, 4, 4)
        self.urlInfo.setMinimumHeight(36)
        self.urlInfo.setWordWrap(True)
        gbUrlLayout.addWidget(self.urlInfo)

        gbUrl.setLayout(gbUrlLayout)
        mainLayout.addWidget(gbUrl)

        # File selection

        gbFiles = QGroupBox('Mod Files')
        gbFilesLayout = QVBoxLayout()
        gbFiles.setSizePolicy(QSizePolicy.MinimumExpanding,
                              QSizePolicy.MinimumExpanding)

        self.files = QTableWidget(0, 4)
        self.files.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.files.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.files.setContextMenuPolicy(Qt.CustomContextMenu)
        self.files.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.files.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.files.setWordWrap(False)
        self.files.setSortingEnabled(True)
        self.files.setFocusPolicy(Qt.StrongFocus)
        self.files.verticalHeader().hide()
        self.files.setSortingEnabled(True)
        self.files.sortByColumn(2, Qt.DescendingOrder)
        self.files.verticalHeader().setVisible(False)
        self.files.verticalHeader().setDefaultSectionSize(25)
        self.files.horizontalHeader().setHighlightSections(False)
        self.files.horizontalHeader().setStretchLastSection(True)
        self.files.setHorizontalHeaderLabels(
            ['File Name', 'Version', 'Upload Date', 'Description'])
        self.files.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.files.verticalScrollBar().valueChanged.connect(
            lambda: self.files.clearFocus())
        self.files.itemSelectionChanged.connect(lambda: self.validateFiles())
        self.files.setDisabled(True)
        self.files.setStyleSheet('''
            QTableView {
                gridline-color: rgba(255,255,255,1);
            }
            QTableView::item {
                padding: 5px;
                margin: 1px 0;
            }
            QTableView::item:!selected:hover {
                background-color: rgb(217, 235, 249);
                padding: 0;
            }
            ''')
        gbFilesLayout.addWidget(self.files)

        _mouseMoveEvent = self.files.mouseMoveEvent
        self.files.hoverIndexRow = -1

        def mouseMoveEvent(event: QMouseEvent) -> None:
            self.files.hoverIndexRow = self.files.indexAt(event.pos()).row()
            _mouseMoveEvent(event)

        self.files.mouseMoveEvent = mouseMoveEvent  # type: ignore
        self.files.setItemDelegate(ModListItemDelegate(self.files))
        self.files.setMouseTracking(True)

        gbFiles.setLayout(gbFilesLayout)
        mainLayout.addWidget(gbFiles)

        # Actions

        actionsLayout = QHBoxLayout()
        actionsLayout.setAlignment(Qt.AlignRight)
        self.download = QPushButton('Download', self)
        self.download.clicked.connect(lambda: self.downloadEvent())
        self.download.setAutoDefault(True)
        self.download.setDefault(True)
        self.download.setDisabled(True)
        actionsLayout.addWidget(self.download)
        cancel = QPushButton('Cancel', self)
        cancel.clicked.connect(self.cancelEvent)
        actionsLayout.addWidget(cancel)
        mainLayout.addLayout(actionsLayout)

        # Setup

        self.setMinimumSize(QSize(420, 420))
        self.setSizePolicy(QSizePolicy.MinimumExpanding,
                           QSizePolicy.MinimumExpanding)
        self.resize(QSize(720, 420))

        self.finished.connect(
            lambda: self.validateUrl.cancel())  # type: ignore
        self.finished.connect(
            lambda: self.downloadEvent.cancel())  # type: ignore

        self.modId = 0
        self.validateUrl(self.url.text())

    def cancelEvent(self) -> None:
        self.close()

    @debounce(200, cancel_running=True)
    async def validateUrl(self, url: str) -> bool:
        self.download.setDisabled(True)
        self.files.setDisabled(True)
        self.files.clearSelection()
        self.files.clearFocus()
        self.files.clearContents()
        self.files.setRowCount(0)
        self.files.setSortingEnabled(False)
        self.url.setStyleSheet('')
        self.modId = 0
        if not url:
            self.urlInfo.setText('''
                <font color="#888">Please enter a valid mod url.</font>
                ''')
            return False
        modId = getModId(url)
        if not modId:
            self.files.setDisabled(True)
            self.url.setStyleSheet('''
                *{
                    border: 1px solid #B22222;
                    padding: 1px 0px;
                }
                ''')
            self.urlInfo.setText('''
                <font color="#888">Please enter a valid mod url.</font>
                ''')
            return False
        self.urlInfo.setText('🌐')
        try:
            filesResponse = await getModFiles(modId)
        except (RequestError, ResponseError, Exception) as e:
            self.url.setStyleSheet('''
                *{
                    border: 1px solid #B22222;
                    padding: 1px 0px;
                }
                ''')
            self.urlInfo.setText(f'''
                <font color="#888">Could not get mod files: {e}.</font>
                ''')
            return False
        try:
            files = filesResponse['files']
            if not len(files):
                self.urlInfo.setText(f'''
                    <font color="#888">Mod "{modId}" has no files!</font>
                    ''')
                return False

            self.files.setRowCount(len(files))
            for i in range(len(files)):
                file = files[i]
                fileid = int(file['file_id'])
                name = str(file['name'])
                version = str(file['version'])
                _uploadtime = dateparser.parse(file['uploaded_time'])
                uploadtime = _uploadtime.astimezone(tz=None).strftime(
                    '%Y-%m-%d %H:%M:%S') if _uploadtime else '?'
                description = html.unescape(str(file['description']))
                nameItem = QTableWidgetItem(name)
                nameItem.setToolTip(name)
                nameItem.setData(Qt.UserRole, fileid)
                self.files.setItem(i, 0, nameItem)
                versionItem = QTableWidgetItem(version)
                versionItem.setToolTip(version)
                self.files.setItem(i, 1, versionItem)
                uploadtimeItem = QTableWidgetItem(uploadtime)
                uploadtimeItem.setToolTip(uploadtime)
                self.files.setItem(i, 2, uploadtimeItem)
                descriptionItem = QTableWidgetItem(description)
                descriptionItem.setToolTip(description)
                self.files.setItem(i, 3, descriptionItem)
        except KeyError as e:
            logger.exception(
                f'Could not find key "{str(e)}" in mod files response')
            self.urlInfo.setText(f'''
                <font color="#888">Could not find key "{str(e)}" in mod files response.</font>
                ''')
            return False

        self.urlInfo.setText(f'''
            <font color="#888">Found {len(files)} available files.</font>
            ''')
        self.files.resizeColumnsToContents()
        self.files.setDisabled(False)
        self.files.setSortingEnabled(True)
        self.modId = modId
        return True

    def validateFiles(self) -> bool:
        selection = self.files.selectionModel().selectedRows()
        if len(selection) > 0:
            self.download.setText(f'Download {len(selection)} mods')
            self.download.setDisabled(False)
            return True
        return False

    @debounce(25, cancel_running=True)
    async def downloadEvent(self) -> None:
        self.download.setDisabled(True)
        self.url.setDisabled(True)
        selection = self.files.selectionModel().selectedRows()
        files = [
            self.files.item(index.row(), 0).data(Qt.UserRole)
            for index in selection
        ]
        self.files.setDisabled(True)
        try:
            urls = await asyncio.gather(
                *[getModFileUrls(self.modId, file) for file in files],
                loop=asyncio.get_running_loop())
        except (RequestError, ResponseError, Exception) as e:
            self.url.setStyleSheet('''
                *{
                    border: 1px solid #B22222;
                    padding: 1px 0px;
                }
                ''')
            self.urlInfo.setText(f'''
                <font color="#888">Could not download mod files: {e}.</font>
                ''')
            return
        try:
            self.signals.download.emit([url[0]['URI'] for url in urls])
        except KeyError as e:
            logger.exception(
                f'Could not find key "{str(e)}" in file download response')
            self.urlInfo.setText(f'''
                <font color="#888">Could not find key "{str(e)}" in file download response.</font>
                ''')
            return
        self.close()