From e30ba4ec4ff29ad5b1a4d48dbcc67f9ff61758ec Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sun, 16 Feb 2025 18:53:21 -0700 Subject: [PATCH] Restructured the project and optimized several nodes. --- .../data_collector.cpython-312.pyc | Bin 0 -> 11112 bytes .../__pycache__/data_manager.cpython-312.pyc | Bin 0 -> 2415 bytes Modules/data_collector.py | 408 +++++++----------- Modules/data_manager.py | 66 +++ .../flyff_EXP_current.cpython-312.pyc | Bin 0 -> 4652 bytes .../flyff_FP_current.cpython-312.pyc | Bin 0 -> 4696 bytes .../flyff_FP_total.cpython-312.pyc | Bin 0 -> 4658 bytes .../flyff_HP_current.cpython-312.pyc | Bin 0 -> 4982 bytes .../flyff_HP_total.cpython-312.pyc | Bin 0 -> 4658 bytes .../flyff_MP_current.cpython-312.pyc | Bin 0 -> 4696 bytes .../flyff_MP_total.cpython-312.pyc | Bin 0 -> 4658 bytes ...lyff_character_status_node.cpython-312.pyc | Bin 0 -> 5005 bytes ...lyff_low_health_alert_node.cpython-312.pyc | Bin 0 -> 8937 bytes Nodes/{ => Flyff}/flyff_EXP_current.py | 0 Nodes/{ => Flyff}/flyff_FP_current.py | 0 Nodes/{ => Flyff}/flyff_FP_total.py | 0 Nodes/{ => Flyff}/flyff_HP_current.py | 0 Nodes/{ => Flyff}/flyff_HP_total.py | 0 Nodes/{ => Flyff}/flyff_MP_current.py | 0 Nodes/{ => Flyff}/flyff_MP_total.py | 0 Nodes/Flyff/flyff_character_status_node.py | 108 +++++ .../flyff_low_health_alert_node.py | 0 .../__pycache__/backdrop_node.cpython-312.pyc | Bin 0 -> 8458 bytes Nodes/{ => Organization}/backdrop_node.py | 0 ...lyff_character_status_node.cpython-312.pyc | Bin 6232 -> 4999 bytes Nodes/__pycache__/widget_node.cpython-312.pyc | Bin 4584 -> 5574 bytes Nodes/flyff_character_status_node.py | 126 ------ Nodes/group_node.py | 21 - Nodes/widget_node.py | 155 ------- borealis.py | 82 +++- debug_processed.png | Bin 2585 -> 0 bytes debug_screenshot.png | Bin 12521 -> 0 bytes 32 files changed, 390 insertions(+), 576 deletions(-) create mode 100644 Modules/__pycache__/data_collector.cpython-312.pyc create mode 100644 Modules/__pycache__/data_manager.cpython-312.pyc create mode 100644 Modules/data_manager.py create mode 100644 Nodes/Flyff/__pycache__/flyff_EXP_current.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_FP_current.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_FP_total.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_HP_current.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_HP_total.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_MP_current.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_MP_total.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_character_status_node.cpython-312.pyc create mode 100644 Nodes/Flyff/__pycache__/flyff_low_health_alert_node.cpython-312.pyc rename Nodes/{ => Flyff}/flyff_EXP_current.py (100%) rename Nodes/{ => Flyff}/flyff_FP_current.py (100%) rename Nodes/{ => Flyff}/flyff_FP_total.py (100%) rename Nodes/{ => Flyff}/flyff_HP_current.py (100%) rename Nodes/{ => Flyff}/flyff_HP_total.py (100%) rename Nodes/{ => Flyff}/flyff_MP_current.py (100%) rename Nodes/{ => Flyff}/flyff_MP_total.py (100%) create mode 100644 Nodes/Flyff/flyff_character_status_node.py rename Nodes/{ => Flyff}/flyff_low_health_alert_node.py (100%) create mode 100644 Nodes/Organization/__pycache__/backdrop_node.cpython-312.pyc rename Nodes/{ => Organization}/backdrop_node.py (100%) delete mode 100644 Nodes/flyff_character_status_node.py delete mode 100644 Nodes/group_node.py delete mode 100644 Nodes/widget_node.py delete mode 100644 debug_processed.png delete mode 100644 debug_screenshot.png diff --git a/Modules/__pycache__/data_collector.cpython-312.pyc b/Modules/__pycache__/data_collector.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb082f31f103164178cd6209590e8da85690ec89 GIT binary patch literal 11112 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 E57O2YMIeNbfGRXdB%x3Vxk#(UyGiV& zt5_$*tun5I&U|KjJYDIqf*x9DI;@}vR>i$l8*ggwIX1J4QDpH}p)1ha?|@iAEX)-l zCakhW8AVxMK_+gVOdHTXc3GWNsln!4BV3?SoU@5tWG*o(E+eHPHEe2JyfQVJmqW}1 z5p!~V)@7Dh&X1@qrupm0_^IpD#4o!=W}WkiYkMVn{gUt0J(pe=j#kC4+Osan*BV@x z2D?@w{V;YvblL}^h5k&V?t$-4etYUk=D;KM0Lg*%pA)_Ye}FW23El2UyPN>?uC@R~ zdZ4U^e{SOiTm~xcZV^lvHB?h;o|D;LVK!j_K60atv(pm>b;(VDr&Kj;&}#q}a}CFK zD}EJ_gv^})s+ZP10CTpFfq;#Qr!U7F6`LTd&K1Lokf}~KyT*>4$iENISZNtT0sGWB zpE9o2e8S*Mv1>K1kb0H$LMRCx5vY=35Y0EK$i)K8tssH?Y0od(9zBzXA#Z~V6?@S_ z8+zt$>Z{bfPw%tk)KmSa40ILZCm{grI!^|(!gIS9K}O*3ThOTxEwrs;?cLw?zGwRIV}1C(^23K~`dI7ix~ko|uz2B%iI)i> z!V3u7pMm%{V0Si|2^Py%PDR?zJMsf+#l~`1tj0>@htUF(bv4#eS3r-VUuzitj4ZHO z&%a@NKolqi%Ai8Rg3@?8C=YRLa1}OXKD0a_Lu=~Z^~~TSbufTcKp8aw zsf2X~l>ZLv0^R|30iA}i9RR)tXy7;q>_yPl_v~M~xJ(zPV#J=RdjqWE zgBAt}oJ9X$DY$@E!#Qv#hOZ@|#PGbt@RJ(Q1kkH{tW=9(q_4!$u$kdkiyGyR zKtLHWz!mfE@OH4AQjv8Kk@rNvS~u$6Jjnwtf(BGvP%okzgL=p5yWOc5f=kj zS*hZ3ZAM(QM8F(eK;6@g%j|gmGCL2YM8ts}?7N@=AB}G@C^ehasa5nn53VMkSnjP7 zWt8idRkbUwWs$vNghqU*KY1O=Gbt!2!3~+ht|OJ}rz`SmJ423xi`XdI1hJ`LjGv?2 z&nWXNGJZiLn>rd8T+j8b_YdsIOm?eR&n8;wtvx6^`1Q3`dNYX*9e#vHw~{E;z5LPV weoOfct9SM-?pr?b1P^Q_R6NpFP-1XXXxrT>oZaqE;i2vRBpz<(KuA{p4N$-W 200 and 255) # Apply a threshold to clean up the image - return thresh.filter(ImageFilter.MedianFilter(3)) # Apply a median filter to remove noise - -def sanitize_experience_string(raw_text): - text_no_percent = raw_text.replace('%', '') - text_no_spaces = text_no_percent.replace(' ', '') - cleaned = re.sub(r'[^0-9\.]', '', text_no_spaces) - match = re.search(r'\d+(?:\.\d+)?', cleaned) - if not match: - return None - val = float(match.group(0)) - if val < 0: - val = 0 - elif val > 100: - val = 100 - return round(val, 4) - -def locate_bars_opencv(template_path, threshold=MATCH_THRESHOLD): - """ - Attempt to locate the bars via OpenCV template matching. - """ - screenshot_pil = ImageGrab.grab() - screenshot_np = np.array(screenshot_pil) - screenshot_bgr = cv2.cvtColor(screenshot_np, cv2.COLOR_RGB2BGR) - template_bgr = cv2.imread(template_path, cv2.IMREAD_COLOR) - if template_bgr is None: - return None - result = cv2.matchTemplate(screenshot_bgr, template_bgr, cv2.TM_CCOEFF_NORMED) - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) - th, tw, _ = template_bgr.shape - if max_val >= threshold: - found_x, found_y = max_loc - return (found_x, found_y, tw, th) - else: - return None - -def parse_all_stats(raw_text): - raw_lines = raw_text.splitlines() - lines = [l.strip() for l in raw_lines if l.strip()] - stats_dict = { - "hp": (0,1), - "mp": (0,1), - "fp": (0,1), - "exp": None +def create_ocr_region(region_id, x=250, y=50, w=DEFAULT_WIDTH, h=DEFAULT_HEIGHT): + collector_mutex.lock() + if region_id in regions: + collector_mutex.unlock() + return + regions[region_id] = { + 'bbox': [x, y, w, h], + 'raw_text': "" } - if len(lines) < 4: - return stats_dict + collector_mutex.unlock() + _ensure_overlay() - hp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[0]) - if hp_match: - stats_dict["hp"] = (int(hp_match.group(1)), int(hp_match.group(2))) +def get_raw_text(region_id): + collector_mutex.lock() + if region_id not in regions: + collector_mutex.unlock() + return "" + text = regions[region_id]['raw_text'] + collector_mutex.unlock() + return text - mp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[1]) - if mp_match: - stats_dict["mp"] = (int(mp_match.group(1)), int(mp_match.group(2))) +def start_collector(): + t = threading.Thread(target=_update_ocr_loop, daemon=True) + t.start() - fp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[2]) - if fp_match: - stats_dict["fp"] = (int(fp_match.group(1)), int(fp_match.group(2))) - - exp_val = sanitize_experience_string(lines[3]) - stats_dict["exp"] = exp_val - return stats_dict - -# ============================================================================= -# Region & UI -# ============================================================================= - -class Region: - """ - Defines a draggable/resizable screen region for OCR capture. - """ - def __init__(self, x, y, label="Region", color=QColor(0, 0, 255)): - self.x = x - self.y = y - self.w = DEFAULT_WIDTH - self.h = DEFAULT_HEIGHT - self.label = label - self.color = color - self.visible = True - self.data = "" - - def rect(self): - return QRect(self.x, self.y, self.w, self.h) - - def label_rect(self): - return QRect(self.x, self.y - LABEL_HEIGHT, self.w, LABEL_HEIGHT) - - def resize_handles(self): - return [ - QRect(self.x - HANDLE_SIZE // 2, self.y - HANDLE_SIZE // 2, HANDLE_SIZE, HANDLE_SIZE), - QRect(self.x + self.w - HANDLE_SIZE // 2, self.y - HANDLE_SIZE // 2, HANDLE_SIZE, HANDLE_SIZE), - QRect(self.x - HANDLE_SIZE // 2, self.y + self.h - HANDLE_SIZE // 2, HANDLE_SIZE, HANDLE_SIZE), - QRect(self.x + self.w - HANDLE_SIZE // 2, self.y + self.h - HANDLE_SIZE // 2, HANDLE_SIZE, HANDLE_SIZE), - ] - -# ============================================================================= -# Flask API Server -# ============================================================================= - -@app.route('/data') -def get_data(): - """Returns the latest OCR data as JSON.""" - return jsonify(latest_data) - -def collect_ocr_data(): - """ - Collects OCR data every 0.5 seconds and updates global latest_data. - """ - global latest_data +def _update_ocr_loop(): while True: - # **Fetch updated region values from UI (thread-safe)** - region_lock.lock() # Lock for thread safety - x, y, w, h = shared_region["x"], shared_region["y"], shared_region["w"], shared_region["h"] - region_lock.unlock() + collector_mutex.lock() + region_ids = list(regions.keys()) + collector_mutex.unlock() - # **Grab the image of the updated region** - screenshot = ImageGrab.grab(bbox=(x, y, x + w, y + h)) + for rid in region_ids: + collector_mutex.lock() + bbox = regions[rid]['bbox'][:] + collector_mutex.unlock() - # **Debug: Save screenshots to verify capture** - screenshot.save("debug_screenshot.png") + x, y, w, h = bbox + screenshot = ImageGrab.grab(bbox=(x, y, x + w, y + h)) + processed = _preprocess_image(screenshot) + raw_text = pytesseract.image_to_string(processed, config='--psm 4 --oem 1') - # Preprocess image - processed = preprocess_image(screenshot) - processed.save("debug_processed.png") # Debug: Save processed image + collector_mutex.lock() + if rid in regions: + regions[rid]['raw_text'] = raw_text + collector_mutex.unlock() - # Run OCR - text = pytesseract.image_to_string(processed, config='--psm 4 --oem 1') + time.sleep(0.7) - stats = parse_all_stats(text.strip()) - hp_cur, hp_max = stats["hp"] - mp_cur, mp_max = stats["mp"] - fp_cur, fp_max = stats["fp"] - exp_val = stats["exp"] - - # Update latest data - latest_data = { - "hp_current": hp_cur, - "hp_total": hp_max, - "mp_current": mp_cur, - "mp_total": mp_max, - "fp_current": fp_cur, - "fp_total": fp_max, - "exp": exp_val - } +def _preprocess_image(image): + gray = image.convert("L") + scaled = gray.resize((gray.width * 3, gray.height * 3)) + thresh = scaled.point(lambda p: 255 if p > 200 else 0) + return thresh.filter(ImageFilter.MedianFilter(3)) - # DEBUG OUTPUT - print(f"Flyff - Character Status: HP: {hp_cur}/{hp_max}, MP: {mp_cur}/{mp_max}, FP: {fp_cur}/{fp_max}, EXP: {exp_val}%") +def _ensure_overlay(): + """ + Creates the overlay window if none exists. + If no QApplication instance is running yet, schedule the creation after + the main application event loop starts (to avoid "Must construct a QApplication first" errors). + """ + global overlay_window + if overlay_window is not None: + return - time.sleep(0.5) + # If there's already a running QApplication, create overlay immediately. + if QApplication.instance() is not None: + overlay_window = OverlayCanvas() + overlay_window.show() + else: + # Schedule creation for when the app event loop is up. + def delayed_create(): + global overlay_window + if overlay_window is None: + overlay_window = OverlayCanvas() + overlay_window.show() -# ============================================================================= -# OverlayCanvas (UI) -# ============================================================================= + QTimer.singleShot(0, delayed_create) class OverlayCanvas(QWidget): - """ - UI overlay that allows dragging/resizing of the OCR region. - """ def __init__(self, parent=None): super().__init__(parent) - - # **Full-screen overlay** screen_geo = QApplication.primaryScreen().geometry() - self.setGeometry(screen_geo) # Set to full screen - + self.setGeometry(screen_geo) self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_TranslucentBackground, True) - # **Load shared region** - self.region = shared_region self.drag_offset = None self.selected_handle = None + self.selected_region_id = None def paintEvent(self, event): - """Draw the blue OCR region.""" painter = QPainter(self) pen = QPen(QColor(0, 0, 255)) - pen.setWidth(5) # Thicker lines + pen.setWidth(5) painter.setPen(pen) - painter.drawRect(self.region["x"], self.region["y"], self.region["w"], self.region["h"]) - painter.setFont(QFont("Arial", 12, QFont.Bold)) - painter.setPen(QColor(0, 0, 255)) - painter.drawText(self.region["x"], self.region["y"] - 5, "Character Status") + + collector_mutex.lock() + region_copy = {rid: data['bbox'][:] for rid, data in regions.items()} + collector_mutex.unlock() + + for rid, bbox in region_copy.items(): + x, y, w, h = bbox + painter.drawRect(x, y, w, h) + painter.setFont(QFont("Arial", 12, QFont.Bold)) + painter.setPen(QColor(0, 0, 255)) + painter.drawText(x, y - 5, f"OCR Region: {rid}") def mousePressEvent(self, event): - """Handle drag and resize interactions.""" if event.button() == Qt.LeftButton: - region_lock.lock() # Lock for thread safety - x, y, w, h = self.region["x"], self.region["y"], self.region["w"], self.region["h"] - region_lock.unlock() + collector_mutex.lock() + all_items = list(regions.items()) + collector_mutex.unlock() - for i, handle in enumerate(self.resize_handles()): - if handle.contains(event.pos()): - self.selected_handle = i + for rid, data in all_items: + x, y, w, h = data['bbox'] + handles = self._resize_handles(x, y, w, h) + for i, handle_rect in enumerate(handles): + if handle_rect.contains(event.pos()): + self.selected_handle = i + self.selected_region_id = rid + return + if QRect(x, y, w, h).contains(event.pos()): + self.drag_offset = event.pos() - QPoint(x, y) + self.selected_region_id = rid return - if QRect(x, y, w, h).contains(event.pos()): - self.drag_offset = event.pos() - QPoint(x, y) - def mouseMoveEvent(self, event): - """Allow dragging and resizing.""" + if not self.selected_region_id: + return + collector_mutex.lock() + if self.selected_region_id not in regions: + collector_mutex.unlock() + return + bbox = regions[self.selected_region_id]['bbox'] + collector_mutex.unlock() + + x, y, w, h = bbox if self.selected_handle is not None: - region_lock.lock() - sr = self.region - if self.selected_handle == 0: # Top-left - sr["w"] += sr["x"] - event.x() - sr["h"] += sr["y"] - event.y() - sr["x"] = event.x() - sr["y"] = event.y() - elif self.selected_handle == 1: # Bottom-right - sr["w"] = event.x() - sr["x"] - sr["h"] = event.y() - sr["y"] - - sr["w"] = max(sr["w"], 10) - sr["h"] = max(sr["h"], 10) - region_lock.unlock() - + # resizing + if self.selected_handle == 0: # top-left + new_w = w + (x - event.x()) + new_h = h + (y - event.y()) + new_x = event.x() + new_y = event.y() + if new_w < 10: new_w = 10 + if new_h < 10: new_h = 10 + collector_mutex.lock() + if self.selected_region_id in regions: + regions[self.selected_region_id]['bbox'] = [new_x, new_y, new_w, new_h] + collector_mutex.unlock() + elif self.selected_handle == 1: # bottom-right + new_w = event.x() - x + new_h = event.y() - y + if new_w < 10: new_w = 10 + if new_h < 10: new_h = 10 + collector_mutex.lock() + if self.selected_region_id in regions: + regions[self.selected_region_id]['bbox'] = [x, y, new_w, new_h] + collector_mutex.unlock() self.update() - elif self.drag_offset: - region_lock.lock() - self.region["x"] = event.x() - self.drag_offset.x() - self.region["y"] = event.y() - self.drag_offset.y() - region_lock.unlock() - + # dragging + new_x = event.x() - self.drag_offset.x() + new_y = event.y() - self.drag_offset.y() + collector_mutex.lock() + if self.selected_region_id in regions: + regions[self.selected_region_id]['bbox'][0] = new_x + regions[self.selected_region_id]['bbox'][1] = new_y + collector_mutex.unlock() self.update() def mouseReleaseEvent(self, event): - """End drag or resize event.""" self.selected_handle = None self.drag_offset = None + self.selected_region_id = None - def resize_handles(self): - """Get the resizing handles of the region.""" + def _resize_handles(self, x, y, w, h): return [ - QRect(self.region["x"] - HANDLE_SIZE // 2, self.region["y"] - HANDLE_SIZE // 2, HANDLE_SIZE, HANDLE_SIZE), - QRect(self.region["x"] + self.region["w"] - HANDLE_SIZE // 2, self.region["y"] + self.region["h"] - HANDLE_SIZE // 2, HANDLE_SIZE, HANDLE_SIZE) + QRect(x - HANDLE_SIZE//2, y - HANDLE_SIZE//2, HANDLE_SIZE, HANDLE_SIZE), # top-left + QRect(x + w - HANDLE_SIZE//2, y + h - HANDLE_SIZE//2, HANDLE_SIZE, HANDLE_SIZE) # bottom-right ] - -# ============================================================================= -# Start Application -# ============================================================================= - -def run_flask_app(): - """Runs the Flask API server in a separate thread.""" - app.run(host="127.0.0.1", port=5000) - -if __name__ == '__main__': - # Start the OCR thread - collector_thread = threading.Thread(target=collect_ocr_data, daemon=True) - collector_thread.start() - - # Start the Flask API thread - flask_thread = threading.Thread(target=run_flask_app, daemon=True) - flask_thread.start() - - # Start PyQt5 GUI - app_gui = QApplication([]) - overlay_window = OverlayCanvas() - overlay_window.show() - - # Run event loop - app_gui.exec_() diff --git a/Modules/data_manager.py b/Modules/data_manager.py new file mode 100644 index 0000000..72e9521 --- /dev/null +++ b/Modules/data_manager.py @@ -0,0 +1,66 @@ +# Modules/data_manager.py +import threading +import time +from flask import Flask, jsonify +from PyQt5.QtCore import QMutex + +# Global datastore for character metrics +data_store = { + "hp_current": 0, + "hp_total": 0, + "mp_current": 0, + "mp_total": 0, + "fp_current": 0, + "fp_total": 0, + "exp": 0.0 +} + +# Mutex for thread safety +data_mutex = QMutex() + +# Flag to ensure only one character status collector node exists +character_status_collector_exists = False + +# Flask Application +app = Flask(__name__) + +@app.route('/data') +def data_api(): + """ + Returns the current character metrics as JSON. + """ + return jsonify(get_data()) + +def start_api_server(): + """ + Starts the Flask API server in a separate daemon thread. + """ + def run(): + app.run(host="127.0.0.1", port=5000) + t = threading.Thread(target=run, daemon=True) + t.start() + +def get_data(): + """ + Return a copy of the global data_store. + """ + data_mutex.lock() + data_copy = data_store.copy() + data_mutex.unlock() + return data_copy + +def set_data(key, value): + """ + Set a single metric in the global data_store. + """ + data_mutex.lock() + data_store[key] = value + data_mutex.unlock() + +def set_data_bulk(metrics_dict): + """ + Update multiple metrics in the global data_store at once. + """ + data_mutex.lock() + data_store.update(metrics_dict) + data_mutex.unlock() diff --git a/Nodes/Flyff/__pycache__/flyff_EXP_current.cpython-312.pyc b/Nodes/Flyff/__pycache__/flyff_EXP_current.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84bb7118265ce8dd1dc6b67c569c0f0bd3ccbf1a GIT binary patch literal 4652 zcma)9Z){W76~E8#z4+O&6NiKl;{17b$RA6?BqRirG!%kEV55c+ppd|x9)2(2;Mnec zF9o|!#fM6BCpN;mDygcfk-86=sI+dM`Vp#DLfRJ_%+`JI_aG+ENC1l+4`aA6XUUHl5%;~YYDITwj7;PMOhPNQPXiGc8SoQ)>KUl z$D?s9_6!7mBKGwSLN(Cu6Q|)*gh-*%#6%qHgdrHw5>Yv+i6xLu=Yq~fcQKO0& zl`pDMP~UrotmY*gCskNYeH+#x77#h&)rfvs)YRy-za(Aj@%b;QdQUgivD;^VLiQ`< zV+>(b)>N`V;nEJ4YRl^2c>p@UghNpg90no>0f%6j5So)5$`qj*D}#SaK&=;*Z4r)8v>W*gz>`k3mV~s}31F zJtc#uNBoKn?K%ht#Stg)u}^w|^9ix}$xWzWf~Vef>XIpOwbO9mRoj|96nltkgL8%B zv6vdxRmJDC8@x7`P%$R97=k24Vi8@Ej2c-{B(fZQ07p0MWL$D2!zRgzh@`}?#*8{C zDr-6&%et=4CUm1#!r+Wa;L}wMRv7gq8dxKar373GSlAFWRhMG&tm?BE9Pv_+DuqFq zCP^CEW%2r}fx|=RPvH0rs49)du_{L++IiwW+Iec#`I6s&cbI+-vq+PpCMczbJxyHQj0?8tC=p{>~NC2dQwql>iSW5IFL`MxvTIQUT5`y5t6>-?Aa z2K8T3gZGwwdxns1D5eL`GvGUA@2(Kx)-;*nDqqZA3h)+Y%6@5nN9YR3!AuenPN^)@ zhV-)M&rqzcaF8CkE1$FG9hkSIEi-`Q8_PVa*P7oHF3o<7KCx81JIyNml^$6AH#P+P z0W?@hS&2#!zlO)aQjb`7}16v4J*l4l}Lh_e!f&r`I3{(6m?P%pOpzK>(ofm@}1Dagg0q zoS(22TBT;K-AUAe^eraG4ul~_lHECnZhUqXU1QIos|-+h*HFARZ`C`^@l2Q5tF-2M z%mc=4s#R^;3R=0+0_+g?LzC&h?`vTA@RtmQzyg;UDoF`i1QHhN5m)hK*Hgao6g3M9+PZEqoUcXe#@X!yJqY}s?*%OVIK{(Gv64MAMY$zs9%@E2BMPW=_B2f#U zg~(IR-72J}1U&xZ^Fef>AE9|R&2E-ano=Ncm7{5zQ!Hj&(U=<|#J)}APa0l3l?cnA z8+$m&2gwgmyBuo)*WK%)6PM-yfq5FE)*vv&T>f4D##z8#XxCtFRfuAk;lOHGjl8YW zdk-k<(&BSCtV-ksLDMt9!?BYmkDt6CQcMv|pwQuMBa+)J>Txk;84`VhVO@P_U~>12 z+H#gjrD+C7l6u2>S=Jy`#CRBp@Ger3pyR8e@PP*iYjsHwc*wvf_pIM z9?ZJ~MeojncO>T>`K$N!1xWsO6+8nu&%guE;6lx#`sStmEBhbT4*>vdonnejU4^Fp zTvPuS!FzkNP5lp>Ccd+wjswh77Pail48MHgKrNkc34!)eQ)^Z{bSIQOG?AS+ojvnr z_Vn3o+qpv1WUguQd$^uJ^GYM&eYd0j;IQTHux+%R|B9;{ZRWpf=Apmtx^O^9uge$E z`M=XkxEOG;)?Pc{CS^5^BvLn&E9Xg&(n&O=MPbs+`ZS5iT$h%WuY}11Y2}*q5FS5l zC?%Lahe;;s<1tBC@HSGBXhDUH|-j!^OfKMYcxn}v%132<)` z=AS~fK3q7OmIey${v7-r{athR-;i#@-es>zG?&%50oq?=tiG(E;7*p)ywx#aqH0aP|?+T z{pbs>h7UXwyLj)j6O4_YV*b-u#RsUulnb8iRta8sVM!|OJ&}OPv(ut zIwreK8UBDu%rsUDua&IakB`GV(Mls%9Q>GJzT>P6_k0Ut9N(haZ;|WoNPNM)!7zcP Lo_{0KQaS$xqfK0< literal 0 HcmV?d00001 diff --git a/Nodes/Flyff/__pycache__/flyff_FP_current.cpython-312.pyc b/Nodes/Flyff/__pycache__/flyff_FP_current.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01a998e924f0a90afa1b0efc6fdfec650a52086f GIT binary patch literal 4696 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 PGt8k?$A1uMshs}-T~Te} literal 0 HcmV?d00001 diff --git a/Nodes/Flyff/__pycache__/flyff_FP_total.cpython-312.pyc b/Nodes/Flyff/__pycache__/flyff_FP_total.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f157ebd09f967d51fd0bfe5ac2bc672c1a2a641 GIT binary patch literal 4658 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 Q#~J3}s^dS1v{cUj0N*NZrT_o{ literal 0 HcmV?d00001 diff --git a/Nodes/Flyff/__pycache__/flyff_MP_total.cpython-312.pyc b/Nodes/Flyff/__pycache__/flyff_MP_total.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..815f1a6382a01804c92afd4a4fb8632197a5667e GIT binary patch literal 4658 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 Nk@@UD5G%Q#{{Un1#7h7G literal 0 HcmV?d00001 diff --git a/Nodes/Flyff/__pycache__/flyff_low_health_alert_node.cpython-312.pyc b/Nodes/Flyff/__pycache__/flyff_low_health_alert_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4439490fbdb5fbbe725112ed6450b621bb543eb2 GIT binary patch literal 8937 zcmcIqZ*UVycAt@E-GwzsjhMY96W8%yd8)BziA=gwzsDh^41|=8bBsS!BP)8}L;wmL^ zvMaXPWN-i8bsFCxo>01h(jz;q+RC(}m6APe6k=gCSIGSi_2J7i)FtOT)C?W)hP^aJ zc}CQ5LJA`(nvo@8FrFSC7fvK+gky3zu1*R)aT%#XFe%Bedjw&Ra59;Qs!5~>;e-%P zOs7=gVmO|Xg($HQspD_o2!sc>8%l)y?7 z%myWyf>Lx`kQ3ptxGWhx&nPnPH4#saL9f)b6jo(HjZVo(7*8T$8YLsLqC^uD26<0P zO-_Z?Xe1nur_EO2Fdk2aVPeq)WOz_TMbO7sfzshw!J8Qj&^p%}R%E>9fKzu3t9?l% z>+WIoTvVEnRYhlq)uXBC=wQSa1Vt4kK|KpX?Vo|vWeTKCg&Yzca!TABH4YkZ)g59a z=T$1iN(|&KQp;ZDLKWo81z!eb1$?>Zs7rLnGeQO2dgZB$GJ?J93nwmymCU=apMr2^ zNJtvhWEUi3d-00G0s>*qgi!*Q#*&v_5oRW%kx3x}VjGj8bvmg;)o3ySWMgSTLg9&t z;`Rwb#8I*^7kuNjJ$nku@)z_?O0^p2owL+OpzE{rU1-CJrc}oGqNo_|xhBQjYbv04 zw^$YPuHSaqF-yfNUu>Us*dqgl??npLZ`Q>&nP2vAtk(WEzrLYI87s_Gz$&aAfO#~U zyzJj^;JU@8G`}SJZcz?21w6N!_4cn+vU;`JtdDIozwBShDKQc&x$YLFGfRoqzDTgQ zqSaf1u|#X9(o~=#I6AoUHL!}l0!xfu1zlKag<_yaXO+~njC4*EqW}t`h%oNy{BY0o zbR29CtCwCmjiOUwlpcv7Sx)HOgq)m`Rg~7fimVka36#8DJZDDhN8PN==% z$b|`nu|@X)-Px!T1#|&Rep`;HdflWPhn0wcdj{+g)YCcBEOama5|iWO&^N&7j3UR! zb=Pzl0c&7L)+WNzbhju*;$cM*MFp<`jP%OaZw|hG{xnL)U?i~@pd=iRD(5k}DCY^> zo*&1cE5?&Eq5+^HfkiO^BpuUfg!MkNc@+S3m|KoDP5E2MzPds!bCvTI8rP8Boa2P$ zx^4No-CEu5d|kIz*S%DCC|}pF)%7pc4bFKA9G~YJFzJ>Y*G5WB8rSs6;O(JXL;2=z zt-1RbgTEa5$D#b;kOqIvp&a)eLe{8pji2ne9k><9Z#k@OIs8D%aedHo%{TAMvN>+s za{KO|o&WUw{nq=+gTBu``AU8|KZ;W4E#-$Py zca()1dq$RV*<7##00vv6S;Y(7hqVdTUj~}&UL~t914!81RSU$+IS#RgwIjm%igUZ{ zgdJ;ov3=HQkH5~Ie$x(s`(ut$!mI3B%K@G#|5(e5voqTyHUsj!%#`234Y_XLfU7G5 zjh9(ick7E(v&<|jRhTQIDS%Bbxbg=eL|6(s+k>Lv_(~OguXN!I17*f@;?z0e*g(&T z(PM#4x;v^AQ3>ILQ|E?HhXL#WUtIt?r{x3zMTB)lXM;(o&^c)6MJZ(xoRq#~JPA+? zI5mSI4dDX?;B7Do0LAnsR&|p>}E0j=72g=et%lU-j|Do9P?rn;+cxAjj>5Is>sjsk_~Lt9c=^*tr;9 zYTTRSI<2N`ZN71z*0?Xn?O*oQUVDH3{p`Vo-h1rddGC1hyI#|Fy|(1*o?|e3j8*_k zTg{yVLUmofdbd`+J73+URd+2_@6T5s(5eqCRloKCD&ClL zZA8(jRUZZlbI#e#3!4|4A9DxLOR)A;u!o@w8_ly5kgW%MvtTwCcC7*+DrT7)bSx&* z1c(AUf-4w70}g~uU3VEyGZYOWD=N6q3f4|jg1nlXn25{9;gi|2T85=&_A60I!($`^H*pHm0H-h*Q_`-X%CYAFo@eZLs2D7(--R@M*w}kP_ zc$DTIn(%}UN;G&x)&a6{uXRSZyeoS~<~SIvfWPGzs}}6fgJ1U1p4Hwp0E;6y8sK#o zI5}#v6caor!-;9?Q$ zz2hVrf(~K|0Wt+^8u&59&e1U_WVWtWjx`((;7%7H1D~Ye^2>zV6J&My?*ot6dmF}LGIeZ#XRmi2G&LG+D^!PnY^j)6os zuqBXa8T}(i48D{6AMZG8?kV22vjE#JR>2L*ay`iX3e2_iewC)~(7^yl#EAx>lOakX zao1GxqCA>JM{qR}ZWR&DNvI~W4KTKBbv_}_h@cN*94mwXeJq#N5cNr@$~2b|wZ-oZ z-03aIh@^ej-uU4gg(l&4_pR=~I<#1u4|Hqr*VH|CqQLv}{0@!Zk>~el{GP>ljz0u- z*N)F0|L|l{klV7U$6Wgcf}Dea{@;M3f|$7_Y8A}VNW=`m&0Z}8FvGz>1?mGD`&>r- zxQGqS*!^m<`)|Yb1;PnL0B8srR~yu6sL8aH+2zuHn6dIL$Tl*m=IwL+)^2WJXn)M@ zBD;x>L&ty@-_sIsnG!|aBZ^Z=DFt9J>bxkv2cei^ONA&($p~EKFf^jWIP1X~HhY8> z0^#}UmBnjLM2^Qr@eXaM4?b_cZRifhV1%i2>>Clq1@`VVC14mYHTqWd!$XQ7|1;@`q zZpkZgS0S1rL5wWql{}F1B)(D!@fBXLCpWRx_dk3^%j_CUB@*d9@$gtjG}*DiO)b+v zSU`wuZS*WioWK=4{RrM7Ghyr?2z9A=BHED^`}Bh|0MNol2f!TbEHW8UBxZ=6e)E1$ zA_nE?Sh$3^*3emJPYJ&YiM!`s6@P$PtWA;!c9p+ZX*<3!JFGZsl2j%6CMe1O*Yv$= zo7P@q3_HealG0^IG|bi8J?!(?t}DkShTY{g$>fH;Bsfnu1$8DeCU)vn#ydiSkL2<{ zN@}|@JUHbzQb{T^jibazGNPTQ62dGAa-JF=4+NYmzlMQUB*=)oA&kgIh*Qpz2x+FW zFNJWtKsbv7raHYBC>ZQj>y=}&JPk1vaLR!+Q`rkeVW?OPI5W)Nj#rfo)7h~X@-E0R z@2DzIDFF{b8uUkaRJ`*#AC@FBGATzcK-5on;}Qwe>Q(rYIPoRrNOEc{DNf0WlL?-47p^nMqOwnsJtep6aMQ;EPnK6RKFWQb3q!oRr`Xf zaj)jMgH}u-yJO*{g{k|aIqvnVtsZGX^Z7FpPl4APcadR5+rq`@nNdz$H?a#OWl0fH#9fQ92L7ZxKT6 z5GhmBI*;Ut9K9%u5Q`5745v}|5C)_dVO%`wig!ea1(GTsu;PgijJ4#eT$$0@>Jt#TX)tKXvAOZG|0y#nDeMe4NUTe*FI5D-ZU6mcGsy&2{&j3w+MEr_d}c zbpGYB8}BWu_uu=Q^quX$huUu+ z5PZLm1yw=17b*=AhOPUY*imtEetIeQ84is0*#D)Stu7a zHg5RkZi*MqgQ_)`_7^P>fcceq%`!(dd>7)sxPl1#96GJ6`~i|-7T3YaF7usJX&kLDx1>itsn9);^Y6_04;1{hH>++`J@yOOb&zSux4-_-+w`Ijfgzq)0^Z%y_fu4F z!*RJN1Aa2F!5N-Btm6dx`uEtuuTIf9G3_Yu#}m3sfQO6C!-9qY=pTDtOS6z&VbNN8ue# z)ZM{T{R3jK=i~q)fcz(z@&`D>2NY6phGCW{5Sb=AgNtV&BMbPpaStVsjx%!c#xX6p zhZgFNTnQHZFJBn~ReH$O7V0)bxq0hDrtvxBVGloTaI)K= 4: + # line 1: HP + hp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[0]) + if hp_match: + hp_current = int(hp_match.group(1)) + hp_total = int(hp_match.group(2)) + + # line 2: MP + mp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[1]) + if mp_match: + mp_current = int(mp_match.group(1)) + mp_total = int(mp_match.group(2)) + + # line 3: FP + fp_match = re.search(r"(\d+)\s*/\s*(\d+)", lines[2]) + if fp_match: + fp_current = int(fp_match.group(1)) + fp_total = int(fp_match.group(2)) + + # line 4: EXP + exp_match = re.search(r"(\d+(?:\.\d+)?)", lines[3]) + if exp_match: + val = float(exp_match.group(1)) + if val < 0: val = 0 + if val > 100: val = 100 + exp_value = val + + return hp_current, hp_total, mp_current, mp_total, fp_current, fp_total, exp_value + + def process_input(self): + """ + Called periodically by the global timer in your main application (borealis.py). + """ + # Grab raw text from data_collector + raw_text = data_collector.get_raw_text(self.region_id) + + # Parse it + hp_c, hp_t, mp_c, mp_t, fp_c, fp_t, exp_v = self.parse_character_stats(raw_text) + + # Update data_manager + data_manager.set_data_bulk({ + "hp_current": hp_c, + "hp_total": hp_t, + "mp_current": mp_c, + "mp_total": mp_t, + "fp_current": fp_c, + "fp_total": fp_t, + "exp": exp_v + }) + + # Update the node's text fields + self.set_property('hp', f"HP: {hp_c}/{hp_t}") + self.set_property('mp', f"MP: {mp_c}/{mp_t}") + self.set_property('fp', f"FP: {fp_c}/{fp_t}") + self.set_property('exp', f"EXP: {exp_v}%") diff --git a/Nodes/flyff_low_health_alert_node.py b/Nodes/Flyff/flyff_low_health_alert_node.py similarity index 100% rename from Nodes/flyff_low_health_alert_node.py rename to Nodes/Flyff/flyff_low_health_alert_node.py diff --git a/Nodes/Organization/__pycache__/backdrop_node.cpython-312.pyc b/Nodes/Organization/__pycache__/backdrop_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62802410148c5de35adaa22394d7e0d190410f0c GIT binary patch literal 8458 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 HOV;rpO{%2x literal 0 HcmV?d00001 diff --git a/Nodes/backdrop_node.py b/Nodes/Organization/backdrop_node.py similarity index 100% rename from Nodes/backdrop_node.py rename to Nodes/Organization/backdrop_node.py diff --git a/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc b/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc index 4921b72d000f750be702ef1c63905e8b7b4e7fa7..379ae78e8783b7ba780812108af51be7f33985c3 100644 GIT binary patch literal 4999 zcmc&%O>7&-6`m!R&4Ye#lO$C2f~I1(Bwwk%mbSeEJ{PFNL-6=x+;UUHe) zWhD|z!w8I4MI1y)5m-SQL_L*;1H>s>z&#YrF+eY5B}2+WK?~Fu-JIAh(x8XF8FER< zKZ%MSy1>49JM-r4o4;?q_vdgpfZ+N4&we}g?^=ZZ&OV&SQ&#SefN~uX6h%ZJE=`CE zP`Wi&!X0%BtWMO#geU6Zx<~USd{JKlN3noh=nNv>tBCki_f2=nU(}y*MX+*2KqxSz zWhN)3foX**F;k_|1yeE8hBTrRRq7g1XQfwEYQ*(qq%R;nFAXb(l+>koGL<%sgL>Lz zWjAXXpsHf3hNL8=^8-JWs5%9mz)&KJsmL*1)6|%$Q*Jw|(zs6IF-6ld(iBxD42deU zlBv#_(j?Urk~yuIWfupeltK+~95b9p1gV2@NDA82d~-M`Yg&nMXVNq_{mVHYA{QnL-aT2e>qH`|dN7Lxr= zWM-42%``h&wS*XVRKFaJG>}HpM4CTtxmkt^n4zklR>!VdR@VhQwpZ&NjkG$mDxeIC zw2j!FK}vO+?K_`@!>T7$=?RE2;2d#&G^IE|fu`xRD(TKjr_(77j=QNovcEXcbpOb9 zO{Z+v@MzXIJlZGq9PEK3C9=b4yAsfF4jRst8f@1jR187IP`Sdb&ZTTO9Pz9V1oYl( z*FC(;H@<}dZm=V^@jE8o01**wVa^sZwlHf8)08blq{j9b>6A(-+p0E}<#;k~$}(lM zYwv#8YZcaHbuMn024#EC#xWW<8M@j2!MT{4G64zg8if#>h}QuOr_71p+sEhFI%UqYjp)2vq6=A}EzyOx8J%}ibdT@zXmq}9MCaQQ zUDyh5i7vd&=+(WQ6k(mR6>JDkp@D zNODG|#gmMAIh`Xe%JwK1Q_f!8VAz|j?;0o1MaGT&2jS&9x7uJQc7t`w*0S2J{=V^U zX4xOvV8?TVfQVo_V8BIX^54NtVA43;ESj3MT~xKbhN{rmwC#>3P1`d?^>oViOlrDf zM(Su6^tUmvSdQ_wpxK_1m%wyNj-@G8p%Vt()J?b;0*OtvpqFbWH`Rh(s`bPDCUf%1 z9V8SJg22$Jtbt)kG4faMI#NWw)=_T|5*D2;L3OdX7&oY zhtb~WS38dvJCCn)_Ab|*DR%ZQzJ7b`PZ$4i@sF=9)L(CTr{%r2LTB&1r%-ohZPznJ zymLOVF#K4pv1Q(WH&`>DezR|}{>}coEp68`?_>(QkKIb&9{Y4|x#i{g;V)`i)}NM^ zT7T2=>yBH(^6taSPajzbthaY8&V6+G{mXe}c~{SJ`=JF4Sl{~8|F?fz`$zuw{e?Y8 zZ>hIWemcF}+CP8xi`v%p=Jv(2ADw^yeE#I}&hF*rgA4AphUUfkcaJaaUL0U*!@Ipp z1Box2wNf2(a{ ztcmUM`|R2R&R)5^X39VhZIuUDAv;G8Jr$T1puKX9RE;dbu^5NeoC~fixTbP&LFKSn zVYo9q(XIAkn&q9uPX2+Kqm26SaK1!<@x}z3+n^)q3B#!PqYWVZ;9OAvn_ic z!IqcX`?h5dd4h$(KKP-#8cQY=31^iGVhsQnR*3}6vYPl#WO#NtrRfujCYkXBWZ#h7 zX7n_b5(;Qas>GUEBHcAn=5oN)BAbxVM_+LYv$lnU zlx0v^koCA12I&lqIaCFY#n z_hgjXY$5#^vRP6Z05&-?84+qa($mBcIPLAAPlm z56z47Wa0E;=aRTY@`H>fHm%}4MZ9P2sgAYQom*r>>sPh7Gw=XmfAC&2XR>WY+yRkYzg{%L$#N zHKvDT`SrA-m0N0nx)Zp6;*)WeLaTq|{OLh?> zwojIc9)oP^81%D)=OLyuR7p*bnUvv^T|1f?GmmwTnHS>(wp}@!@}1+OGWeF`o09vx zfl9vleTIKf8u|$u14k@gV;_inZb1;f5`BVre+Lo*Un1#C^z2_z&jWE>5RNR){R6R* G`}q$jc){`j literal 6232 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 J2)7EU{{r5Bza#(v diff --git a/Nodes/__pycache__/widget_node.cpython-312.pyc b/Nodes/__pycache__/widget_node.cpython-312.pyc index 0476609753adf0380190d39f56f7b2ce7e445b56..4f511ec0b68cf5338e944c7697b85f05393eb5bf 100644 GIT binary patch literal 5574 zcmeI0Uu+b|8Ng@vcJJ1{KhR+O?})PJq{3^034}N#212n7i2`Q`sDG|htM%<-4$ixm z**#*PjN}KTI1*8)(iT5PeX?a9sy?AU^|4Z4e6`(kR>@PI`bK41p+5EdW_Ryy?Sr}s zB2tvGnEQ6VZ)az|Z{|1O@YiIroj}?A{Ew3dQiObi4?z)qVbcNO9?^-;70DEr<2aQ0 zB0m+$ML5EfvqX>FCc0p7%MtILoWNQ|XpJ_timWvTtx{8Kl(n`&YkN~`Y$mQG{(;>o zoGYEyY$I#w2FTHi&WJ^gy!R!q=KY7wqab_?gEdHw(@Bokxm-l&bHY5i%E2I|oTx{2 z;Wn3x>M*!mY@SSWIq4EnVs2u@nwqdqThsWRO4mf$G-nPLwTZ!kHJC@0YN8?;n8|3D z=bDBQ1sNT%xd7E~xqAd1=M^UyA((Y_B^g2XBEI7uLtwdeXE|&~SasIBPss*#)sH)qp>AGrpUrM3ud2z}x%Wg-BT6x2^ z)sG7Lq~WN>O~Z7ScvpZCU3J@3HDA;~tJ>%{dG0^`XWqVYfm+vLwCZVyzE&*QS1ICamTyEIBE@GZ1|Hm!Kp%n2z{}2zn{=pV+Ed_UU2`-gLVpBSuE>1oro5OM zx_VP9mJJ&}Bg-<~1HW_^RFzD~IjsT#Bp|j7)g0OAd9l_rQ0*C59A52tb3VoV&|8&y z7f!BA1GEE-LY_cqFi&iNup>|K^k=htD{lm=mY#XKew6&`2qCv3vytlovf$f633|CD zIQ4xtE9m^PA3vMBBhL!gTkNn|AjW+mu;a*`qXL;>e@kMP;_Ge#IAj!V8Y~bL*B5My z-L_Sxc|$E()KN`s%2*QJsJ{<(4lSJ*K);!X3gX<^d+)}tZ+wyY+?7ljsv5mfiwZ0S8z7v1%d*^OyBXg*hIaN=K|$M=9UmQ2vp+dM#&$Sc$rR`3CVg1sjbc$%mpF>EvT^^N@)kW} ziLpsQ1$y}LZL+ah^0M439`|J>rM4}QULf3_L-+aZU5V~~5+xF3R!X(v=Z)M*dMZUXj!K>-=)i&j7nO}-V- ze+=COK<`S|y85eK{fqlnyN2cy4WJ)im-@HQwZ7WE&p@a9(DArj=qu=|mK+UvImA%~ zUt|@Yhjaj|16aYi6haYO;8>+mQAg2{tte8OQH1u-9z`S9jQout+wBdZD37WMYdT1H zear16swcd^gCRa266xao=rV#F%8t_ASk_~a6wIoUylhQQ77ahob)#dZHc>RrC_Fs| zokB9}XpRve=_E8YAt}M~Sl>TS1yZwr>lrBS@113!Jyoe^Vc-kv!MlGMTakL!qz}F$ zB%MMBY(r9#c@vPtoYjg+q4~FB(sA^K3>73F-TP|YO0`>A99ZpsV?Nnn(us9R`GHAK zW>Uz@Ats#y&+dpxY=vXRU{z<*;jK&>Xl4@HKYJ#frB=Drnn{zWYA`9VdR`#k--;v_wgxkVL5LB_-x%TbR5wp{)(k#ti6NsKL7;eL}Xy zzTpR|4!860f;|q^|H!sJOMQ~M`_YR0+M0Bj?R0kAKDT)Afw}U_36_85za!Yak8|Dz zyEya8lkd|ZZ?r<)W%P^;6}xBEI`>yQ_b(<^JKva(H&8dcF75vTbx%fJ$jc$r{T!pV zlk|(Nc&y%oe~t93fjSXxf+=3?Z$=&3XBEkfkHdZREY(WaE;h*dv0!j|QLiw3k`_ Ki{LN&$bSLshFHM> literal 4584 zcmbssZEO_Bb@uM|Zfl z@N?Xs4rIiHC=eexOIYA0VWKXq_}%%5Ag2a_8mgj(IF$l5Tt%hVrC8(%PK*f-J*}B~ zJjZmv8?b-N$j$yJ#in#?I6IdCY9~_9rLy`kOIeq+bc$(K&KR85lUGmY7Lu+2F7%Z? zo*e|_Jj|mAs^CbezpAZXVQULjBB3K{`LLzjL#6+iV2)1Rp`RsW;JBJ(Q zPf7~3fvlo0Xqnlxu4I9#%%`krMN{<5gwB}G6ph0RgSpH^ZX&m!WMFy^pZTqf6U?4@ zu8R}Ek)%*1Z4huY-?Kjf_>FLfSmi;>Q-my%Uq%R7DUAs5JrgRkl@TValvd^(;qpKk zVZN7g_$wS!aKOJ4mIT-hcHi2V$byRk3q7DK0e4Iw*GViK&j(UjoX{cBDrb6rKNis^(rO3)F_ zxmn#X&>jv|)l@cRsj3r(BLRGN&T=F~K^VlNh$r!w_?|Jp6EOAkq#@(%u&O50nrW)4 zi6^2cH@-b^rvEZr#Th+msi$*>4)@Nyj2FYa?4GEK$J;Y|-Kd2b`Sz{%r^gcrJdT)G z0DNn0noQ{inl3&zG_1x4&JS~?oSKqZl6pF=s`mw>33b7ncFJf5$W6}y z-epq(@Xbx~)aMiFxk?R{3sj5}Xzf>^TR%;6)bKYYhqlH~|Zm+vKcxO}MlE16*^n zv)+n;lR_M=I4J|yVZJ3F^Dp^XU@5@FB@tZ7i?y(n*I2OPbT4r2&?b)UT${2mlO9C8 zSjgSq%PO_!Z zRr;A772Ly^H0g(xL+;9+F029mKx5We(l}hDaZ{PAk@GdZRAUJwmi`}&VRok{aiwPs zRVOaZ?5d1-ST)BBttS1?RtvjUgOJ`5&qsgylYzqt?Qs6c;l3*e_c|os$#bxhoYo92 zY3YX9uKLOz#9qv9h%iF*edHpX46_ypU$NjM1CA}8dIj4gc<@?H}$NU zvQpP{r!I-*0VHJgTFOjKq=ABYL!Gd)9tAQ$_?lNuIMMQiv^Jrq9qO?=)HISRY(E_f z7_Y*H28Jj{E+qk%J2v>rM$|>jOrlzzh{iH*gY4Q2@NvzI5AL7B)s6!0h z#Z65ZaY|_`4Em(g6I$}Bfg!^QTubTmPCZmas+$$nQcWS7Kq?Db&odlPn9HR>BA8%KcbagevI#t!ajLg|*>1AYh@u}t za2NqUhI;Q9%7h zhkYB(kyfb?S#KlZ|0bwzBH3Ly}&?f7#d?y@^~?F?8mZm)<|V71LJf|;5VNaK#F?{`+=Nq-Es>(@BIY~n(Y91;rFPndzt+q z``hfJ?&A+HJc@qr3wnZU6bl=e)ePh`Fqat}2zDaCZQXEzI~iRFFdH?n#4>_pJ z@X=>h*J0EPNt;f+{BZg+2)?-%`|b}X22~mhR30*LZ?=12iNF_Rp-p@*sKp*gD7Ck%jr8$u2h zleu)xF!u3bPBbp!@Y_fTy2kL#6oHUZm}J_bu;UPCp!4rG5M{Q$-tIrTtYgc=-o~=qf#0 zj>F4W3bkL**W63n3cEb#16(Nt@1Y6;4X$!;<2z38H0DwSwowh zzF-*`QJm%~N>4JEH7zY`nQlf^s>i%*Tb0gCp10lC5%*3m-U(RN-4)+X4*kCSJo};X zE_@2`ZypD*?iU2%F;O0qV~@!j8^IldbbDcgK)2Da2}0lPkqv_V#)!X3IClHQ*91Dg G?0*2CM=T%! diff --git a/Nodes/flyff_character_status_node.py b/Nodes/flyff_character_status_node.py deleted file mode 100644 index d48e9dd..0000000 --- a/Nodes/flyff_character_status_node.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 - -""" -Standardized Flyff Character Status Node: - - Polls an API for character stats and updates values dynamically. - - Uses a global update timer for processing. - - Immediately transmits updated values to connected nodes. - - If the API is unreachable, it will wait for 5 seconds before retrying - and log the error only once per retry period. - - Calls self.view.draw_node() after updating the node name, ensuring - the node's bounding box recalculates even when the API is disconnected. - - Port colors adjusted to match earlier styling. -""" - -from OdenGraphQt import BaseNode -from Qt import QtCore -import requests -import traceback -import time - -class FlyffCharacterStatusNode(BaseNode): - __identifier__ = 'bunny-lab.io.flyff_character_status_node' - NODE_NAME = 'Flyff - Character Status' - - def __init__(self): - super(FlyffCharacterStatusNode, self).__init__() - self.values = { - "HP: Current": "N/A", - "HP: Total": "N/A", - "MP: Current": "N/A", - "MP: Total": "N/A", - "FP: Current": "N/A", - "FP: Total": "N/A", - "EXP": "N/A" - } - - # Set each output with a custom color: - # (Choose values close to the screenshot for each port.) - self.add_output('HP: Current', color=(126, 36, 57)) - self.add_output('HP: Total', color=(126, 36, 57)) - self.add_output('MP: Current', color=(35, 89, 144)) - self.add_output('MP: Total', color=(35, 89, 144)) - self.add_output('FP: Current', color=(36, 116, 32)) - self.add_output('FP: Total', color=(36, 116, 32)) - self.add_output('EXP', color=(48, 116, 143)) - - self.set_name("Flyff - Character Status (API Disconnected)") - self.view.draw_node() # ensure bounding box updates initially - - # Variables to handle API downtime gracefully: - self._api_down = False - self._last_api_attempt = 0 - self._retry_interval = 5 # seconds to wait before retrying after a failure - self._last_error_printed = 0 - - def process_input(self): - current_time = time.time() - # If the API is down, only retry after _retry_interval seconds - if self._api_down and (current_time - self._last_api_attempt < self._retry_interval): - return - - self._last_api_attempt = current_time - try: - response = requests.get("http://127.0.0.1:5000/data", timeout=1) - if response.status_code == 200: - data = response.json() - if isinstance(data, dict): - mapping = { - "hp_current": "HP: Current", - "hp_total": "HP: Total", - "mp_current": "MP: Current", - "mp_total": "MP: Total", - "fp_current": "FP: Current", - "fp_total": "FP: Total", - "exp": "EXP" - } - updated = False - for key, value in data.items(): - if key in mapping: - formatted_key = mapping[key] - if str(value) != self.values.get(formatted_key, None): - self.values[formatted_key] = str(value) - updated = True - self._api_down = False - if updated: - self.set_name("Flyff - Character Status (API Connected)") - self.view.draw_node() # recalc bounding box on connect - self.transmit_data() - else: - if current_time - self._last_error_printed >= self._retry_interval: - print("[ERROR] Unexpected API response format (not a dict):", data) - self._last_error_printed = current_time - self.set_name("Flyff - Character Status (API Disconnected)") - self.view.draw_node() # recalc bounding box on disconnect - self._api_down = True - else: - if current_time - self._last_error_printed >= self._retry_interval: - print(f"[ERROR] API request failed with status code {response.status_code}") - self._last_error_printed = current_time - self.set_name("Flyff - Character Status (API Disconnected)") - self.view.draw_node() - self._api_down = True - except Exception as e: - if current_time - self._last_error_printed >= self._retry_interval: - print("[ERROR] Error polling API in CharacterStatusNode:", str(e)) - self._last_error_printed = current_time - self.set_name("Flyff - Character Status (API Disconnected)") - self.view.draw_node() - self._api_down = True - - def transmit_data(self): - for stat, value in self.values.items(): - port = self.get_output(stat) - if port and port.connected_ports(): - for connected_port in port.connected_ports(): - connected_node = connected_port.node() - if hasattr(connected_node, 'receive_data'): - try: - connected_node.receive_data(value, stat) - except Exception as e: - print(f"[ERROR] Error transmitting data to {connected_node}: {e}") - print("[ERROR] Stack Trace:\n", traceback.format_exc()) - - def receive_data(self, data, source_port_name=None): - # This node only transmits data; it does not receive external data. - pass diff --git a/Nodes/group_node.py b/Nodes/group_node.py deleted file mode 100644 index 9b937b5..0000000 --- a/Nodes/group_node.py +++ /dev/null @@ -1,21 +0,0 @@ -from OdenGraphQt import GroupNode - - -class MyGroupNode(GroupNode): - """ - example test group node with a in port and out port. - """ - - # set a unique node identifier. - __identifier__ = 'nodes.group' - - # set the initial default node name. - NODE_NAME = 'group node' - - def __init__(self): - super(MyGroupNode, self).__init__() - self.set_color(50, 8, 25) - - # create input and output port. - self.add_input('in') - self.add_output('out') \ No newline at end of file diff --git a/Nodes/widget_node.py b/Nodes/widget_node.py deleted file mode 100644 index eff1ac2..0000000 --- a/Nodes/widget_node.py +++ /dev/null @@ -1,155 +0,0 @@ -from OdenGraphQt import BaseNode -from OdenGraphQt.constants import NodePropWidgetEnum -from OdenGraphQt.widgets.node_widgets import NodeLineEditValidatorCheckBox - - -class DropdownMenuNode(BaseNode): - """ - An example node with a embedded added QCombobox menu. - """ - - # unique node identifier. - __identifier__ = 'nodes.widget' - - # initial default node name. - NODE_NAME = 'menu' - - def __init__(self): - super(DropdownMenuNode, self).__init__() - - # create input & output ports - self.add_input('in 1') - self.add_output('out 1') - self.add_output('out 2') - - # create the QComboBox menu. - items = ["item 1", "item 2", "item 3"] - self.add_combo_menu( - "my_menu", - "Menu Test", - items=items, - tooltip="example custom tooltip", - ) - - -class TextInputNode(BaseNode): - """ - An example of a node with a embedded QLineEdit. - """ - - # unique node identifier. - __identifier__ = 'nodes.widget' - - # initial default node name. - NODE_NAME = 'text' - - def __init__(self): - super().__init__() - pattern = r"^[A-Za-z0-9]*$" - placeholder = "" - tooltip = "Valid characters: A-Z a-z 0-9" - is_case_sensitive = True - checkbox_label = "Use Parser?" - - # create input & output ports - self.add_input('in') - self.add_output('out') - - # create QLineEdit text input widget. - self.add_text_input('my_input', 'Text Input', tab='widgets') - - tool_btn_kwargs = { - "func": self._callback, - "tooltip": "Awesome" - } - kwargs = { - "validator": { - "pattern": pattern, - "placeholder": placeholder, - "tooltip": tooltip, - "is_case_insensitive": is_case_sensitive, - "checkbox_visible": True, - "tool_btn_visible": True, - }, - "checkbox_label": checkbox_label, - "tool_btn": tool_btn_kwargs, - } - node_widget = NodeLineEditValidatorCheckBox( - "src_path", - pattern, - placeholder, - tooltip, - is_case_sensitive, - checkbox_label, - checkbox_visible=True, - tool_btn_visible=True, - widget_label="src_path", - parent=self.view, - ) - node_widget.get_custom_widget().set_tool_btn(**tool_btn_kwargs) - self.add_custom_widget( - node_widget, - NodePropWidgetEnum.LINEEDIT_VALIDATOR_CHECKBOX.value, - "widgets", - **kwargs, - ) - - kwargs2 = { - "validator": { - "pattern": pattern, - "placeholder": placeholder, - "tooltip": tooltip, - "is_case_insensitive": is_case_sensitive, - "checkbox_visible": False, - "tool_btn_visible": False, - }, - "checkbox_label": "Check In Luggage?", - "tool_btn": tool_btn_kwargs, - } - node_widget2 = NodeLineEditValidatorCheckBox( - "dst_path", - pattern, - placeholder, - tooltip, - is_case_sensitive, - "Check In Luggage?", - checkbox_visible=False, - tool_btn_visible=False, - widget_label="dst_path", - parent=self.view, - ) - node_widget2.get_custom_widget().set_tool_btn(**tool_btn_kwargs) - node_widget2.set_checkbox_visible(False) - node_widget2.set_tool_btn_visible(False) - self.add_custom_widget( - node_widget2, - NodePropWidgetEnum.LINEEDIT_VALIDATOR_CHECKBOX.value, - "widgets", - **kwargs2, - ) - - def _callback(self): - print(f"YOU HAVE CLICKED ON '{self.NODE_NAME}'") - - -class CheckboxNode(BaseNode): - """ - An example of a node with 2 embedded QCheckBox widgets. - """ - - # set a unique node identifier. - __identifier__ = 'nodes.widget' - - # set the initial default node name. - NODE_NAME = 'checkbox' - - def __init__(self): - super(CheckboxNode, self).__init__() - - # create the checkboxes. - self.add_checkbox('cb_1', '', 'Checkbox 1', True) - self.add_checkbox('cb_2', '', 'Checkbox 2', False) - - # create input and output port. - self.add_input('in', color=(200, 100, 0)) - self.add_output('out', color=(0, 100, 200)) \ No newline at end of file diff --git a/borealis.py b/borealis.py index 18cc76a..dba6bee 100644 --- a/borealis.py +++ b/borealis.py @@ -1,11 +1,29 @@ # -*- coding: utf-8 -*- #!/usr/bin/env python3 -from Qt import QtWidgets, QtCore, QtGui import sys import pkgutil import importlib import inspect +import os + +from Qt import QtWidgets, QtCore, QtGui + +# ------------------------------------------------------------------ +# MONKEY-PATCH to fix "module 'qtpy.QtGui' has no attribute 'QUndoStack'" +# OdenGraphQt tries to do QtGui.QUndoStack(self). +# We'll import QUndoStack from QtWidgets and attach it to QtGui. +try: + from qtpy.QtWidgets import QUndoStack + import qtpy + # Force QtGui.QUndoStack to reference QtWidgets.QUndoStack + qtpy.QtGui.QUndoStack = QUndoStack +except ImportError: + print("WARNING: Could not monkey-patch QUndoStack. You may see an error if OdenGraphQt needs it.") +# ------------------------------------------------------------------ + +# Import your data_manager so we can start the Flask server +from Modules import data_manager # --- BEGIN MONKEY PATCH FOR PIPE COLOR (Optional) --- # If you want custom pipe colors, uncomment this patch: @@ -59,34 +77,78 @@ from OdenGraphQt import NodeGraph, BaseNode def import_nodes_from_folder(package_name): """ - Dynamically import all modules from the given package + Recursively import all modules from the given package and return a list of classes that subclass BaseNode. """ imported_nodes = [] package = importlib.import_module(package_name) - for loader, module_name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + "."): - module = importlib.import_module(module_name) - for name, obj in inspect.getmembers(module, inspect.isclass): - if issubclass(obj, BaseNode) and obj.__module__ == module.__name__: - imported_nodes.append(obj) + + # Get the root directory of the package + package_path = package.__path__[0] + + for root, _, files in os.walk(package_path): + rel_path = os.path.relpath(root, package_path).replace(os.sep, '.') + module_prefix = f"{package_name}.{rel_path}" if rel_path != '.' else package_name + + for file in files: + if file.endswith(".py") and file != "__init__.py": + module_name = f"{module_prefix}.{file[:-3]}" + try: + module = importlib.import_module(module_name) + for name, obj in inspect.getmembers(module, inspect.isclass): + if issubclass(obj, BaseNode) and obj.__module__ == module.__name__: + imported_nodes.append(obj) + except Exception as e: + print(f"Failed to import {module_name}: {e}") + return imported_nodes def make_node_command(graph, node_type_str): """ Return a function that creates a node of the given type at the current cursor position. + For the Flyff Character Status Collector node, check if one already exists. + If so, schedule an error message to be shown. + + Also ensure that node creation is delayed until after QApplication is up, to avoid + 'QWidget: Must construct a QApplication before a QWidget' errors. """ - def command(): + def real_create(): + # Check if we are about to create a duplicate Character Status Collector node. + if node_type_str.startswith("bunny-lab.io.flyff_character_status_node"): + for node in graph.all_nodes(): + if node.__class__.__name__ == "FlyffCharacterStatusNode": + # Show error message about duplicates + QtWidgets.QMessageBox.critical( + None, + "Error", + "Only one Flyff Character Status Collector node is allowed. If you added more, things would break (really) badly." + ) + return try: pos = graph.cursor_pos() graph.create_node(node_type_str, pos=pos) except Exception as e: - print(f"Error creating node of type {node_type_str}: {e}") + QtWidgets.QMessageBox.critical(None, "Error", str(e)) + + def command(): + # If there's already a QApplication running, just create the node now. + if QtWidgets.QApplication.instance(): + real_create() + else: + # Otherwise, schedule the node creation for the next event cycle. + QtCore.QTimer.singleShot(0, real_create) + return command if __name__ == "__main__": + # Create the QApplication first app = QtWidgets.QApplication([]) - # Create the NodeGraph controller. + # Start the Flask server from data_manager so /data is always available + data_manager.start_api_server() + + # Create the NodeGraph controller + # (the monkey-patch ensures NodeGraph won't crash if it tries QtGui.QUndoStack(self)) graph = NodeGraph() graph.widget.setWindowTitle("Project Borealis - Flyff Information Overlay") diff --git a/debug_processed.png b/debug_processed.png deleted file mode 100644 index 5e945f82cbead805952d77f447c1733033ae7cd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2585 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 y}Cwxk34M!E`g~a2?wNQ2O_Jt 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