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()
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)
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()