from phBot import *
import QtBind
import ctypes, ctypes.wintypes
import time, threading, json, os, struct
import webbrowser
import urllib.request, urllib.parse
pName = 'FAutoTrade'
pVersion = '2.2.0'
DISCORD_URL = 'https://discord.gg/eB9sGSMYBg'
WM_MOUSEMOVE = 0x0200
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
MK_LBUTTON = 0x0001
user32 = ctypes.windll.user32
# ============================================================
# ============================================================
_input_blocked = False
_input_lock = threading.Lock()
GWL_STYLE = -16
WS_DISABLED = 0x08000000
def _block_input(block: bool):
global _input_blocked
with _input_lock:
if block == _input_blocked:
return
hwnd = _get_hwnd()
if not hwnd:
return
try:
style = user32.GetWindowLongW(hwnd, GWL_STYLE)
if block:
new_style = style | WS_DISABLED
else:
new_style = style & ~WS_DISABLED
user32.SetWindowLongW(hwnd, GWL_STYLE, new_style)
_input_blocked = block
state = 'BLOCKED π' if block else 'RELEASED π'
log(f'[AutoTrade] Game window input {state}')
except Exception as e:
log(f'[AutoTrade] β block_input error: {e}')
# ============================================================
# GUI
# ============================================================
gui = QtBind.init(__name__, pName)
# ββ Dummy vars ββββββββββββββββββββββββββββββββββββββββββββββ
tbx_cmd = tbx_npc = btn_load_npcs = None
tbx_npc_x = tbx_npc_y = btn_cap_npc = None
tbx_max_trips = lbl_trips_done = None
tbx_x1 = tbx_y1 = tbx_d1 = tbx_r1 = None
tbx_x2 = tbx_y2 = tbx_d2 = tbx_r2 = None
tbx_x3 = tbx_y3 = tbx_d3 = tbx_r3 = None
tbx_x4 = tbx_y4 = tbx_d4 = tbx_r4 = None
tbx_x5 = tbx_y5 = tbx_d5 = tbx_r5 = None
tbx_x6 = tbx_y6 = tbx_d6 = tbx_r6 = None
tbx_x7 = tbx_y7 = tbx_d7 = tbx_r7 = None
btn_cap1 = btn_cap2 = btn_cap3 = btn_cap4 = btn_cap5 = None
tbx_x8 = tbx_y8 = tbx_d8 = tbx_r8 = None
btn_cap6 = btn_cap7 = btn_cap8 = None
btn_add = btn_del = None
chk_pairs = btn_refresh_scripts = btn_save_pairs = None
tbx_town1 = tbx_pc1 = tbx_ps1 = None
tbx_town2 = tbx_pc2 = tbx_ps2 = None
lbl_status = lbl_char = lbl_window = None
QtBind.createLabel(gui, 'Command:', 8, 7)
tbx_cmd = QtBind.createLineEdit(gui, '', 68, 3, 110, 22)
QtBind.createLabel(gui, 'NPC:', 185, 7)
tbx_npc = QtBind.createCombobox(gui, 210, 3, 130, 22)
btn_load_npcs = QtBind.createButton(gui, 'btn_load_npcs_cb', 'Load NPC', 345, 3)
# NPC Stop Position
QtBind.createLabel(gui, 'NPC X:', 8, 35)
tbx_npc_x = QtBind.createLineEdit(gui, '0', 70, 30, 50, 22)
QtBind.createLabel(gui, 'Y:', 126, 35)
tbx_npc_y = QtBind.createLineEdit(gui, '0', 136, 30, 50, 22)
btn_cap_npc = QtBind.createButton(gui, 'btn_cap_npc_cb', 'π―Get Position Npc', 192, 30)
# Clicks
QtBind.createLabel(gui, '# X Y Delay Rep Cap', 8, 60)
tbx_x1=QtBind.createLineEdit(gui,'0', 20,80,45,18)
tbx_y1=QtBind.createLineEdit(gui,'0', 70,80,45,18)
tbx_d1=QtBind.createLineEdit(gui,'1.0',120,80,35,18)
tbx_r1=QtBind.createLineEdit(gui,'1', 160,80,25,18)
QtBind.createLabel(gui,'1:',8,82)
btn_cap1=QtBind.createButton(gui,'btn_cap1_cb','π¦Cap Oppen Npc',190,77)
tbx_x2=QtBind.createLineEdit(gui,'0', 20,102,45,18)
tbx_y2=QtBind.createLineEdit(gui,'0', 70,102,45,18)
tbx_d2=QtBind.createLineEdit(gui,'1.0',120,102,35,18)
tbx_r2=QtBind.createLineEdit(gui,'1', 160,102,25,18)
QtBind.createLabel(gui,'2:',8,104)
btn_cap2=QtBind.createButton(gui,'btn_cap2_cb','π¦Cap Sell Gods',190,99)
tbx_x3=QtBind.createLineEdit(gui,'0', 20,124,45,18)
tbx_y3=QtBind.createLineEdit(gui,'0', 70,124,45,18)
tbx_d3=QtBind.createLineEdit(gui,'1.0',120,124,35,18)
tbx_r3=QtBind.createLineEdit(gui,'1', 160,124,25,18)
QtBind.createLabel(gui,'3:',8,126)
btn_cap3=QtBind.createButton(gui,'btn_cap3_cb','π¦Cap Yes Sell Gods',190,121)
tbx_x4=QtBind.createLineEdit(gui,'0', 20,146,45,18)
tbx_y4=QtBind.createLineEdit(gui,'0', 70,146,45,18)
tbx_d4=QtBind.createLineEdit(gui,'1.0',120,146,35,18)
tbx_r4=QtBind.createLineEdit(gui,'1', 160,146,25,18)
QtBind.createLabel(gui,'4:',8,148)
btn_cap4=QtBind.createButton(gui,'btn_cap4_cb','π¦Select Goods',190,143)
tbx_x5=QtBind.createLineEdit(gui,'0', 20,168,45,18)
tbx_y5=QtBind.createLineEdit(gui,'0', 70,168,45,18)
tbx_d5=QtBind.createLineEdit(gui,'1.0',120,168,35,18)
tbx_r5=QtBind.createLineEdit(gui,'1', 160,168,25,18)
QtBind.createLabel(gui,'5:',8,170)
btn_cap5=QtBind.createButton(gui,'btn_cap5_cb','π¦Trade Pet Slot',190,165)
tbx_x6=QtBind.createLineEdit(gui,'0', 20,190,45,18)
tbx_y6=QtBind.createLineEdit(gui,'0', 70,190,45,18)
tbx_d6=QtBind.createLineEdit(gui,'1.0',120,190,35,18)
tbx_r6=QtBind.createLineEdit(gui,'1', 160,190,25,18)
QtBind.createLabel(gui,'6:',8,192)
btn_cap6=QtBind.createButton(gui,'btn_cap6_cb','π¦Scale Button',190,187)
tbx_x7=QtBind.createLineEdit(gui,'0', 20,212,45,18)
tbx_y7=QtBind.createLineEdit(gui,'0', 70,212,45,18)
tbx_d7=QtBind.createLineEdit(gui,'1.0',120,212,35,18)
tbx_r7=QtBind.createLineEdit(gui,'1', 160,212,25,18)
QtBind.createLabel(gui,'7:',8,214)
btn_cap7=QtBind.createButton(gui,'btn_cap7_cb','π¦Star Select',190,209)
tbx_x8=QtBind.createLineEdit(gui,'0', 20,234,45,18)
tbx_y8=QtBind.createLineEdit(gui,'0', 70,234,45,18)
tbx_d8=QtBind.createLineEdit(gui,'1.0',120,234,35,18)
tbx_r8=QtBind.createLineEdit(gui,'1', 160,234,25,18)
QtBind.createLabel(gui,'8:',8,236)
btn_cap8=QtBind.createButton(gui,'btn_cap8_cb','π¦Confirm',190,231)
btn_add =QtBind.createButton(gui,'btn_add_cb', 'βAdd/Update', 8, 258)
btn_del =QtBind.createButton(gui,'btn_del_cb', 'ποΈ Delete', 100, 258)
QtBind.createButton(gui, 'btn_edit_cmd_cb', 'βοΈ Edit', 185, 258)
btn_auto_start = QtBind.createButton(gui,'btn_auto_start_cb','βΆοΈ Auto Start',270,258)
btn_stop_auto = QtBind.createButton(gui,'_stop_auto_action','βΉοΈ Stop', 360,258)
_rx = 360
# Saved Commands list
QtBind.createLabel(gui, 'Saved Commands:', 470, 7)
lst_cmds = QtBind.createList(gui, 470, 20, 250, 100)
chk_pairs = None
btn_refresh_scripts=QtBind.createButton(gui,'btn_refresh_scripts_cb','π Refresh Scripts',_rx+85,258)
btn_save_pairs=QtBind.createButton(gui,'btn_save_pairs_cb','πΎ Save',_rx+195,258)
QtBind.createLabel(gui,' Townβ‘οΈ',_rx+15,170)
QtBind.createLabel(gui,'Commandβ‘οΈ',_rx+120,170)
QtBind.createLabel(gui,'Scriptβ‘οΈ',_rx+220,170)
tbx_town1=QtBind.createCombobox(gui,_rx+15,188,95,22)
for _t in ['Jangan','Donwhang','Hotan']: QtBind.append(gui,tbx_town1,_t)
tbx_pc1=QtBind.createCombobox(gui,_rx+115,188,95,22)
tbx_ps1=QtBind.createCombobox(gui,_rx+215,188,145,22)
tbx_town2=QtBind.createCombobox(gui,_rx+15,214,95,22)
for _t in ['Jangan','Donwhang','Hotan']: QtBind.append(gui,tbx_town2,_t)
tbx_pc2=QtBind.createCombobox(gui,_rx+115,214,95,22)
tbx_ps2=QtBind.createCombobox(gui,_rx+215,214,145,22)
QtBind.createLabel(gui, 'Limit Trade:', 8, 291)
tbx_max_trips = QtBind.createLineEdit(gui, '0', 78, 289, 30, 18)
QtBind.createLabel(gui, 'Done:', 110, 291)
lbl_trips_done = QtBind.createLabel(gui, '0', 148, 292)
QtBind.createLabel(gui, 'π«Pet:', 170, 291)
cbx_pet = QtBind.createCombobox(gui, 210, 289, 130, 22)
QtBind.createButton(gui, 'btn_load_pets_g1_cb', 'π« Pet Load',_rx+280, 258)
QtBind.createLabel(gui, 'Script DeaTH:', 350, 293)
tbx_death_scr = QtBind.createCombobox(gui, 430, 289, 130, 22)
chk_char_death = QtBind.createCheckBox(gui, 'chk_char_death_cb', 'Char Death', 560, 289)
chk_pet_death = QtBind.createCheckBox(gui, 'chk_pet_death_cb', 'Pet Death', 640, 289)
QtBind.setChecked(gui, chk_char_death, True)
QtBind.setChecked(gui, chk_pet_death, True)
lbl_death_status = QtBind.createLabel(gui, 'Idle', 8, 316)
lbl_pet_status = QtBind.createLabel(gui, 'β', 200, 316)
# ββ Status βββββββββββββββββββββββββββββββββββββββββββββββββββ
QtBind.createLabel(gui,'Status:',8,335)
lbl_status=QtBind.createLabel(gui,'Idle',55,335)
QtBind.createLabel(gui,'Char:',160,335)
lbl_char=QtBind.createLabel(gui,'β',195,335)
QtBind.createLabel(gui,'Window:',260,335)
lbl_window=QtBind.createLabel(gui,'β',310,335)
btn_notifications = QtBind.createButton(
gui, 'show_notifications_cb', 'π Notifications', 470, 123
)
btn_discord = QtBind.createButton(
gui, 'discord_clicked', u'\U0001f4ac Discord', 610, 123
)
QtBind.createLabel(
gui, u'β Made By FascinaTe', 565, 150
)
# Notification settings open as a page inside this tab. The list is an opaque
# backdrop, keeping the original (already crowded) AutoTrade UI unchanged.
_NOTIFY_OFFSCREEN_X = 1200
_notification_widgets = []
def _notification_widget(widget, x, y):
_notification_widgets.append((widget, x, y))
QtBind.move(gui, widget, _NOTIFY_OFFSCREEN_X, y)
return widget
_notification_widget(QtBind.createList(gui, _NOTIFY_OFFSCREEN_X, 0, 720, 350), 0, 0)
_notification_widget(
QtBind.createLabel(gui, 'FAutoTrade Notifications',
_NOTIFY_OFFSCREEN_X, 10), 15, 10)
_notification_widget(
QtBind.createButton(gui, 'hide_notifications_cb', 'β© Back', _NOTIFY_OFFSCREEN_X, 5),
640, 5)
chk_notify_discord = _notification_widget(
QtBind.createCheckBox(gui, 'notification_setting_changed_cb', 'Enable Discord',
_NOTIFY_OFFSCREEN_X, 45), 15, 45)
_notification_widget(
QtBind.createLabel(gui, 'Webhook URL:', _NOTIFY_OFFSCREEN_X, 75), 15, 75)
tbx_notify_discord_webhook = _notification_widget(
QtBind.createLineEdit(gui, '', _NOTIFY_OFFSCREEN_X, 70, 430, 22), 105, 70)
_notification_widget(
QtBind.createButton(gui, 'test_discord_notification_cb', 'π§ͺ Test Discord',
_NOTIFY_OFFSCREEN_X, 68), 550, 68)
chk_notify_telegram = _notification_widget(
QtBind.createCheckBox(gui, 'notification_setting_changed_cb', 'Enable Telegram',
_NOTIFY_OFFSCREEN_X, 110), 15, 110)
_notification_widget(
QtBind.createLabel(gui, 'Bot Token:', _NOTIFY_OFFSCREEN_X, 140), 15, 140)
tbx_notify_telegram_token = _notification_widget(
QtBind.createLineEdit(gui, '', _NOTIFY_OFFSCREEN_X, 135, 330, 22), 105, 135)
_notification_widget(
QtBind.createLabel(gui, 'Chat ID:', _NOTIFY_OFFSCREEN_X, 170), 15, 170)
tbx_notify_telegram_chat_id = _notification_widget(
QtBind.createLineEdit(gui, '', _NOTIFY_OFFSCREEN_X, 165, 180, 22), 105, 165)
_notification_widget(
QtBind.createButton(gui, 'test_telegram_notification_cb', 'π§ͺ Test Telegram',
_NOTIFY_OFFSCREEN_X, 133), 550, 133)
_notification_widget(
QtBind.createLabel(gui, 'Notify me about',
_NOTIFY_OFFSCREEN_X, 195), 15, 195)
chk_notify_start_stop = _notification_widget(
QtBind.createCheckBox(gui, 'notification_setting_changed_cb', 'Start / Stop',
_NOTIFY_OFFSCREEN_X, 218), 15, 218)
chk_notify_trips = _notification_widget(
QtBind.createCheckBox(gui, 'notification_setting_changed_cb', 'Trip completed / limit',
_NOTIFY_OFFSCREEN_X, 218), 145, 218)
chk_notify_deaths = _notification_widget(
QtBind.createCheckBox(gui, 'notification_setting_changed_cb',
'Character / transport death', _NOTIFY_OFFSCREEN_X, 218),
330, 218)
chk_notify_errors = _notification_widget(
QtBind.createCheckBox(gui, 'notification_setting_changed_cb', 'Script / pet errors',
_NOTIFY_OFFSCREEN_X, 245), 15, 245)
_notification_widget(
QtBind.createButton(gui, 'save_notification_settings_cb', 'πΎ Save Notifications',
_NOTIFY_OFFSCREEN_X, 270), 15, 270)
lbl_notification_status = _notification_widget(
QtBind.createLabel(gui,
'Settings are stored per character.',
_NOTIFY_OFFSCREEN_X, 278), 170, 278)
# ββ Click fields list (used by form helpers & cap callbacks) ββ
_CLICK_FIELDS = None # will be set after GUI creation
# Constant click groups β defined once at module level
_CLICK_GROUPS = [(0, 3), (3, 8)]
QtBind.append(gui, cbx_pet, '')
_CLICK_FIELDS = [
(tbx_x1,tbx_y1,tbx_d1,tbx_r1),
(tbx_x2,tbx_y2,tbx_d2,tbx_r2),
(tbx_x3,tbx_y3,tbx_d3,tbx_r3),
(tbx_x4,tbx_y4,tbx_d4,tbx_r4),
(tbx_x5,tbx_y5,tbx_d5,tbx_r5),
(tbx_x6,tbx_y6,tbx_d6,tbx_r6),
(tbx_x7,tbx_y7,tbx_d7,tbx_r7),
(tbx_x8,tbx_y8,tbx_d8,tbx_r8),
]
# ============================================================
# State β single definition (no duplicates)
# ============================================================
_stop_flag = False
_running = False
_commands = {}
_current_pair = 0
_trips_done = 0
_pair_running = False
_auto_running = False
_bot_was_running = False
_bot_start_time = 0.0
_death_triggered = False
_pet_death_triggered = False
_transport_died_flag = False
_was_auto_running = False
_death_script_running = False
_teleport_time = 0.0
_last_transport_seen = time.time()
_transport_missing_count = 0
_TRANSPORT_MISSING_THRESHOLD = 12
_TRANSPORT_TELEPORT_GRACE = 45
_script_teleporting = False
_script_teleport_time = 0.0
_SCRIPT_TELEPORT_GUARD = 60
_script_pause_flag = False
_pending_pair_idx = -1
_pair_lock = threading.Lock()
_death_lock = threading.Lock()
_teleporting_now = False
_gate_crossing = False
_gold_before_teleport = -1
_notification_settings = {
'discord_enabled': False,
'discord_webhook': '',
'telegram_enabled': False,
'telegram_bot_token': '',
'telegram_chat_id': '',
'notify_start_stop': True,
'notify_trips': True,
'notify_deaths': True,
'notify_errors': True,
}
_notification_last_sent = {}
_notification_lock = threading.Lock()
# ============================================================
# Transport Pet
# ============================================================
_PET_TYPE_IDS = {0x11EC, 0x11ED}
_PET_NAME_TYPE_MAP = [
(['elephant', 'white_elephant', 'cos_t_elephant'], 0x11ED),
(['fire_bull', 'firebull', 'bull'], 0x11EC),
(['donkey', 'cos_t_donkey'], 0x11EC),
(['horse', 'cos_t_horse'], 0x11EC),
(['camel', 'cos_t_camel'], 0x11EC),
]
def _pet_get_type_id(item):
for key in ('id', 'model', 'type_id', 'object_id'):
val = item.get(key)
if val and isinstance(val, int) and val in _PET_TYPE_IDS:
return val
sn = item.get('servername', '').lower()
name = item.get('name', '').lower()
combined = sn + ' ' + name
for keywords, type_id in _PET_NAME_TYPE_MAP:
if any(kw in combined for kw in keywords):
return type_id
log(f'[AutoTrade] β οΈ WARNING: unknown pet type for "{item.get("name","?")}" β using 0x11EC')
return 0x11EC
def _pet_find_slot():
try:
name_filter = QtBind.text(gui, cbx_pet).strip().lower()
if not name_filter: return -1, None
inv = get_inventory()
if not inv: return -1, None
items = inv.get('items', [])
for idx, item in enumerate(items):
if not item: continue
item_name = item.get('name', '').lower()
servername = item.get('servername', '').lower()
type_id = item.get('id') or item.get('model') or 0
name_match = name_filter in item_name or name_filter in servername
type_match = type_id in _PET_TYPE_IDS
kw_match = any(x in servername for x in [
'cos_t_', 'cos_p_', 'transport', 'donkey', 'horse', 'camel', 'elephant', 'bull'
])
if (name_match and (type_match or kw_match)) or type_match:
return idx, item
return -1, None
except Exception as e:
log(f'[AutoTrade] pet_find_slot error: {e}')
return -1, None
def _pet_get_active():
try:
pets = get_pets()
if not pets: return None
for pet_id, pet in pets.items():
if pet.get('type') in ('transport', 'horse'):
return pet_id, pet
return None
except: return None
def _pet_summon_with_retry(max_tries=3, wait=5):
for attempt in range(1, max_tries + 1):
if _pet_get_active():
return True
_pet_summon_once()
time.sleep(wait)
if _pet_get_active():
log('[AutoTrade] β
Pet summoned successfully.')
return True
log('[AutoTrade] β Pet summon failed after all retries!')
_queue_notification('Transport pet could not be summoned.', 'errors')
return False
def _pet_summon_once():
if _pet_get_active():
return True
try:
import AutoTransportPet as _atp
pet_name = QtBind.text(gui, cbx_pet).strip()
if pet_name:
QtBind.setText(_atp.gui, _atp.tbx_name, pet_name)
_atp.btn_find_cb()
time.sleep(0.3)
_atp.btn_summon_cb()
return True
except ImportError: pass
except Exception as e: log(f'[AutoTrade] β AutoTransportPet error: {e}')
# inject packet
slot, item = _pet_find_slot()
if slot == -1:
log('[AutoTrade] β Pet not found in bag!')
return False
try:
type_id = _pet_get_type_id(item)
data = struct.pack('= len(_CLICK_FIELDS): break
tx, ty, td, tr = _CLICK_FIELDS[i]
QtBind.setText(gui, tx, str(c.get('x', 0)))
QtBind.setText(gui, ty, str(c.get('y', 0)))
QtBind.setText(gui, td, str(c.get('d', 1.0)))
QtBind.setText(gui, tr, str(c.get('r', 1)))
# ============================================================
# Cap + GUI Callbacks
# ============================================================
def _make_cap_cb(num, tx, ty):
def _cb():
threading.Thread(target=_capture_pos, args=(num, tx, ty), daemon=True).start()
_cb.__name__ = f'btn_cap{num}_cb'
return _cb
btn_cap1_cb = _make_cap_cb(1, tbx_x1, tbx_y1)
btn_cap2_cb = _make_cap_cb(2, tbx_x2, tbx_y2)
btn_cap3_cb = _make_cap_cb(3, tbx_x3, tbx_y3)
btn_cap4_cb = _make_cap_cb(4, tbx_x4, tbx_y4)
btn_cap5_cb = _make_cap_cb(5, tbx_x5, tbx_y5)
btn_cap6_cb = _make_cap_cb(6, tbx_x6, tbx_y6)
btn_cap7_cb = _make_cap_cb(7, tbx_x7, tbx_y7)
btn_cap8_cb = _make_cap_cb(8, tbx_x8, tbx_y8)
def _capture_game_pos(tbx_x, tbx_y):
_set_status('Reading in-game position...')
try:
pos = get_position()
if pos:
gx = int(pos.get('x', 0))
gy = int(pos.get('y', 0))
QtBind.setText(gui, tbx_x, str(gx))
QtBind.setText(gui, tbx_y, str(gy))
_set_status(f'NPC Stop pos: {gx},{gy}')
else:
_set_status('Not in game!')
except Exception as e:
log(f'[AutoTrade] cap_game_pos error: {e}')
def btn_cap_npc_cb():
threading.Thread(target=_capture_game_pos, args=(tbx_npc_x, tbx_npc_y), daemon=True).start()
def btn_auto_start_cb():
global _pair_running, _auto_running, _trips_done, _last_transport_seen, _teleport_time, _transport_missing_count
if _auto_running:
return
_trips_done = 0
try: QtBind.setText(gui, lbl_trips_done, '0')
except: pass
_last_transport_seen = time.time()
_teleport_time = time.time()
_transport_missing_count = 0
_auto_running = True
_pair_running = False
try:
import phBotChat
phBotChat.ClientNotice('AutoTrade Made by FascinaTe discord: mrfascinate')
except: pass
pairs = _get_pairs()
if not pairs:
_set_status('No pairs configured!')
_auto_running = False
return
current_town = _get_current_town()
matched_idx = 0
for i, (town, cmd, scr) in enumerate(pairs):
if town and (town in current_town or current_town in town):
matched_idx = i
break
log(f'[AutoTrade] Auto Start β pair {matched_idx + 1} (town="{current_town}")')
_set_status('Auto Start!')
_queue_notification('AutoTrade started (pair %s).' % (matched_idx + 1),
'start_stop')
threading.Thread(target=_run_pair, args=(matched_idx,), daemon=True).start()
def _stop_auto_action():
global _pair_running, _auto_running, _bot_was_running, _stop_flag
global _pet_death_triggered, _transport_died_flag, _was_auto_running, _death_triggered
global _transport_missing_count, _running, _script_pause_flag, _pending_pair_idx
global _npc_triggered_cmd, _death_script_running
_auto_running = False
_was_auto_running = False
_pair_running = False
_bot_was_running = False
_stop_flag = True
_running = False
_pet_death_triggered = False
_transport_died_flag = False
_death_triggered = False
_death_script_running = False
_transport_missing_count = 0
_script_pause_flag = False
_pending_pair_idx = -1
_resurrecting = False
_set_status('βΉοΈ Stopped.')
def _bg_stop():
global _stop_flag
_party_members_before = []
try:
pm = get_party()
if pm:
for mid, mdata in pm.items():
n = mdata.get('name', '')
if n:
_party_members_before.append(n)
except: pass
try: stop_bot()
except Exception as e: log(f'[AutoTrade] stop_bot error: {e}')
try: inject_joymax(0x704B, b'\x92\x00\x00\x00', False)
except: pass
time.sleep(0.3)
_stop_flag = False
log('[AutoTrade] βΉοΈ Auto Stop β everything halted.')
_queue_notification('AutoTrade stopped.', 'start_stop')
if _party_members_before:
time.sleep(3)
try:
pm_after = get_party()
current = set()
if pm_after:
for mid, mdata in pm_after.items():
current.add(mdata.get('name', ''))
missing = [m for m in _party_members_before if m not in current]
if missing:
log(f'[AutoTrade] β οΈ Party left after stop: {missing}')
_set_status('Stopped. β οΈ Party left β disable "Leave Party on Stop" in phBot!')
except: pass
threading.Thread(target=_bg_stop, daemon=True).start()
def btn_refresh_scripts_cb():
_refresh_scripts_comboboxes()
_refresh_cmd_comboboxes()
_refresh_death_scripts()
_set_status('Refreshed!')
def btn_load_pets_g1_cb():
_populate_pet_combobox()
_set_status('Pets loaded!')
_KNOWN_NPCS = [
'Specialty Trader Jodaesan',
'Specialty Shop Elder Leegak',
'Specialty Trader Sanmok',
]
def _refresh_npc_combobox():
try:
cur = QtBind.text(gui, tbx_npc)
QtBind.clear(gui, tbx_npc)
QtBind.append(gui, tbx_npc, '')
added = set()
for n in _KNOWN_NPCS:
QtBind.append(gui, tbx_npc, n)
added.add(n.lower())
try:
npcs = get_npcs()
if npcs:
for npc_id, info in npcs.items():
name = info.get('name', '')
sname = info.get('servername', '').upper()
if not name: continue
if any(x in sname for x in ('GATE','WAREHOUSE','STORAGE')): continue
if name.lower() not in added:
QtBind.append(gui, tbx_npc, name)
added.add(name.lower())
except: pass
if cur: QtBind.setText(gui, tbx_npc, cur)
except: pass
def chk_char_death_cb(checked=False):
_save_config()
def chk_pet_death_cb(checked=False):
_save_config()
def btn_load_npcs_cb():
_refresh_npc_combobox()
_set_status('NPCs loaded!')
def _refresh_death_scripts():
_refill_combobox(tbx_death_scr, _get_available_scripts())
def _set_death_status(txt):
try: QtBind.setText(gui, lbl_death_status, str(txt))
except: pass
def btn_save_pairs_cb():
_save_config()
_auto_append_par_to_scripts()
def _auto_append_par_to_scripts():
try:
pairs = _get_pairs()
total = len(pairs)
if total == 0: return
for i, (town, cmd, scr) in enumerate(pairs):
if not scr: continue
scr_path = _resolve_script_path(scr)
if not scr_path or not os.path.exists(scr_path): continue
next_idx = (i + 1) % total
par_line = f'par,{next_idx + 1}'
with open(scr_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
clean_lines = [l for l in content.splitlines() if not l.strip().lower().startswith('par,')]
result = '\n'.join(clean_lines).rstrip() + f'\n{par_line}\n'
with open(scr_path, 'w', encoding='utf-8') as f:
f.write(result)
_set_status('Saved + Scripts updated!')
except Exception as e:
log(f'[AutoTrade] _auto_append_par error: {e}')
def btn_add_cb():
cmd = QtBind.text(gui, tbx_cmd).strip().lower()
if not cmd: _set_status('Enter command!'); return
_commands[cmd] = _form_to_entry()
_refresh_list(); _refresh_cmd_comboboxes(); _save_config()
_set_status(f'Saved: {cmd}')
def btn_del_cb():
try:
idx = QtBind.currentIndex(gui, lst_cmds)
keys = list(_commands.keys())
if idx < 0 or idx >= len(keys): return
del _commands[keys[idx]]
_refresh_list(); _refresh_cmd_comboboxes(); _save_config()
except: pass
def lst_cmds_cb(v):
try:
idx=QtBind.currentIndex(gui,lst_cmds); keys=list(_commands.keys())
if idx<0 or idx>=len(keys): return
cmd=keys[idx]; QtBind.setText(gui,tbx_cmd,cmd); _entry_to_form(_commands[cmd])
except: pass
def btn_edit_cmd_cb():
try:
idx = QtBind.currentIndex(gui, lst_cmds)
keys = list(_commands.keys())
if idx < 0 or idx >= len(keys):
_set_status('Select a command first!')
return
cmd = keys[idx]
QtBind.setText(gui, tbx_cmd, cmd)
_entry_to_form(_commands[cmd])
_set_status(f'Editing: {cmd}')
except Exception as e:
log(f'[AutoTrade] β btn_edit_cmd_cb error: {e}')
# ============================================================
# Run Logic
# ============================================================
def _do_run(cmd_name):
global _running, _stop_flag
cmd = cmd_name.strip().lower()
if cmd not in _commands:
log(f'[AutoTrade] Not found: {cmd}'); return
if _running: return
entry = _commands[cmd]
npc_filter = entry.get('npc','').lower()
clicks_raw = entry.get('clicks',[])
hwnd = _get_hwnd()
if not hwnd: _set_status('No game window!'); return
_stop_flag = False
_running = True
def _run():
global _running, _stop_flag
try:
pet_name = QtBind.text(gui, cbx_pet).strip() if cbx_pet else ''
if pet_name and not _pet_get_active():
_set_pet_status('Summoning...')
ok = _pet_summon_with_retry(max_tries=3, wait=5)
_set_pet_status('Active' if ok else 'Failed')
except Exception as e:
log(f'[AutoTrade] pet check error: {e}')
# ββ Walk to NPC (stop near NPC, not on top of it) ββ
try:
npcs = get_npcs()
if npcs and npc_filter:
for npc_id, info in npcs.items():
name = info.get('name','').lower()
sname = info.get('servername','').upper()
if npc_filter not in name: continue
if any(x in sname for x in ('GATE','WAREHOUSE','STORAGE')): continue
nx, ny = info.get('x',0), info.get('y',0)
npc_stop_x = entry.get('npc_stop_x', 0)
npc_stop_y = entry.get('npc_stop_y', 0)
if npc_stop_x != 0 or npc_stop_y != 0:
tx, ty = npc_stop_x, npc_stop_y
else:
_STOP_DIST = 120
try:
cur_pos = get_position()
if cur_pos:
cx, cy = cur_pos.get('x', nx), cur_pos.get('y', ny)
dx, dy = nx - cx, ny - cy
dist = (dx**2 + dy**2) ** 0.5
if dist > _STOP_DIST:
ratio = (dist - _STOP_DIST) / dist
tx = int(cx + dx * ratio)
ty = int(cy + dy * ratio)
else:
tx, ty = cx, cy
else:
tx, ty = nx, ny
except:
tx, ty = nx, ny
_set_status(f'Walking near {info.get("name")}...')
move_to(tx, ty, 0)
dl = time.time() + 30
while time.time() < dl:
pos = get_position()
if pos:
px, py = pos.get('x',0), pos.get('y',0)
if abs(px - tx) < 80 and abs(py - ty) < 80: break
if abs(px - nx) < 150 and abs(py - ny) < 150: break
time.sleep(0.5)
time.sleep(0.5)
inject_joymax(0x7045, struct.pack('= 0:
return
_was_auto_running = False
def script_finished():
global _pair_running, _bot_was_running, _pet_death_triggered, _was_auto_running, _trips_done
global _death_script_running
_death_script_running = False
if not _auto_running and not _was_auto_running:
return
if _pending_pair_idx >= 0:
return
_bot_was_running = False
_pair_running = False
_was_auto_running = False
_trips_done += 1
try: QtBind.setText(gui, lbl_trips_done, str(_trips_done))
except: pass
_queue_notification('Trip #%s completed.' % _trips_done, 'trips')
try:
max_t = int(QtBind.text(gui, tbx_max_trips).strip()) if tbx_max_trips else 0
except:
max_t = 0
if max_t > 0 and _trips_done >= max_t:
log(f'[AutoTrade] β
Reached {_trips_done}/{max_t} trips β Auto Stop!')
_queue_notification(
'Trade limit reached: %s/%s trips.' % (_trips_done, max_t), 'trips')
_set_status(f'Done! {_trips_done} trips completed.')
_stop_auto_action()
return
def _check_and_restart():
global _pet_death_triggered
try: set_training_script('')
except: pass
time.sleep(2)
pairs = _get_pairs()
if not pairs: return
next_idx = (_current_pair + 1) % len(pairs)
next_town, next_cmd, next_scr = pairs[next_idx]
transport_died = _transport_died_flag
scr_death = ''
try:
scr_death = QtBind.text(gui, tbx_death_scr).strip()
except:
pass
if transport_died and scr_death:
log(f'[AutoTrade] Transport died β death script: {scr_death}')
_set_death_status(f'Transport died β starting: {scr_death}')
_pet_death_triggered = False
time.sleep(3)
scr_path = _resolve_script_path(scr_death)
if scr_path:
try:
set_training_script(scr_path)
start_bot()
log(f'[AutoTrade] Death script started: {scr_death}')
_set_death_status(f'Bot started: {scr_death}')
except Exception as e:
log(f'[AutoTrade] death script error: {e}')
return
_pet_death_triggered = False
_set_status(f'Waiting for {next_town}...')
if next_town:
deadline = time.time() + 90
arrived = False
while time.time() < deadline:
try:
current = _get_current_town()
if next_town in current or current in next_town:
arrived = True
break
except: pass
time.sleep(2)
if not arrived:
log(f'[AutoTrade] Timeout waiting for "{next_town}" β starting Pair {next_idx+1} anyway')
time.sleep(3)
if not _auto_running: return
threading.Thread(target=_run_pair, args=(next_idx,), daemon=True).start()
threading.Thread(target=_check_and_restart, daemon=True).start()
def _get_current_town():
try:
pos = get_position()
if not pos: return ''
region = pos.get('region', 0)
if not region: return ''
try:
name = get_zone_name(region)
return name.lower() if name else ''
except:
pass
return {
25000: 'jangan', 25257: 'donwhang',
25514: 'hotan', 24832: 'samarkand',
}.get(region, '')
except:
return ''
def handle_joymax(opcode, data):
global _gate_crossing, _teleport_time, _script_teleport_time, _script_teleporting
global _death_triggered, _resurrecting, _was_auto_running
global _auto_running, _bot_was_running, _pair_running
if opcode == 0x30BF:
if not data or data[-1] != 0x02: return True
if _resurrecting: return True
if not _auto_running and not _bot_was_running and not _pair_running: return True
if time.time() - _script_teleport_time < 30: return True
try:
if chk_char_death and not QtBind.isChecked(gui, chk_char_death): return True
except: pass
try:
ch = get_character_data()
if ch:
char_id = ch.get('player_id', -1)
packet_id = int.from_bytes(data[:4], 'little')
if char_id != -1 and packet_id != char_id: return True
except: pass
scr = ''
try: scr = QtBind.text(gui, tbx_death_scr).strip()
except: pass
_death_triggered = True
_was_auto_running = _auto_running or _bot_was_running or _pair_running
_auto_running = False
_bot_was_running = False
_pair_running = False
def _char_death_joymax(_scr=scr):
global _resurrecting, _death_script_running, _auto_running, _bot_was_running, _death_triggered
try:
if _stop_flag: return
_resurrecting = True
log('[AutoTrade] Died β starting death sequence')
_queue_notification(
'Character died; recovery sequence started.', 'deaths')
_set_death_status('Died β starting death sequence...')
try: inject_joymax(0x3053, b'\x01', True)
except: pass
try: stop_bot()
except: pass
time.sleep(2)
if _stop_flag: return
if _scr:
scr_path = _resolve_script_path(_scr)
if scr_path:
set_training_script(scr_path)
_death_script_running = True
_auto_running = True
_bot_was_running = True
start_bot()
log(f'[AutoTrade] Death script started: {_scr}')
_queue_notification(
'Character recovery script started: %s' % _scr,
'deaths')
_set_death_status(f'Bot started: {_scr}')
else:
log(f'[AutoTrade] Death script not found: {_scr}')
_queue_notification(
'Recovery script was not found: %s' % _scr,
'errors')
threading.Thread(target=btn_auto_start_cb, daemon=True).start()
else:
threading.Thread(target=btn_auto_start_cb, daemon=True).start()
except Exception as e:
log(f'[AutoTrade] β char death error: {e}')
finally:
_death_triggered = False
_death_script_running = False
_resurrecting = False
threading.Thread(target=_char_death_joymax, daemon=True).start()
return True
# ============================================================
# Chat + Script Commands
# ============================================================
def handle_chat(t, player, msg):
if not msg: return
global _pair_running, _bot_was_running
try:
me = get_character_data()
if me and player and player.strip().lower() != me.get('name','').strip().lower():
return
except: pass
cmd = msg.strip().lower()
if cmd == 'next_pair':
if _auto_running:
_bot_was_running = False
_pair_running = False
pairs = _get_pairs()
if pairs:
next_idx = (_current_pair + 1) % len(pairs)
def _delayed_next(_idx=next_idx):
try: stop_bot()
except: pass
time.sleep(1)
threading.Thread(target=_run_pair, args=(_idx,), daemon=True).start()
threading.Thread(target=_delayed_next, daemon=True).start()
return
# par 1, par 2...
if cmd.startswith('par ') and cmd[4:].isdigit():
idx = int(cmd[4:]) - 1
_pair_running = False
pairs = _get_pairs()
if pairs and 0 <= idx < len(pairs):
def _delayed_pair(_idx=idx):
try: stop_bot()
except: pass
time.sleep(3)
threading.Thread(target=_run_pair, args=(_idx,), daemon=True).start()
threading.Thread(target=_delayed_pair, daemon=True).start()
return
if cmd in _commands:
def _chat_run(_cmd=cmd):
try: stop_bot()
except: pass
time.sleep(1.0)
_do_run(_cmd)
deadline = time.time() + 5
while not _running and time.time() < deadline:
time.sleep(0.2)
deadline = time.time() + 120
while _running and time.time() < deadline:
time.sleep(0.5)
_set_status('Starting bot...')
try:
start_bot()
_set_status('Bot started!')
except Exception as e:
log(f'[AutoTrade] start error: {e}')
threading.Thread(target=_chat_run, daemon=True).start()
def handle_script(command, args):
global _pair_running, _bot_was_running, _script_pause_flag, _teleport_time, _transport_missing_count
global _script_teleporting, _script_teleport_time, _last_transport_seen
cmd = command.strip().lower()
if cmd == 'teleporting' or 'teleport' in cmd:
global _teleporting_now, _gate_crossing, _gold_before_teleport
_teleport_time = time.time()
_script_teleport_time = time.time()
_script_teleporting = True
_last_transport_seen = time.time()
_transport_missing_count = 0
_teleporting_now = True
_gate_crossing = True
try:
ch = get_character_data()
_gold_before_teleport = ch.get('gold', -1) if ch else -1
except:
_gold_before_teleport = -1
def _clear_teleporting():
global _teleporting_now, _script_teleporting
time.sleep(15)
_teleporting_now = False
_script_teleporting = False
threading.Thread(target=_clear_teleporting, daemon=True).start()
return False
# ββ stuck recovery β reset missing counter ββ
if 'stuck' in cmd:
_last_transport_seen = time.time()
_transport_missing_count = 0
# ββ pause ββ
if cmd == 'pause':
_script_pause_flag = True
return True
if cmd in ('nextpar', 'next', 'next_pair'):
if _auto_running:
_script_pause_flag = True
_bot_was_running = False
_pair_running = False
pairs = _get_pairs()
if pairs:
target_idx = (_current_pair + 1) % len(pairs)
if cmd == 'nextpar' and args and args[0].strip().isdigit():
target_idx = (int(args[0].strip()) - 1) % len(pairs)
elif cmd == 'next' and args and args[0].strip().lower() == 'par':
if len(args) >= 2 and args[1].strip().isdigit():
target_idx = int(args[1].strip()) - 1
target_idx = target_idx % len(pairs)
def _go_next(_idx=target_idx):
try: stop_bot()
except: pass
time.sleep(1)
if _auto_running:
threading.Thread(target=_run_pair, args=(_idx,), daemon=True).start()
threading.Thread(target=_go_next, daemon=True).start()
return True
# ββ direct command ββ
if cmd in _commands:
threading.Thread(target=_do_run, args=(cmd,), daemon=True).start()
return True
return False
# ββ Script command: par βββββββββββββββββββββββββββββββββ
def par(arguments):
global _pair_running, _bot_was_running, _script_pause_flag, _auto_running, _pending_pair_idx, _trips_done
try:
idx_str = ''
for a in arguments:
if a.strip().isdigit():
idx_str = a.strip()
break
if not idx_str:
log(f'[AutoTrade] par: no digit found in arguments {arguments}')
return 0
idx = int(idx_str) - 1
pairs = _get_pairs()
if not pairs or idx < 0 or idx >= len(pairs):
log(f'[AutoTrade] par {idx+1}: invalid pair index')
return 0
_trips_done += 1
try: QtBind.setText(gui, lbl_trips_done, str(_trips_done))
except: pass
log(f'[AutoTrade] Trip #{_trips_done} done β par {idx+1}')
_queue_notification(
'Trip #%s completed; moving to pair %s.' % (_trips_done, idx + 1),
'trips')
try:
max_t = int(QtBind.text(gui, tbx_max_trips).strip()) if tbx_max_trips else 0
except:
max_t = 0
if max_t > 0 and _trips_done >= max_t:
log(f'[AutoTrade] β
Reached {_trips_done}/{max_t} trips β Auto Stop!')
_queue_notification(
'Trade limit reached: %s/%s trips.' % (_trips_done, max_t),
'trips')
_set_status(f'Done! {_trips_done} trips completed.')
threading.Thread(target=_stop_auto_action, daemon=True).start()
return 0
log(f'[AutoTrade] Script: par {idx+1} β waiting for bot stop then town arrival')
_pending_pair_idx = idx
_auto_running = True
_script_pause_flag = True
_pair_running = False
_bot_was_running = False
def _wait_and_run(_i=idx):
global _pending_pair_idx, _script_pause_flag
target_town = pairs[_i][0] if pairs and 0 <= _i < len(pairs) else ''
deadline_stop = time.time() + 30
while time.time() < deadline_stop:
time.sleep(0.5)
if not _bot_was_running:
break
time.sleep(1)
if not _auto_running:
_pending_pair_idx = -1
_script_pause_flag = False
return
if target_town:
log(f'[AutoTrade] par {_i+1}: waiting for town "{target_town}"...')
_set_status(f'Waiting for {target_town}...')
deadline_town = time.time() + 120
arrived = False
while time.time() < deadline_town:
if not _auto_running:
_pending_pair_idx = -1
_script_pause_flag = False
return
try:
current = _get_current_town()
if target_town in current or current in target_town:
arrived = True
break
except:
pass
time.sleep(2)
if not arrived:
log(f'[AutoTrade] par {_i+1}: timeout waiting for "{target_town}" β starting anyway')
else:
log(f'[AutoTrade] par {_i+1}: arrived at "{target_town}"')
time.sleep(2)
_script_pause_flag = False
_pending_pair_idx = -1
if _auto_running:
threading.Thread(target=_run_pair, args=(_i,), daemon=True).start()
threading.Thread(target=_wait_and_run, daemon=True).start()
except Exception as e:
log(f'[AutoTrade] β par error: {e}')
return 0
# ============================================================
# phBot Events
# ============================================================
def joined_game():
try:
c = get_client()
if c and c.get('window'):
try: QtBind.setText(gui, lbl_window, str(c['window']))
except: pass
ch = get_character_data()
if ch:
try: QtBind.setText(gui, lbl_char, ch.get('name','β'))
except: pass
except: pass
def _delayed_load():
time.sleep(1.5)
_load_config()
_refresh_scripts_comboboxes()
_refresh_death_scripts()
_refresh_npc_combobox()
threading.Thread(target=_delayed_load, daemon=True).start()
def _fill_pet_combobox():
time.sleep(3)
_populate_pet_combobox()
threading.Thread(target=_fill_pet_combobox, daemon=True).start()
_set_status('Ready')
log(f'[{pName}] v{pVersion} β Ready.')
# ============================================================
# ============================================================
_resurrecting = False
def _handle_death(scr_name, was_auto, wait_sec=5):
global _resurrecting, _pet_death_triggered, _death_script_running
global _auto_running, _bot_was_running, _transport_died_flag
if _resurrecting:
return
_resurrecting = True
def _run():
global _resurrecting, _pet_death_triggered, _death_script_running
global _auto_running, _bot_was_running, _transport_died_flag
try:
try: stop_bot()
except: pass
time.sleep(1)
if _stop_flag or not _was_auto_running:
_set_death_status('Stopped by user.')
return
log(f'[AutoTrade] Death detected β waiting {wait_sec}s...')
_set_death_status(f'Died β waiting {wait_sec}s...')
for _ in range(wait_sec * 2):
if _stop_flag: return
time.sleep(0.5)
if _stop_flag:
_set_death_status('Stopped by user.')
return
try:
use_return_scroll()
log('[AutoTrade] Return scroll used β waiting 5s...')
_set_death_status('Return scroll used β waiting 5s...')
for _ in range(10):
if _stop_flag: return
time.sleep(0.5)
inject_joymax(0x3053, b'\x01', True)
except:
pass
if _stop_flag:
_set_death_status('Stopped by user.')
return
if not was_auto:
_set_death_status('Stopped by user.')
return
if scr_name:
scr_path = _resolve_script_path(scr_name)
if scr_path:
try:
set_training_script(scr_path)
_death_script_running = True
_auto_running = True
_bot_was_running = True
start_bot()
log(f'[AutoTrade] Death script started: {scr_name}')
_set_death_status(f'Bot started: {scr_name}')
except Exception as e:
log(f'[AutoTrade] β death script error: {e}')
_queue_notification(
'Recovery script could not be started: %s' %
scr_name, 'errors')
_auto_running = False
else:
log(f'[AutoTrade] Death script not found: {scr_name}')
_auto_running = False
threading.Thread(target=btn_auto_start_cb, daemon=True).start()
else:
log('[AutoTrade] No death script β restarting auto...')
_auto_running = False
threading.Thread(target=btn_auto_start_cb, daemon=True).start()
finally:
_pet_death_triggered = False
_transport_died_flag = False
_death_script_running = False
_resurrecting = False
threading.Thread(target=_run, daemon=True).start()
def teleported():
global _teleport_time, _script_teleporting, _script_teleport_time, _gate_crossing
_teleport_time = time.time()
_script_teleport_time = time.time()
_script_teleporting = False
_gate_crossing = False
def event_loop():
pass
def handle_event(t, data):
global _death_triggered, _pet_death_triggered
global _bot_was_running, _pair_running, _auto_running
global _transport_died_flag, _was_auto_running
if t == 7:
if _resurrecting: return
# AutoTrade calisiyor mu? Calismiyorsa (elle PvP vb.) tamamen yoksay.
# handle_joymax(0x30BF)'deki ile ayni guard - yoksa normal olumde de sehre donuyordu.
if not _auto_running and not _bot_was_running and not _pair_running: return
try:
if not QtBind.isChecked(gui, chk_char_death): return
except: return
try: inject_joymax(0x3053, b'\x01', True)
except: pass
scr = ''
try: scr = QtBind.text(gui, tbx_death_scr).strip()
except: pass
log('[AutoTrade] Died β starting death sequence')
_queue_notification(
'Character died; recovery sequence started.', 'deaths')
_set_death_status('Died β starting death sequence...')
_death_triggered = True
_was_auto_running = _auto_running
_auto_running = False
_bot_was_running = False
_pair_running = False
_handle_death(scr, _was_auto_running, wait_sec=20)
elif t == 3:
if _resurrecting: return
if _death_script_running: return
try:
if not QtBind.isChecked(gui, chk_pet_death):
return
except:
return
current_region = 0
try:
pos = get_position()
if pos: current_region = pos.get('region', 0)
except: pass
def _confirm_transport_death(_region=current_region):
global _pet_death_triggered, _transport_died_flag, _was_auto_running
global _bot_was_running, _pair_running, _auto_running
time.sleep(1.0)
if not _auto_running or _death_script_running or _stop_flag or _transport_died_flag or _resurrecting:
return
try:
pos = get_position()
new_region = pos.get('region', 0) if pos else 0
if new_region != _region and _region != 0:
return
except: pass
if not _auto_running and not _was_auto_running:
return
with _death_lock:
if _pet_death_triggered or _transport_died_flag or _resurrecting:
return
log('[AutoTrade] β‘ Transport died β starting death sequence')
_queue_notification(
'Transport died; recovery sequence started.', 'deaths')
_set_death_status('Transport died β returning to town...')
_pet_death_triggered = True
_transport_died_flag = True
_was_auto_running = _auto_running
_bot_was_running = False
_pair_running = False
_auto_running = False
scr = ''
try: scr = QtBind.text(gui, tbx_death_scr).strip()
except: pass
_handle_death(scr, _was_auto_running, wait_sec=20)
threading.Thread(target=_confirm_transport_death, daemon=True).start()
# ============================================================
# Startup
# ============================================================
_load_config()
_refresh_scripts_comboboxes()
_refresh_death_scripts()
log('[%s] Loaded - β Made By FascinaTe' % pName)