import time import sys from OdenGraphQt import BaseNode from Qt import QtWidgets, QtCore, QtGui # Attempt to import winsound (Windows-only) try: import winsound HAS_WINSOUND = True except ImportError: HAS_WINSOUND = False class OverlayCanvas(QtWidgets.QWidget): """ UI overlay for displaying a red warning box, which can be repositioned by dragging. """ def __init__(self, parent=None): super().__init__(parent) # **Full-screen overlay** screen_geo = QtWidgets.QApplication.primaryScreen().geometry() self.setGeometry(screen_geo) # Set to full screen self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True) self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, False) self.setAttribute(QtCore.Qt.WA_AlwaysStackOnTop, True) # **Draggable Low Health Alert** self.helper_LowHealthAlert = QtCore.QRect(250, 300, 900, 35) # Default Position self.dragging = False self.drag_offset = QtCore.QPoint() self.setVisible(False) # Initially hidden def paintEvent(self, event): """Draw the helper overlay objects.""" if not self.isVisible(): return # Don't draw anything if invisible painter = QtGui.QPainter(self) painter.setPen(QtCore.Qt.NoPen) painter.setBrush(QtGui.QColor(255, 0, 0)) # Solid red rectangle painter.drawRect(self.helper_LowHealthAlert) # Draw bold white text centered within the rectangle font = QtGui.QFont("Arial", 14, QtGui.QFont.Bold) painter.setFont(font) painter.setPen(QtGui.QColor(255, 255, 255)) text = "LOW HEALTH" metrics = QtGui.QFontMetrics(font) text_width = metrics.horizontalAdvance(text) text_height = metrics.height() text_x = self.helper_LowHealthAlert.center().x() - text_width // 2 text_y = self.helper_LowHealthAlert.center().y() + text_height // 4 painter.drawText(text_x, text_y, text) def toggle_alert(self, state): """ Show or hide the overlay based on the state (1 = show, 0 = hide). """ self.setVisible(state == 1) self.update() def mousePressEvent(self, event): """Detect clicks inside the red box and allow dragging.""" if event.button() == QtCore.Qt.LeftButton and self.helper_LowHealthAlert.contains(event.pos()): self.dragging = True self.drag_offset = event.pos() - self.helper_LowHealthAlert.topLeft() def mouseMoveEvent(self, event): """Handle dragging movement.""" if self.dragging: new_x = event.pos().x() - self.drag_offset.x() new_y = event.pos().y() - self.drag_offset.y() self.helper_LowHealthAlert.moveTopLeft(QtCore.QPoint(new_x, new_y)) self.update() def mouseReleaseEvent(self, event): """Stop dragging when the mouse button is released.""" self.dragging = False class FlyffLowHealthAlertNode(BaseNode): """ Custom OdenGraphQt node that toggles a visual alert overlay and plays a beep when health is low. """ __identifier__ = 'bunny-lab.io.flyff_low_health_alert_node' NODE_NAME = 'Flyff - Low Health Alert' overlay_instance = None # Shared overlay instance last_beep_time = 0 # Time tracking for beep interval BEEP_INTERVAL_SECONDS = 2 # Beep every 2 seconds def __init__(self): super(FlyffLowHealthAlertNode, self).__init__() # Create checkboxes to decide which kind of alert(s) to utilize self.add_checkbox('cb_1', '', 'Sound Alert', True) self.add_checkbox('cb_2', '', 'Visual Alert', True) # Create Input Port self.add_input('Toggle (1 = On | 0 = Off)', color=(200, 100, 0)) # Add text input widget to display received value self.add_text_input('value', 'Current Value', text='0') # Ensure only one overlay instance exists if not FlyffLowHealthAlertNode.overlay_instance: FlyffLowHealthAlertNode.overlay_instance = OverlayCanvas() FlyffLowHealthAlertNode.overlay_instance.show() def process_input(self): """ This function runs every 500ms (via the global update loop). It updates the displayed value and toggles the alert if needed. """ input_port = self.input(0) # If there is a connected node, fetch its output value if input_port.connected_ports(): connected_node = input_port.connected_ports()[0].node() if hasattr(connected_node, 'get_property'): value = connected_node.get_property('value') else: value = "0" else: value = "0" # Default to zero if nothing is connected try: input_value = int(value) # Ensure we interpret input as an integer (0 or 1) except ValueError: input_value = 0 # Default to off if the input is not valid # Update the value display box self.set_property('value', str(input_value)) # Check if the "Visual Alert" checkbox is enabled visual_alert_enabled = self.get_property('cb_2') # Ensure that if "Visual Alert" is unchecked, the overlay is always hidden if not visual_alert_enabled: FlyffLowHealthAlertNode.overlay_instance.toggle_alert(0) else: FlyffLowHealthAlertNode.overlay_instance.toggle_alert(input_value) # Check if "Sound Alert" is enabled and beep if necessary self.handle_beep(input_value) def handle_beep(self, input_value): """ Plays a beep sound every 2 seconds when the value is `1` and "Sound Alert" is enabled. """ sound_alert_enabled = self.get_property('cb_1') current_time = time.time() if input_value == 1 and sound_alert_enabled: if (current_time - FlyffLowHealthAlertNode.last_beep_time) >= FlyffLowHealthAlertNode.BEEP_INTERVAL_SECONDS: FlyffLowHealthAlertNode.last_beep_time = current_time self.play_beep() else: FlyffLowHealthAlertNode.last_beep_time = 0 # Reset when health is safe def play_beep(self): """ Plays a beep using `winsound.Beep` (Windows) or prints a terminal bell (`\a`). """ if HAS_WINSOUND: winsound.Beep(376, 100) # 376 Hz, 100ms duration else: print('\a', end='') # Terminal bell for non-Windows systems