Restructured the project and optimized several nodes.
This commit is contained in:
BIN
Nodes/Flyff/__pycache__/flyff_EXP_current.cpython-312.pyc
Normal file
BIN
Nodes/Flyff/__pycache__/flyff_EXP_current.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/Flyff/__pycache__/flyff_FP_current.cpython-312.pyc
Normal file
BIN
Nodes/Flyff/__pycache__/flyff_FP_current.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/Flyff/__pycache__/flyff_FP_total.cpython-312.pyc
Normal file
BIN
Nodes/Flyff/__pycache__/flyff_FP_total.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/Flyff/__pycache__/flyff_HP_current.cpython-312.pyc
Normal file
BIN
Nodes/Flyff/__pycache__/flyff_HP_current.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/Flyff/__pycache__/flyff_HP_total.cpython-312.pyc
Normal file
BIN
Nodes/Flyff/__pycache__/flyff_HP_total.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/Flyff/__pycache__/flyff_MP_current.cpython-312.pyc
Normal file
BIN
Nodes/Flyff/__pycache__/flyff_MP_current.cpython-312.pyc
Normal file
Binary file not shown.
BIN
Nodes/Flyff/__pycache__/flyff_MP_total.cpython-312.pyc
Normal file
BIN
Nodes/Flyff/__pycache__/flyff_MP_total.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
108
Nodes/Flyff/flyff_character_status_node.py
Normal file
108
Nodes/Flyff/flyff_character_status_node.py
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Flyff Character Status Node (New Version):
|
||||
- Has no inputs/outputs.
|
||||
- Creates an OCR region in data_collector.
|
||||
- Periodically grabs raw text from that region, parses it here in the node,
|
||||
and sets data_manager's HP, MP, FP, EXP accordingly.
|
||||
- Also updates its own text fields with the parsed values.
|
||||
"""
|
||||
|
||||
import re
|
||||
from OdenGraphQt import BaseNode
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from Modules import data_manager, data_collector
|
||||
|
||||
class FlyffCharacterStatusNode(BaseNode):
|
||||
__identifier__ = 'bunny-lab.io.flyff_character_status_node'
|
||||
NODE_NAME = 'Flyff - Character Status'
|
||||
|
||||
def __init__(self):
|
||||
super(FlyffCharacterStatusNode, self).__init__()
|
||||
# Prevent duplicates
|
||||
if data_manager.character_status_collector_exists:
|
||||
QMessageBox.critical(None, "Error", "Only one Flyff Character Status Collector node is allowed.")
|
||||
raise Exception("Duplicate Character Status Node.")
|
||||
data_manager.character_status_collector_exists = True
|
||||
|
||||
# Add text fields for display
|
||||
self.add_text_input('hp', 'HP', text="HP: 0/0")
|
||||
self.add_text_input('mp', 'MP', text="MP: 0/0")
|
||||
self.add_text_input('fp', 'FP', text="FP: 0/0")
|
||||
self.add_text_input('exp', 'EXP', text="EXP: 0%")
|
||||
|
||||
# Create a unique region id for this node (or just "character_status")
|
||||
self.region_id = "character_status"
|
||||
data_collector.create_ocr_region(self.region_id, x=250, y=50, w=180, h=130)
|
||||
|
||||
# Start the data_collector background thread (if not already started)
|
||||
data_collector.start_collector()
|
||||
|
||||
# Set the node name
|
||||
self.set_name("Flyff - Character Status")
|
||||
|
||||
def parse_character_stats(self, raw_text):
|
||||
"""
|
||||
Extract HP, MP, FP, EXP from the raw OCR text lines.
|
||||
"""
|
||||
lines = [l.strip() for l in raw_text.splitlines() if l.strip()]
|
||||
hp_current, hp_total = 0, 0
|
||||
mp_current, mp_total = 0, 0
|
||||
fp_current, fp_total = 0, 0
|
||||
exp_value = 0.0
|
||||
|
||||
if len(lines) >= 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}%")
|
BIN
Nodes/Organization/__pycache__/backdrop_node.cpython-312.pyc
Normal file
BIN
Nodes/Organization/__pycache__/backdrop_node.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
@@ -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')
|
@@ -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))
|
Reference in New Issue
Block a user