from phBot import *
import QtBind
import phBotChat
import json
import os
import struct
import time
import webbrowser
pName = 'FSroRAutoTrade'
pVersion = '3.6.2'
DISCORD_URL = 'https://discord.gg/eB9sGSMYBg'
CHAT_PARTY = 4
SYNC_PROTOCOL = '#FRT'
EVENT_TRANSPORT_DIED = 3
EVENT_DIED = 7
STATE_IDLE = 'IDLE'
STATE_RETURNING = 'RETURNING'
STATE_LEAVING_PARTY = 'LEAVING_PARTY'
STATE_EQUIPPING = 'EQUIPPING'
STATE_RUNNING_TRADE = 'RUNNING_TRADE'
STATE_UNEQUIPPING = 'UNEQUIPPING'
STATE_WAITING_ACTION = 'WAITING_ACTION'
STATE_SETTLING_POUCH = 'SETTLING_POUCH'
STATE_DEATH_RECOVERY = 'DEATH_RECOVERY'
STATE_ERROR_RECOVERY = 'ERROR_RECOVERY'
STATE_ERROR = 'ERROR'
POLL_SECONDS = 1.0
RETURN_TIMEOUT = 90.0
PARTY_TIMEOUT = 20.0
EQUIP_TIMEOUT = 20.0
TRADE_TIMEOUT = 1800.0
CITY_CONFIRM_TIMEOUT = 90.0
DEATH_RECOVERY_TIMEOUT = 60.0
ERROR_RECOVERY_TIMEOUT = 90.0
RESPAWN_RETRY_SECONDS = 5.0
RESPAWN_MAX_ATTEMPTS = 3
RESPAWN_FINAL_WAIT_SECONDS = 10.0
POUCH_SETTLE_TIMEOUT = 30.0
POUCH_SETTLE_INTERVAL = 2.0
POUCH_STABLE_READS = 3
ACTION_RETRY_SECONDS = 3.0
TRANSPORT_DEATH_CONFIRM_SECONDS = 5.0
TRANSPORT_TELEPORT_GRACE_SECONDS = 5.0
TRANSPORT_SNAPSHOT_MAX_AGE = 10.0
DEFAULT_SYNC_CHECK_INTERVAL = 10.0
SYNC_RESPONSE_TIMEOUT = 30.0
SYNC_PREPARE_TIMEOUT = 30.0
state = STATE_IDLE
state_since = time.time()
last_poll = 0.0
last_action = 0.0
cycle_active = False
cycle_armed = True
unequip_request_sent = False
unequip_destination = -1
trade_command_received = False
trade_settled_received = False
trade_complete_time = 0.0
profile_name = 'default'
settings_loading = False
job_candidates = []
script_candidates = []
training_inside_streak = 0
pending_action = None
pouch_settle_started = 0.0
pouch_last_poll = 0.0
pouch_last_count = None
pouch_stable_reads = 0
death_teleport_received = False
respawn_attempts = 0
last_respawn_request = 0.0
last_box_count = None
status_panel_open = False
STATUS_OFFSCREEN_X = 3000
pending_transport_death_time = 0.0
pending_transport_death_region = 0
last_teleport_time = 0.0
tracked_transport_id = 0
transport_load_seen = False
transport_unloaded_after_load = False
last_transport_box_count = None
last_transport_snapshot_time = 0.0
profile_candidates = []
trade_profile_active = False
sync_run_id = ''
sync_phase = 'IDLE'
sync_expected_members = set()
sync_ready_members = {}
sync_ack_members = set()
sync_member_aliases = {}
sync_last_check = 0.0
sync_phase_since = 0.0
sync_wait_details = {}
current_language = 'en'
def _config_directory():
try:
root = get_config_dir()
except Exception:
root = None
if not root:
root = os.path.dirname(os.path.abspath(__file__))
return os.path.join(root, pName)
def _scripts_directory():
return os.path.join(_config_directory(), 'scripts')
def _settings_path():
return os.path.join(_config_directory(), 'settings.json')
def _ensure_directories():
try:
if not os.path.isdir(_scripts_directory()):
os.makedirs(_scripts_directory())
return True
except Exception as ex:
log('[%s] Config klasoru olusturulamadi: %s' % (pName, ex))
return False
def _fixed_width_text(content, width):
return ('
') % (width, content)
gui = QtBind.init(__name__, pName)
QtBind.createLabel(
gui, '🐫 FSRO-R AUTO TRADE', 12, 6
)
QtBind.createLabel(gui, 'v%s' % pVersion, 280, 12)
btn_language = QtBind.createButton(gui, 'btn_language_clicked', 'EN / TR', 325, 3)
btn_status_tab = QtBind.createButton(gui, 'btn_status_tab_clicked', '📊 Sync Status', 385, 3)
btn_discord = QtBind.createButton(gui, 'discord_clicked', u'💬 Discord', 510, 3)
QtBind.createLabel(
gui, u'⚜ Made By FascinaTe', 610, 11
)
QtBind.createLineEdit(gui, '', 12, 30, 716, 1)
lbl_box_section = QtBind.createLabel(gui, '📦 BOX CONTROL', 10, 38)
chk_enabled = QtBind.createCheckBox(gui, 'chk_enabled_changed', 'Plugin aktif', 10, 58)
lbl_box_total_title = QtBind.createLabel(gui, 'Total boxes:', 10, 83)
# QtBind label genisligini ilk metne gore sabitler. Genis bir ilk degerle
# olusturup tekrar 0'a cekmek, 3+ haneli kutu sayilarinin kirpilmasini onler.
lbl_box_count = QtBind.createLabel(
gui, _fixed_width_text('0', 80), 125, 83)
lbl_target_title = QtBind.createLabel(gui, 'Trade target:', 10, 111)
txt_target = QtBind.createLineEdit(gui, '80', 125, 107, 70, 22)
lbl_safety_title = QtBind.createLabel(gui, 'Safety limit (<):', 10, 139)
txt_safety = QtBind.createLineEdit(gui, '5', 125, 135, 70, 22)
lbl_delay_title = QtBind.createLabel(gui, 'Action delay:', 10, 167)
txt_action_delay = QtBind.createLineEdit(gui, '2000', 125, 163, 70, 22)
lbl_delay_unit = QtBind.createLabel(gui, 'ms', 198, 167)
btn_check = QtBind.createButton(gui, 'btn_check_clicked', '↻ Kutuyu Kontrol Et', 10, 192)
lbl_job_section = QtBind.createLabel(gui, '🥷 JOB ITEM', 240, 38)
cmb_job_item = QtBind.createCombobox(gui, 240, 63, 330, 22)
btn_refresh_jobs = QtBind.createButton(gui, 'btn_refresh_jobs_clicked', '↻ Job Itemlerini Yenile', 240, 95)
chk_grind_with_job = QtBind.createCheckBox(
gui, 'chk_grind_with_job_changed', 'Kasarken job itemi giyili kalsin', 240, 130
)
lbl_required_commands = QtBind.createLabel(
gui, '⚠ Scriptte ZORUNLU komutlar:', 240, 155
)
lbl_settled_command = QtBind.createLabel(
gui, 'Trade sonrasi: FSroRAutoTrade_settled', 240, 175
)
lbl_complete_command = QtBind.createLabel(
gui, 'Script sonunda: FSroRAutoTrade_complete', 240, 195
)
lbl_script_section = QtBind.createLabel(
gui, '📜 KERVAN SCRIPTİ'
' Config/FSroRAutoTrade/scripts', 10, 220
)
cmb_script = QtBind.createCombobox(gui, 10, 237, 400, 22)
btn_refresh_scripts = QtBind.createButton(gui, 'btn_refresh_scripts_clicked', '↻ Scriptleri Yenile', 425, 236)
btn_save = QtBind.createButton(gui, 'btn_save_clicked', '💾 Ayarlari Kaydet', 10, 280)
btn_manual = QtBind.createButton(gui, 'btn_manual_clicked', '▶ Kervani Manuel Baslat', 145, 280)
btn_abort = QtBind.createButton(gui, 'btn_abort_clicked', '■ Abort Local', 330, 280)
btn_rearm = QtBind.createButton(gui, 'btn_rearm_clicked', '↻ Re-arm This Client', 465, 280)
lbl_live_section = QtBind.createLabel(gui, '● LIVE STATUS', 10, 320)
lbl_state_title = QtBind.createLabel(gui, 'State:', 10, 345)
lbl_state = QtBind.createLabel(
gui, _fixed_width_text('Hazir', 480), 70, 345)
lbl_message = QtBind.createLabel(
gui, _fixed_width_text('Config klasoru hazirlaniyor...', 700), 10, 372)
lbl_training_title = QtBind.createLabel(gui, 'Training Area:', 10, 400)
lbl_training = QtBind.createLabel(
gui, _fixed_width_text('Kontrol bekleniyor.', 600), 105, 400)
# Sync ekranindaki buyuk arka plan ana ekran widget'larini orter. Header ve geri
# donus butonu her iki ekranda da gorunur kalir.
lst_sync_background = QtBind.createList(gui, STATUS_OFFSCREEN_X, 38, 716, 390)
chk_sync_enabled = QtBind.createCheckBox(
gui, 'chk_sync_enabled_changed', 'Party synchronized trade', STATUS_OFFSCREEN_X, 45)
chk_sync_coordinator = QtBind.createCheckBox(
gui, 'chk_sync_coordinator_changed', 'This character is coordinator', STATUS_OFFSCREEN_X, 45)
chk_error_recovery = QtBind.createCheckBox(
gui, 'chk_error_recovery_changed', 'Recover bot after trade errors', STATUS_OFFSCREEN_X, 45)
lbl_sync_coordinator = QtBind.createLabel(gui, 'Coordinator:', STATUS_OFFSCREEN_X, 77)
txt_sync_coordinator = QtBind.createLineEdit(gui, '', STATUS_OFFSCREEN_X, 72, 180, 22)
lbl_sync_members = QtBind.createLabel(gui, 'Required members:', STATUS_OFFSCREEN_X, 77)
txt_sync_members = QtBind.createLineEdit(gui, '', STATUS_OFFSCREEN_X, 72, 265, 22)
lbl_farm_profile = QtBind.createLabel(gui, 'Farm Profile:', STATUS_OFFSCREEN_X, 112)
cmb_farm_profile = QtBind.createCombobox(gui, STATUS_OFFSCREEN_X, 107, 180, 22)
lbl_trade_profile = QtBind.createLabel(gui, 'Trade Profile:', STATUS_OFFSCREEN_X, 112)
cmb_trade_profile = QtBind.createCombobox(gui, STATUS_OFFSCREEN_X, 107, 180, 22)
btn_refresh_profiles = QtBind.createButton(
gui, 'btn_refresh_profiles_clicked', '↻ Profiles', STATUS_OFFSCREEN_X, 106)
lbl_sync_state = QtBind.createLabel(
gui, _fixed_width_text('Sync idle.', 700),
STATUS_OFFSCREEN_X, 180)
lbl_sync_check_interval = QtBind.createLabel(
gui, 'CHECK interval:', STATUS_OFFSCREEN_X, 147)
txt_sync_check_interval = QtBind.createLineEdit(
gui, '10', STATUS_OFFSCREEN_X, 142, 55, 22)
lbl_sync_check_interval_unit = QtBind.createLabel(
gui, 'sec', STATUS_OFFSCREEN_X, 147)
lst_status_panel = QtBind.createList(gui, STATUS_OFFSCREEN_X, 210, 716, 200)
sync_panel_positions = (
(lst_sync_background, 12, 38),
(chk_sync_enabled, 20, 45),
(chk_sync_coordinator, 245, 45),
(chk_error_recovery, 500, 45),
(lbl_sync_coordinator, 20, 77),
(txt_sync_coordinator, 125, 72),
(lbl_sync_members, 325, 77),
(txt_sync_members, 450, 72),
(lbl_farm_profile, 20, 112),
(cmb_farm_profile, 125, 107),
(lbl_trade_profile, 325, 112),
(cmb_trade_profile, 430, 107),
(btn_refresh_profiles, 625, 106),
(lbl_sync_check_interval, 20, 147),
(txt_sync_check_interval, 135, 142),
(lbl_sync_check_interval_unit, 195, 147),
(lbl_sync_state, 20, 180),
(lst_status_panel, 12, 210)
)
main_panel_positions = (
(lbl_box_section, 10, 38), (chk_enabled, 10, 58),
(lbl_box_total_title, 10, 83), (lbl_box_count, 125, 83),
(lbl_target_title, 10, 111), (txt_target, 125, 107),
(lbl_safety_title, 10, 139), (txt_safety, 125, 135),
(lbl_delay_title, 10, 167), (txt_action_delay, 125, 163),
(lbl_delay_unit, 198, 167), (btn_check, 10, 192),
(lbl_job_section, 240, 38), (cmb_job_item, 240, 63),
(btn_refresh_jobs, 240, 95), (chk_grind_with_job, 240, 130),
(lbl_required_commands, 240, 155), (lbl_settled_command, 240, 175),
(lbl_complete_command, 240, 195), (lbl_script_section, 10, 220),
(cmb_script, 10, 237), (btn_refresh_scripts, 425, 236),
(btn_save, 10, 280), (btn_manual, 145, 280), (btn_abort, 330, 280),
(btn_rearm, 465, 280),
(lbl_live_section, 10, 320), (lbl_state_title, 10, 345),
(lbl_state, 70, 345), (lbl_message, 10, 372),
(lbl_training_title, 10, 400), (lbl_training, 105, 400)
)
TRANSLATIONS = {
'en': {
'status': '📊 Sync Status', 'back': '← Main Screen',
'box_section': '📦 BOX CONTROL',
'enabled': 'Plugin enabled', 'total': 'Total boxes:',
'target': 'Trade target:',
'safety': 'Safety limit (<):',
'delay': 'Action delay:',
'check': '↻ Check Boxes', 'job_section': '🥷 JOB ITEM',
'refresh_jobs': '↻ Refresh Job Items', 'grind_job': 'Keep job item equipped while grinding',
'required': '⚠ Required script commands:',
'settled': 'After trade: FSroRAutoTrade_settled',
'complete': 'Script end: FSroRAutoTrade_complete',
'script': '📜 TRADE SCRIPT Config/FSroRAutoTrade/scripts',
'refresh_scripts': '↻ Refresh Scripts', 'save': '💾 Save Settings',
'manual': '▶ Start Trade Manually', 'abort': '■ Abort Local',
'rearm': '↻ Re-arm This Client',
'live': '● LIVE STATUS',
'state': 'State:',
'training': 'Training Area:',
'sync_enabled': 'Party synchronized trade', 'is_coordinator': 'This character is coordinator',
'error_recovery': 'Recover bot after trade errors',
'coordinator': 'Coordinator:', 'members': 'Required members:',
'farm': 'Farm Profile:', 'trade': 'Trade Profile:', 'profiles': '↻ Profiles',
'sync_interval': 'CHECK interval:', 'seconds': 'sec'
},
'tr': {
'status': '📊 Senkron Durum', 'back': '← Ana Ekran',
'box_section': '📦 KUTU KONTROLÜ',
'enabled': 'Plugin aktif', 'total': 'Toplam kutu:',
'target': 'Kervan hedefi:',
'safety': 'Güvenlik sınırı (<):',
'delay': 'Komut gecikmesi:',
'check': '↻ Kutuyu Kontrol Et', 'job_section': '🥷 JOB ITEMİ',
'refresh_jobs': '↻ Job Itemlerini Yenile', 'grind_job': 'Kasarken job itemi giyili kalsın',
'required': '⚠ Scriptte zorunlu komutlar:',
'settled': 'Trade sonrası: FSroRAutoTrade_settled',
'complete': 'Script sonunda: FSroRAutoTrade_complete',
'script': '📜 KERVAN SCRIPTİ Config/FSroRAutoTrade/scripts',
'refresh_scripts': '↻ Scriptleri Yenile', 'save': '💾 Ayarları Kaydet',
'manual': '▶ Kervanı Manuel Başlat', 'abort': '■ Yerel İşlemi Durdur',
'rearm': '↻ Bu Clientı Hazırla',
'live': '● CANLI DURUM',
'state': 'Durum:',
'training': 'Training Area:',
'sync_enabled': 'Parti senkronlu kervan', 'is_coordinator': 'Bu karakter koordinatör',
'error_recovery': 'Kervan hatasından sonra botu kurtar',
'coordinator': 'Koordinatör:', 'members': 'Zorunlu üyeler:',
'farm': 'Farm Profili:', 'trade': 'Trade Profili:', 'profiles': '↻ Profiller',
'sync_interval': 'CHECK aralığı:', 'seconds': 'sn'
}
}
def _ui(key):
return TRANSLATIONS.get(current_language, TRANSLATIONS['en']).get(key, key)
def _apply_language():
pairs = (
(lbl_box_section, 'box_section'), (chk_enabled, 'enabled'),
(lbl_box_total_title, 'total'), (lbl_target_title, 'target'),
(lbl_safety_title, 'safety'), (lbl_delay_title, 'delay'),
(btn_check, 'check'), (lbl_job_section, 'job_section'),
(btn_refresh_jobs, 'refresh_jobs'), (chk_grind_with_job, 'grind_job'),
(lbl_required_commands, 'required'), (lbl_settled_command, 'settled'),
(lbl_complete_command, 'complete'), (lbl_script_section, 'script'),
(btn_refresh_scripts, 'refresh_scripts'), (btn_save, 'save'),
(btn_manual, 'manual'), (btn_abort, 'abort'), (btn_rearm, 'rearm'),
(lbl_live_section, 'live'),
(lbl_state_title, 'state'), (lbl_training_title, 'training'),
(chk_sync_enabled, 'sync_enabled'), (chk_sync_coordinator, 'is_coordinator'),
(chk_error_recovery, 'error_recovery'),
(lbl_sync_coordinator, 'coordinator'), (lbl_sync_members, 'members'),
(lbl_farm_profile, 'farm'), (lbl_trade_profile, 'trade'),
(btn_refresh_profiles, 'profiles'),
(lbl_sync_check_interval, 'sync_interval'),
(lbl_sync_check_interval_unit, 'seconds')
)
for widget, key in pairs:
QtBind.setText(gui, widget, _ui(key))
QtBind.setText(gui, btn_status_tab, _ui('back') if status_panel_open else _ui('status'))
QtBind.setText(gui, btn_language, 'TR' if current_language == 'en' else 'EN')
def _html_escape(value):
return str(value).replace('&', '&').replace('<', '<').replace('>', '>')
def _set_message(message, write_log=False):
QtBind.setText(gui, lbl_message,
_fixed_width_text(
'%s' % _html_escape(message), 700))
if write_log:
log('[%s] %s' % (pName, message))
def _set_state(new_state, message):
global state, state_since, last_action
state = new_state
state_since = time.time()
last_action = 0.0
color = '#CC0000' if new_state == STATE_ERROR else '#c98a1a'
if new_state == STATE_IDLE:
color = '#1f9d63'
QtBind.setText(gui, lbl_state,
_fixed_width_text(
'● %s' % (color, new_state), 480))
_set_message(message, True)
def _set_training_text(message):
QtBind.setText(gui, lbl_training, _fixed_width_text(
'%s' % _html_escape(message), 600))
def _set_sync_text(message, color='#9aa0ac'):
QtBind.setText(gui, lbl_sync_state, _fixed_width_text(
'%s' % (color, _html_escape(message)), 700))
def _action_delay_seconds():
value = _number(txt_action_delay, 0, 'Komut gecikmesi')
if value is None:
return 2.0
return min(value, 60000) / 1000.0
def _schedule_action(action, message):
global pending_action
pending_action = action
_set_state(STATE_WAITING_ACTION, '%s (%d ms bekleniyor)' % (
message, int(_action_delay_seconds() * 1000)))
def _fail(message, allow_recovery=True):
global cycle_active, cycle_armed, pending_action, pending_transport_death_time
recovery_enabled = False
try:
recovery_enabled = bool(
allow_recovery and cycle_active and
QtBind.isChecked(gui, chk_error_recovery) and
state not in (STATE_DEATH_RECOVERY, STATE_ERROR_RECOVERY))
except Exception:
recovery_enabled = False
pending_action = None
pending_transport_death_time = 0.0
try:
stop_script()
except Exception:
pass
try:
stop_bot()
except Exception:
pass
if recovery_enabled:
cycle_active = True
cycle_armed = True
_set_state(STATE_ERROR_RECOVERY,
'%s Otomatik hata kurtarma icin sehir dogrulaniyor.' % message)
else:
cycle_active = False
_set_state(STATE_ERROR, message)
def _recover_error_at_training():
"""Kullanici botu manuel baslatip slota donunce gecici hatayi temizler."""
global cycle_active, cycle_armed, pending_action
global trade_command_received, trade_settled_received, trade_complete_time
global pending_transport_death_time, pending_transport_death_region
global pouch_settle_started, pouch_last_poll
global pouch_last_count, pouch_stable_reads, death_teleport_received
global respawn_attempts, last_respawn_request
cycle_active = False
cycle_armed = True
pending_action = None
trade_command_received = False
trade_settled_received = False
trade_complete_time = 0.0
pending_transport_death_time = 0.0
_reset_transport_tracking()
pending_transport_death_region = 0
pouch_settle_started = 0.0
pouch_last_poll = 0.0
pouch_last_count = None
pouch_stable_reads = 0
death_teleport_received = False
respawn_attempts = 0
last_respawn_request = 0.0
_set_state(STATE_IDLE,
'Training area 3/3 dogrulandi; hata sifirlandi ve kontrol yeniden basladi.')
def _iter_items(value):
if value is None:
return
if isinstance(value, (list, tuple)):
for entry in value:
for item in _iter_items(entry):
yield item
return
if not isinstance(value, dict):
return
if ('name' in value or 'servername' in value or 'model' in value):
yield value
return
if 'items' in value:
for item in _iter_items(value.get('items')):
yield item
return
for entry in value.values():
for item in _iter_items(entry):
yield item
def _quantity(item):
try:
value = int(item.get('quantity', 1) or 1)
return value if value > 0 else 0
except (TypeError, ValueError):
return 1
def _is_specialty_box(item):
name = str(item.get('name') or '').strip().lower()
servername = str(item.get('servername') or '').strip().lower()
if 'specialty goods box' in name:
return True
return ('specialty' in servername and
('goods' in servername or 'box' in servername))
def _read_box_count(show_error=False):
global last_box_count
try:
pouch = get_job_pouch()
except Exception as ex:
if show_error:
_set_message('Job pouch okunamadi: %s' % ex, True)
return None
if pouch is None:
if show_error:
_set_message('Job pouch verisi henuz hazir degil.', True)
return None
total = sum(_quantity(item) for item in _iter_items(pouch)
if _is_specialty_box(item))
last_box_count = total
QtBind.setText(gui, lbl_box_count,
_fixed_width_text(
'%d' % total, 80))
return total
def _training_debug_snapshot():
result = {
'status': 'Bilinmiyor', 'position_region': '-', 'training_region': '-',
'distance': '-', 'radius': '-', 'inside': False
}
try:
area = get_training_area()
position = get_position()
except Exception as ex:
result['status'] = 'API hatasi: %s' % ex
return result
if not area:
result['status'] = 'Aktif training area yok'
return result
if not position:
result['status'] = 'Karakter konumu hazir degil'
return result
try:
area_region = int(area.get('region', 0) or 0)
position_region = int(position.get('region', 0) or 0)
radius = float(area.get('radius', 50.0) or 50.0)
dx = float(position.get('x', 0.0)) - float(area.get('x', 0.0))
dy = float(position.get('y', 0.0)) - float(area.get('y', 0.0))
distance = (dx * dx + dy * dy) ** 0.5
inside = distance <= radius
result.update({
'status': 'Iceride' if inside else 'Disarida',
'position_region': position_region,
'training_region': area_region,
'distance': '%.1f' % distance,
'radius': '%.1f' % radius,
'inside': inside
})
except (TypeError, ValueError, KeyError) as ex:
result['status'] = 'Gecersiz veri: %s' % ex
return result
def _refresh_status_panel():
if not status_panel_open:
return
training = _training_debug_snapshot()
target_text = QtBind.text(gui, txt_target) or '-'
safety_text = QtBind.text(gui, txt_safety) or '-'
delay_text = QtBind.text(gui, txt_action_delay) or '-'
job = _selected_job() or {}
script_name = _selected_script_name() or '-'
action_name = '-'
if pending_action:
action_name = getattr(pending_action, '__name__', str(pending_action))
tr = current_language == 'tr'
blockers = []
if not QtBind.isChecked(gui, chk_enabled):
blockers.append('Plugin pasif' if tr else 'Plugin disabled')
if state in (STATE_ERROR, STATE_ERROR_RECOVERY):
blockers.append('Hata/kurtarma durumu' if tr else 'Error/recovery state')
if cycle_active:
blockers.append('Aktif döngü var' if tr else 'Cycle already active')
if training_inside_streak < 3:
blockers.append('Training 3/3 değil' if tr else 'Training is not 3/3')
if not cycle_armed:
blockers.append('Cycle armed kapalı' if tr else 'Cycle is not armed')
try:
if last_box_count is None or int(last_box_count) < int(target_text):
blockers.append('Kutu hedefin altında' if tr else 'Boxes below target')
except Exception:
blockers.append('Kutu/hedef geçersiz' if tr else 'Invalid box/target value')
labels = ({
'title': '=== FSroRAutoTrade CANLI DURUM ===', 'action': 'Bekleyen aksiyon',
'boxes': 'Kutu / hedef', 'safety': 'Güvenlik sınırı', 'delay': 'Komut gecikmesi',
'distance': 'Mesafe / radius', 'job': 'Seçili job itemi', 'script': 'Seçili script',
'settled': 'Trade settled alındı', 'complete': 'Trade complete alındı',
'transport_event': 'Transport event bekliyor', 'transport_load': 'Transport son yükü',
'load_seen': 'Transport yük görüldü', 'delivered': 'Transport teslim edildi',
'blockers': 'Otomatik tetik engeli', 'updated': 'Son güncelleme',
'none': 'YOK - tetiklemeye hazır'
} if tr else {
'title': '=== FSroRAutoTrade LIVE STATUS ===', 'action': 'Pending action',
'boxes': 'Boxes / target', 'safety': 'Safety limit', 'delay': 'Action delay',
'distance': 'Distance / radius', 'job': 'Selected job item', 'script': 'Selected script',
'settled': 'Trade settled received', 'complete': 'Trade complete received',
'transport_event': 'Transport event pending', 'transport_load': 'Last transport load',
'load_seen': 'Transport load seen', 'delivered': 'Transport delivered',
'blockers': 'Automatic trigger blockers', 'updated': 'Last update',
'none': 'NONE - ready to trigger'
})
rows = [
labels['title'],
'Plugin enabled : %s' % QtBind.isChecked(gui, chk_enabled),
'State : %s' % state,
'Cycle active : %s' % cycle_active,
'Cycle armed : %s' % cycle_armed,
'%-21s: %s' % (labels['action'], action_name),
'%-21s: %s / %s' % (labels['boxes'],
'-' if last_box_count is None else last_box_count, target_text),
'%-21s: <%s' % (labels['safety'], safety_text),
'%-21s: %s ms' % (labels['delay'], delay_text),
'Training status : %s' % training['status'],
'Training streak : %d/3' % training_inside_streak,
'Position region : %s' % training['position_region'],
'Training region : %s' % training['training_region'],
'%-21s: %s / %s' % (labels['distance'], training['distance'], training['radius']),
'%-21s: %s' % (labels['job'],
job.get('name') or job.get('servername') or '-'),
'%-21s: %s' % (labels['script'], script_name),
'Sync enabled : %s' % QtBind.isChecked(gui, chk_sync_enabled),
'CHECK interval : %s sec' % (
QtBind.text(gui, txt_sync_check_interval) or '-'),
'Error auto-recovery : %s' % QtBind.isChecked(gui, chk_error_recovery),
'Sync coordinator : %s' % _coordinator_name(),
'Sync phase / run : %s / %s' % (sync_phase, sync_run_id or '-'),
'READY / expected : %d / %d' % (
len(sync_ready_members), len(sync_expected_members)),
'ACK / expected : %d / %d' % (
len(sync_ack_members), len(sync_expected_members)),
'Farm / trade profile : %s / %s' % (
_selected_phbot_profile(cmb_farm_profile),
_selected_phbot_profile(cmb_trade_profile)),
'%-21s: %s' % (labels['settled'], trade_settled_received),
'%-21s: %s' % (labels['complete'], trade_command_received),
'%-21s: %s' % (labels['transport_event'], pending_transport_death_time > 0),
'%-21s: %s' % (labels['transport_load'],
'-' if last_transport_box_count is None else last_transport_box_count),
'%-21s: %s' % (labels['load_seen'], transport_load_seen),
'%-21s: %s' % (labels['delivered'], transport_unloaded_after_load),
'%-21s: %s' % (labels['blockers'], ', '.join(blockers) if blockers else labels['none']),
'%-21s: %s' % (labels['updated'], time.strftime('%H:%M:%S'))
]
QtBind.clear(gui, lst_status_panel)
for row in rows:
QtBind.append(gui, lst_status_panel, row)
def _update_training_status():
"""Karakterin aktif training radius'u icinde oldugunu dogrular."""
global training_inside_streak
try:
area = get_training_area()
position = get_position()
except Exception as ex:
training_inside_streak = 0
_set_training_text('Konum okunamadi: %s' % ex)
return False
if not area:
training_inside_streak = 0
_set_training_text('Aktif training area yok.')
return False
if not position:
training_inside_streak = 0
_set_training_text('Karakter konumu hazir degil.')
return False
try:
radius = float(area.get('radius', 50.0) or 50.0)
dx = float(position.get('x', 0.0)) - float(area.get('x', 0.0))
dy = float(position.get('y', 0.0)) - float(area.get('y', 0.0))
distance_squared = dx * dx + dy * dy
inside = distance_squared <= radius * radius
distance = distance_squared ** 0.5
except (TypeError, ValueError, KeyError) as ex:
training_inside_streak = 0
_set_training_text('Training verisi gecersiz: %s' % ex)
return False
if inside:
training_inside_streak = min(3, training_inside_streak + 1)
_set_training_text('Iceride %.1fm/%.1fm (%d/3)' % (
distance, radius, training_inside_streak))
return training_inside_streak >= 3
training_inside_streak = 0
_set_training_text('Disarida %.1fm/%.1fm' % (distance, radius))
return False
def _is_in_town():
"""Resmi city API'si olmadigi icin yakin sehir NPC'leriyle dogrular."""
try:
position = get_position()
npcs = get_npcs()
except Exception:
return False
if not position or not npcs:
return False
keywords = (
'STORE', 'WAREHOUSE', 'BLACKSMITH', 'POTION', 'GROCERY',
'STABLE', 'SMITH', 'ARMOR', 'WEAPON', 'ACCESSORY'
)
nearby_count = 0
for npc in npcs.values():
try:
npc_region = int(npc.get('region', 0) or 0)
position_region = int(position.get('region', 0) or 0)
if npc_region and position_region and npc_region != position_region:
continue
dx = float(position.get('x', 0.0)) - float(npc.get('x', 0.0))
dy = float(position.get('y', 0.0)) - float(npc.get('y', 0.0))
if dx * dx + dy * dy > 120.0 * 120.0:
continue
nearby_count += 1
text = ('%s %s' % (
npc.get('name', ''), npc.get('servername', ''))).upper()
if any(keyword in text for keyword in keywords):
return True
except (TypeError, ValueError):
continue
# Return noktalarinda genellikle birden fazla sabit NPC gorunur.
return nearby_count >= 2
def _transport_is_alive():
try:
pets = get_pets()
if not isinstance(pets, dict):
return False
for pet in pets.values():
if not isinstance(pet, dict):
continue
if pet.get('type') not in ('transport', 'horse'):
continue
try:
if float(pet.get('hp', 1) or 0) > 0:
return True
except (TypeError, ValueError):
return True
except Exception as ex:
log('[%s] Transport kontrol edilemedi: %s' % (pName, ex))
return False
def _reset_transport_tracking():
global tracked_transport_id, transport_load_seen
global transport_unloaded_after_load, last_transport_box_count
global last_transport_snapshot_time
tracked_transport_id = 0
transport_load_seen = False
transport_unloaded_after_load = False
last_transport_box_count = None
last_transport_snapshot_time = 0.0
def _poll_transport_load(now):
"""Ayni transport petindeki yuklu kutularin teslimatta bosaldigini izler."""
global tracked_transport_id, transport_load_seen
global transport_unloaded_after_load, last_transport_box_count
global last_transport_snapshot_time
try:
pets = get_pets()
except Exception:
return
if not isinstance(pets, dict):
return
selected_id = 0
selected_pet = None
for pet_id, pet in pets.items():
if not isinstance(pet, dict):
continue
if pet.get('type') == 'transport':
selected_id, selected_pet = pet_id, pet
break
if selected_pet is None and pet.get('type') == 'horse':
selected_id, selected_pet = pet_id, pet
if selected_pet is None:
return
count = sum(_quantity(item) for item in _iter_items(selected_pet.get('items'))
if _is_specialty_box(item))
try:
selected_id = int(selected_id)
except (TypeError, ValueError):
selected_id = str(selected_id)
# Teleport sonrasi yeni entity ID gelirse onceki petin sifirlanmasi teslimat
# sayilmaz; yeni pet icin temiz bir snapshot serisi baslatilir.
if selected_id != tracked_transport_id:
tracked_transport_id = selected_id
last_transport_box_count = count
last_transport_snapshot_time = now
if count > 0:
transport_load_seen = True
transport_unloaded_after_load = False
return
previous = last_transport_box_count
if count > 0:
transport_load_seen = True
transport_unloaded_after_load = False
elif transport_load_seen and previous is not None and previous > 0:
transport_unloaded_after_load = True
log('[%s] Transport yuku teslimat sonrasi bosaldi (%d -> 0).' %
(pName, previous))
last_transport_box_count = count
last_transport_snapshot_time = now
def _confirm_pending_transport_death(now):
global pending_transport_death_time, pending_transport_death_region
if pending_transport_death_time <= 0:
return
if now - pending_transport_death_time < TRANSPORT_DEATH_CONFIRM_SECONDS:
return
event_time = pending_transport_death_time
event_region = pending_transport_death_region
pending_transport_death_time = 0.0
pending_transport_death_region = 0
if not cycle_active:
return
if (last_teleport_time and
abs(last_teleport_time - event_time) <= TRANSPORT_TELEPORT_GRACE_SECONDS):
_set_message('Transport event teleport kaynakli; yok sayildi.', True)
return
try:
position = get_position()
current_region = int(position.get('region', 0) or 0) if position else 0
except Exception:
current_region = 0
if event_region and current_region and event_region != current_region:
_set_message('Transport event region gecisi kaynakli; yok sayildi.', True)
return
if _transport_is_alive():
_set_message('Transport halen canli; olum eventi yok sayildi.', True)
return
if trade_settled_received:
_set_message('Trade teslimati isaretlendi; pet termination eventi yok sayildi.', True)
return
snapshot_fresh = (last_transport_snapshot_time > 0 and
now - last_transport_snapshot_time <= TRANSPORT_SNAPSHOT_MAX_AGE)
if (snapshot_fresh and transport_load_seen and
transport_unloaded_after_load and last_transport_box_count == 0):
_set_message('Yuk teslim edilip transport kapatildi; event yok sayildi.', True)
return
if snapshot_fresh and last_transport_box_count is not None and last_transport_box_count > 0:
_fail('Transport olumu dogrulandi; son gorulen yuk %d kutu.' %
last_transport_box_count, False)
return
# Pet yuk snapshot'i yoksa despawn ile gercek olumu guvenilir bicimde
# ayiramayiz. Yanlis pozitif ile tamamlanan kervani kesmek yerine script
# watchdog'una birak.
_set_message('Transport eventi yuk snapshoti olmadigi icin yok sayildi.', True)
def _number(widget, minimum, label):
try:
value = int(QtBind.text(gui, widget).strip())
except Exception:
_set_message('%s tam sayi olmali.' % label, True)
return None
if value < minimum:
_set_message('%s en az %d olmali.' % (label, minimum), True)
return None
return value
def _sync_check_interval():
try:
return max(2.0, float(QtBind.text(gui, txt_sync_check_interval)))
except Exception:
return DEFAULT_SYNC_CHECK_INTERVAL
def _character_name():
try:
data = get_character_data()
if data and data.get('name'):
return str(data.get('name'))
except Exception:
pass
return 'default'
def _read_settings_file():
try:
with open(_settings_path(), 'r') as handle:
data = json.load(handle)
return data if isinstance(data, dict) else {}
except Exception:
return {}
def _selected_job():
try:
selected_label = QtBind.text(gui, cmb_job_item)
for item in job_candidates:
if item.get('label') == selected_label:
return item
except Exception:
pass
return None
def _selected_script_name():
try:
selected = QtBind.text(gui, cmb_script)
if selected in script_candidates:
return selected
except Exception:
pass
return ''
def _read_script_text(path):
"""Walk scriptini Windows varsayilan codec'inden bagimsiz olarak okur."""
with open(path, 'rb') as handle:
raw = handle.read()
if raw.startswith((b'\xff\xfe', b'\xfe\xff')):
return raw.decode('utf-16')
encodings = ('utf-8-sig', 'cp1254', 'cp1256', 'cp1251', 'latin-1')
last_error = None
for encoding in encodings:
try:
return raw.decode(encoding)
except (LookupError, UnicodeDecodeError) as ex:
last_error = ex
raise UnicodeDecodeError(
'unknown', raw, 0, len(raw),
'Desteklenen script kodlamalariyla okunamadi: %s' % last_error)
def _selected_phbot_profile(widget):
selected = str(QtBind.text(gui, widget) or '').strip()
for candidate in profile_candidates:
if candidate['label'] == selected:
return candidate['name']
return None
def _fill_profile_combobox(widget, wanted):
values = list(profile_candidates)
values.sort(key=lambda item: (
0 if item['name'] == wanted else 1,
item['label'].lower()))
_fill_combobox(widget, values)
def _refresh_profiles(wanted_farm=None, wanted_trade=None):
global profile_candidates
try:
character = get_character_data() or {}
server = str(character.get('server') or '').strip()
name = str(character.get('name') or '').strip()
root = get_config_dir()
except Exception:
server, name, root = '', '', None
if not server or not name or not root:
profile_candidates = []
_fill_combobox(cmb_farm_profile, [])
_fill_combobox(cmb_trade_profile, [])
return False
prefix = '%s_%s' % (server, name)
found = {'': 'Default'}
try:
for file_name in os.listdir(root):
if file_name == prefix + '.json':
found[''] = 'Default'
continue
marker = prefix + '.'
if not file_name.startswith(marker) or not file_name.endswith('.json'):
continue
profile = file_name[len(marker):-5]
if profile:
found[profile] = profile
except Exception as ex:
_set_message('phBot profilleri okunamadi: %s' % ex, True)
return False
profile_candidates = [
{'name': profile, 'label': label}
for profile, label in found.items()
]
if wanted_farm is None:
wanted_farm = get_profile()
if wanted_trade is None:
wanted_trade = ''
_fill_profile_combobox(cmb_farm_profile, wanted_farm)
_fill_profile_combobox(cmb_trade_profile, wanted_trade)
return True
def _required_member_names():
raw = str(QtBind.text(gui, txt_sync_members) or '')
return set(value.strip().lower() for value in raw.replace(';', ',').split(',')
if value.strip())
def _profile_values():
job = _selected_job() or {}
return {
'enabled': bool(QtBind.isChecked(gui, chk_enabled)),
'target': _number(txt_target, 1, 'Kervan hedefi'),
'safety': _number(txt_safety, 1, 'Guvenlik siniri'),
'action_delay_ms': _number(txt_action_delay, 0, 'Komut gecikmesi'),
'sync_check_interval': _number(
txt_sync_check_interval, 2, 'Sync CHECK araligi'),
'grind_with_job': bool(QtBind.isChecked(gui, chk_grind_with_job)),
'job_model': int(job.get('model', 0) or 0),
'job_servername': str(job.get('servername') or ''),
'script': _selected_script_name(),
'sync_enabled': bool(QtBind.isChecked(gui, chk_sync_enabled)),
'sync_coordinator': bool(QtBind.isChecked(gui, chk_sync_coordinator)),
'error_recovery': bool(QtBind.isChecked(gui, chk_error_recovery)),
'coordinator_name': str(QtBind.text(gui, txt_sync_coordinator) or '').strip(),
'required_members': str(QtBind.text(gui, txt_sync_members) or '').strip(),
'farm_profile': _selected_phbot_profile(cmb_farm_profile),
'trade_profile': _selected_phbot_profile(cmb_trade_profile),
'language': current_language
}
def _save_settings(silent=False, validate_sync=True):
values = _profile_values()
if (values['target'] is None or values['safety'] is None or
values['action_delay_ms'] is None or
values['sync_check_interval'] is None):
return False
if validate_sync and values['sync_enabled']:
valid, reason = _validate_sync_setup()
if not valid:
_set_sync_text(reason, '#e74c3c')
if not silent:
_set_message('Sync ayari gecersiz: %s' % reason, True)
return False
data = _read_settings_file()
profiles = data.get('profiles')
if not isinstance(profiles, dict):
profiles = {}
profiles[profile_name] = values
data['profiles'] = profiles
try:
_ensure_directories()
with open(_settings_path(), 'w') as handle:
json.dump(data, handle, indent=2, sort_keys=True)
if not silent:
_set_message('Ayarlar kaydedildi: %s' % profile_name, True)
return True
except Exception as ex:
_set_message('Ayarlar kaydedilemedi: %s' % ex, True)
return False
def _fill_combobox(widget, values):
QtBind.clear(gui, widget)
for value in values:
QtBind.append(gui, widget, value.get('label', '') if isinstance(value, dict) else value)
def _inventory_items():
try:
inventory = get_inventory()
except Exception:
return []
if not inventory:
return []
result = []
for slot, item in enumerate(inventory.get('items') or []):
if item:
copy = dict(item)
copy['slot'] = slot
result.append(copy)
return result
def _is_job_item(item):
text = ('%s %s' % (item.get('name', ''), item.get('servername', ''))).upper()
if 'OUTFIT' in text:
return True
try:
data = get_item(int(item.get('model', 0)))
return bool(data and int(data.get('tid2', -1)) == 7)
except Exception:
return False
def _refresh_jobs(wanted_servername='', wanted_model=0):
global job_candidates
candidates = []
seen = set()
for item in _inventory_items():
if not _is_job_item(item):
continue
identity = (int(item.get('model', 0) or 0), str(item.get('servername') or ''))
if identity in seen:
continue
seen.add(identity)
entry = dict(item)
entry['label'] = '%s [%s]' % (
item.get('name') or item.get('servername') or 'Job item', item.get('model', '?'))
candidates.append(entry)
wanted = wanted_servername
if not wanted and wanted_model:
for item in candidates:
if int(item.get('model', 0) or 0) == int(wanted_model):
wanted = item.get('servername', '')
break
# QtBind combobox icin secim ayarlama API'si yoktur. Kayitli secimi
# listenin basina tasimak reload sonrasi ayni itemi secili tutar.
if wanted:
candidates.sort(key=lambda item: 0 if str(item.get('servername') or '') == str(wanted) else 1)
job_candidates = candidates
_fill_combobox(cmb_job_item, job_candidates)
return bool(job_candidates)
def _refresh_scripts(wanted=''):
global script_candidates
_ensure_directories()
try:
names = [name for name in os.listdir(_scripts_directory())
if os.path.isfile(os.path.join(_scripts_directory(), name)) and
not name.startswith('.')]
names.sort(key=lambda value: value.lower())
except Exception:
names = []
selected_name = ''
if wanted:
for name in names:
if name.lower() == str(wanted).lower():
selected_name = name
break
if selected_name:
names.remove(selected_name)
names.insert(0, selected_name)
script_candidates = names
_fill_combobox(cmb_script, script_candidates)
if selected_name:
QtBind.setText(gui, cmb_script, selected_name)
return bool(script_candidates)
def _load_profile():
global profile_name, settings_loading, current_language
settings_loading = True
profile_name = _character_name()
data = _read_settings_file().get('profiles', {}).get(profile_name, {})
current_language = str(data.get('language', 'en')).lower()
if current_language not in TRANSLATIONS:
current_language = 'en'
QtBind.setText(gui, txt_target, str(data.get('target', 80)))
QtBind.setText(gui, txt_safety, str(data.get('safety', 5)))
QtBind.setText(gui, txt_action_delay, str(data.get('action_delay_ms', 2000)))
QtBind.setText(gui, txt_sync_check_interval,
str(data.get('sync_check_interval', 10)))
QtBind.setChecked(gui, chk_grind_with_job, bool(data.get('grind_with_job', False)))
_refresh_jobs(data.get('job_servername', ''), data.get('job_model', 0))
_refresh_scripts(data.get('script', ''))
_refresh_profiles(data.get('farm_profile', ''), data.get('trade_profile', ''))
QtBind.setChecked(gui, chk_sync_enabled, bool(data.get('sync_enabled', False)))
QtBind.setChecked(gui, chk_sync_coordinator,
bool(data.get('sync_coordinator', False)))
QtBind.setChecked(gui, chk_error_recovery,
bool(data.get('error_recovery', False)))
QtBind.setText(gui, txt_sync_coordinator,
str(data.get('coordinator_name', '')))
QtBind.setText(gui, txt_sync_members,
str(data.get('required_members', '')))
QtBind.setChecked(gui, chk_enabled, bool(data.get('enabled', False)))
_apply_language()
settings_loading = False
_set_message('Profil yuklendi: %s' % profile_name, True)
def _job_identity():
selected = _selected_job()
if not selected:
return None
return (int(selected.get('model', 0) or 0), str(selected.get('servername') or ''))
def _same_job(item, identity):
if not item or not identity:
return False
model, servername = identity
if model and int(item.get('model', 0) or 0) == model:
return True
return bool(servername and str(item.get('servername') or '') == servername)
def _find_job(identity):
for item in _inventory_items():
if _same_job(item, identity):
return item
return None
def _empty_inventory_slot():
try:
items = get_inventory()['items']
for slot, item in enumerate(items):
if slot >= 13 and not item:
return slot
except Exception:
pass
return -1
def _move_item(source, destination):
packet = struct.pack(' SYNC_RESPONSE_TIMEOUT:
return False
return True
def _sync_reset(message='Sync idle.'):
global sync_run_id, sync_phase, sync_expected_members
global sync_last_check, sync_phase_since
sync_run_id = ''
sync_phase = 'IDLE'
sync_expected_members = set()
sync_ready_members.clear()
sync_ack_members.clear()
sync_member_aliases.clear()
sync_wait_details.clear()
sync_last_check = 0.0
sync_phase_since = 0.0
_set_sync_text(message)
def _sync_begin_coordinator(now):
global sync_run_id, sync_phase, sync_expected_members
global sync_last_check, sync_phase_since
valid, reason = _validate_sync_setup()
if not valid:
_set_sync_text(reason, '#e74c3c')
return
required = _required_member_names()
own = _own_name().lower()
sync_run_id = '%s-%d' % (own, int(now * 1000))
sync_expected_members = required.difference(set([own]))
sync_ready_members.clear()
sync_ack_members.clear()
sync_member_aliases.clear()
sync_wait_details.clear()
sync_phase = 'WAIT_READY'
sync_phase_since = now
sync_last_check = now
_send_sync('CHECK')
_set_sync_text('Waiting READY: 0/%d' % len(sync_expected_members), '#c98a1a')
def _sync_start_local():
global sync_phase, trade_profile_active
if cycle_active or sync_phase == 'STARTED':
return
ready, reason, count = _local_sync_readiness()
if not ready:
_set_sync_text('START rejected locally: %s' % reason, '#e74c3c')
_send_sync('FAILED', '%s:%s' % (_own_name(), reason))
return
trade_profile = _selected_phbot_profile(cmb_trade_profile)
if not _apply_phbot_profile(trade_profile, 'Trade'):
_send_sync('FAILED', '%s:TRADE_PROFILE' % _own_name())
_set_sync_text('Trade profile could not be activated', '#e74c3c')
return
trade_profile_active = True
sync_phase = 'STARTED'
_set_sync_text('START received; trade profile active (%d boxes)' % count, '#1f9d63')
if not _begin_cycle(False):
trade_profile_active = False
_apply_phbot_profile(_selected_phbot_profile(cmb_farm_profile), 'Farm')
_send_sync('FAILED', '%s:BEGIN_CYCLE' % _own_name())
def _poll_sync(now, target, training_ready):
global sync_phase, sync_last_check, sync_phase_since
if not QtBind.isChecked(gui, chk_sync_enabled) or cycle_active:
return
if not QtBind.isChecked(gui, chk_sync_coordinator):
if sync_phase == 'WAIT_START' and now - sync_phase_since > SYNC_RESPONSE_TIMEOUT * 2:
_sync_reset('Coordinator timeout; waiting for a new CHECK.')
return
if sync_phase == 'IDLE':
if training_ready and cycle_armed and last_box_count is not None and last_box_count >= target:
_sync_begin_coordinator(now)
return
if sync_phase == 'WAIT_READY':
if now - sync_last_check >= _sync_check_interval():
sync_last_check = now
_send_sync('CHECK')
if _sync_all_recent(now):
own_ready, own_reason, own_count = _local_sync_readiness()
if not own_ready:
_set_sync_text('Coordinator no longer ready: %s' % own_reason, '#c98a1a')
return
sync_phase = 'WAIT_ACK'
sync_phase_since = now
sync_ack_members.clear()
_send_sync('PREPARE')
_set_sync_text('All READY; waiting final ACK', '#c98a1a')
elif sync_phase == 'WAIT_ACK':
if sync_expected_members.issubset(sync_ack_members):
own_ready, own_reason, own_count = _local_sync_readiness()
party_names = _party_names()
party_missing = set(
member for member in sync_expected_members
if sync_member_aliases.get(member, '') not in party_names)
if not own_ready or party_missing:
reason = own_reason if not own_ready else (
'PARTY_MISSING_%s' % ','.join(sorted(party_missing)))
_send_sync('ABORT', reason)
_sync_reset('Sync aborted before START: %s' % reason)
return
_send_sync('START')
_sync_start_local()
elif now - sync_phase_since > SYNC_PREPARE_TIMEOUT:
_send_sync('ABORT', 'ACK_TIMEOUT')
_sync_reset('Sync aborted: ACK timeout')
def _begin_cycle(manual=False):
global cycle_active, cycle_armed
global trade_command_received, trade_settled_received
global trade_complete_time, pending_transport_death_time
if cycle_active:
_set_message('Zaten aktif bir kervan islemi var.', True)
return False
if not QtBind.isChecked(gui, chk_enabled):
_set_message('Kervan baslatmak icin once Plugin aktif secenegini acin.', True)
return False
if not manual and training_inside_streak < 3:
_set_message('Otomatik kervan icin karakter training area icinde olmali.', True)
return False
target = _number(txt_target, 1, 'Kervan hedefi')
safety = _number(txt_safety, 1, 'Guvenlik siniri')
if target is None or safety is None:
return False
if not _selected_job():
_set_message('Once kullanilacak job itemini secin.', True)
return False
script_name = _selected_script_name()
if not script_name:
_set_message('Once kervan scriptini secin.', True)
return False
script_path = os.path.join(_scripts_directory(), script_name)
if not os.path.isfile(script_path):
_set_message('Secilen kervan scripti bulunamadi.', True)
return False
count = _read_box_count(True)
if count is None:
return False
if not manual and count < target:
return False
_save_settings(True)
cycle_active = True
cycle_armed = False
trade_command_received = False
trade_settled_received = False
trade_complete_time = 0.0
pending_transport_death_time = 0.0
try:
stop_script()
except Exception:
pass
try:
stop_bot()
except Exception:
pass
_schedule_action(_use_initial_return_scroll,
'Bot ve walking durduruldu; return scroll hazirlaniyor.')
return True
def _use_initial_return_scroll():
_set_state(STATE_RETURNING, 'Return scroll kullaniliyor.')
try:
if not use_return_scroll():
_fail('Return scroll kullanilamadi veya bulunamadi.')
return False
except Exception as ex:
_fail('Return scroll hatasi: %s' % ex)
return False
return True
def _begin_party_leave_or_equip():
try:
party = get_party()
except Exception as ex:
_fail('Party bilgisi okunamadi: %s' % ex)
return
if party:
inject_joymax(0x7061, b'', False)
_set_state(STATE_LEAVING_PARTY, 'Sehre varildi; partyden cikiliyor.')
else:
_begin_equip()
def _begin_equip():
identity = _job_identity()
item = _find_job(identity)
if not item:
_fail('Secilen job itemi envanterde bulunamadi.')
return
if int(item.get('slot', -1)) == 8:
_schedule_action(_start_trade_script,
'Job itemi zaten giyili; kervan scripti hazirlaniyor.')
return
_move_item(item.get('slot'), 8)
_set_state(STATE_EQUIPPING, 'Job itemi giyiliyor: %s' % item.get('name', '?'))
def _start_trade_script():
global trade_command_received, trade_settled_received, trade_complete_time
name = _selected_script_name()
path = os.path.join(_scripts_directory(), name)
try:
script_text = _read_script_text(path)
except Exception as ex:
_fail('Kervan scripti okunamadi: %s' % ex)
return
if not script_text.strip():
_fail('Kervan scripti bos.')
return
if 'FSroRAutoTrade_complete' not in script_text:
_fail('Script sonunda FSroRAutoTrade_complete komutu bulunmuyor.')
return
if 'FSroRAutoTrade_settled' not in script_text:
_fail('Trade komutundan sonra FSroRAutoTrade_settled komutu bulunmuyor.')
return
trade_command_received = False
trade_settled_received = False
trade_complete_time = 0.0
try:
result = start_script(script_text)
except Exception as ex:
_fail('Kervan scripti baslatilamadi: %s' % ex)
return
if result is False:
_fail('phBot kervan scriptini baslatmayi reddetti.')
return
_set_state(STATE_RUNNING_TRADE, 'Kervan scripti calisiyor: %s' % name)
def _try_finish_trade():
if state != STATE_RUNNING_TRADE:
return
if not trade_command_received:
return
if not _is_in_town():
_set_message('Tamamlama komutu alindi; karakterin sehirde oldugu dogrulaniyor.')
if trade_complete_time and time.time() - trade_complete_time > CITY_CONFIRM_TIMEOUT:
_fail('Karakterin sehirde oldugu %d saniyede dogrulanamadi.' % CITY_CONFIRM_TIMEOUT)
return
_begin_pouch_settle()
def _begin_pouch_settle():
global pouch_settle_started, pouch_last_poll
global pouch_last_count, pouch_stable_reads
pouch_settle_started = time.time()
pouch_last_poll = 0.0
pouch_last_count = None
pouch_stable_reads = 0
_set_state(STATE_SETTLING_POUCH,
'Sehir dogrulandi; teslim sonrasi job pouch guncellenmesi bekleniyor.')
def _poll_pouch_settle(now):
global pouch_last_poll, pouch_last_count, pouch_stable_reads
if now - pouch_last_poll < POUCH_SETTLE_INTERVAL:
return
pouch_last_poll = now
count = _read_box_count(False)
safety = _number(txt_safety, 1, 'Guvenlik siniri')
if safety is None:
_fail('Kutu guvenlik siniri gecersiz.', False)
return
if count is not None:
if count == pouch_last_count:
pouch_stable_reads += 1
else:
pouch_last_count = count
pouch_stable_reads = 1
_set_message('Pouch sabitleniyor: %d kutu (%d/%d), gerekli: <%d' % (
count, pouch_stable_reads, POUCH_STABLE_READS, safety))
# Yalnizca guvenli dusuk deger kararlı hale gelirse devam et. Yuksek
# deger stale olabilir; timeout dolana kadar guncellenmesini bekle.
if count < safety and pouch_stable_reads >= POUCH_STABLE_READS:
_set_message('Pouch dogrulandi: %d kutu kaldi.' % count, True)
if QtBind.isChecked(gui, chk_grind_with_job):
_schedule_action(_start_grinding, 'Botun baslatilmasi hazirlaniyor.')
else:
_schedule_action(_begin_unequip,
'Job iteminin cikarilmasi hazirlaniyor.')
return
if now - pouch_settle_started >= POUCH_SETTLE_TIMEOUT:
if pouch_last_count is None:
_fail('Teslim sonrasi job pouch %d saniyede okunamadi.' %
POUCH_SETTLE_TIMEOUT, False)
else:
_fail('Teslim sonrasi %d kutu kaldi; guvenli kosul <%d saglanmadi.' % (
pouch_last_count, safety), False)
def _begin_unequip():
global last_action, unequip_request_sent, unequip_destination
identity = _job_identity()
item = _find_job(identity)
if not item or int(item.get('slot', -1)) != 8:
_start_grinding()
return
_set_state(STATE_UNEQUIPPING,
'Job itemi icin bos envanter slotu kontrol ediliyor.')
unequip_request_sent = False
unequip_destination = -1
destination = _empty_inventory_slot()
if destination < 0:
last_action = time.time()
_set_message('Bos envanter slotunun API\'de gorunmesi bekleniyor.')
return
_move_item(8, destination)
unequip_request_sent = True
unequip_destination = destination
last_action = time.time()
_set_message('Job itemi envanter slotu %d konumuna cikariliyor.' %
destination)
def _start_grinding():
global cycle_active, trade_profile_active
if trade_profile_active:
farm_profile = _selected_phbot_profile(cmb_farm_profile)
if not _apply_phbot_profile(farm_profile, 'Farm'):
_fail('Farm profiline donulemedi; bot guvenlik icin baslatilmadi.', False)
return
trade_profile_active = False
try:
result = start_bot()
except Exception as ex:
_fail('Bot baslatilamadi: %s' % ex, False)
return
if result is False:
_fail('phBot botu baslatmayi reddetti.', False)
return
cycle_active = False
_set_state(STATE_IDLE, 'Kervan tamamlandi; bot bir kez baslatildi.')
_sync_reset('Trade completed; farm profile active.')
def _send_respawn_request(now=None):
"""Olum ekranindaki sehirde yeniden dogma secenegini sunucuya gonderir."""
global respawn_attempts, last_respawn_request
if now is None:
now = time.time()
if respawn_attempts >= RESPAWN_MAX_ATTEMPTS:
return False
try:
inject_joymax(0x3053, b'\x01', False)
except Exception as ex:
_fail('Sehirde yeniden dogma istegi gonderilemedi: %s' % ex, False)
return False
respawn_attempts += 1
last_respawn_request = now
_set_message('Sehirde yeniden dogma istegi gonderildi (%d/%d).' % (
respawn_attempts, RESPAWN_MAX_ATTEMPTS), True)
return True
def FSroRAutoTrade_settled(arguments):
global trade_settled_received, pending_transport_death_time
if not cycle_active or state != STATE_RUNNING_TRADE:
log('[%s] Teslimat komutu aktif kervan disinda yok sayildi.' % pName)
return 0
trade_settled_received = True
if pending_transport_death_time > 0:
pending_transport_death_time = 0.0
_set_message('Trade teslim edildi; bekleyen pet termination eventi yok sayildi.', True)
else:
_set_message('Trade teslimat komutu alindi.', True)
return 0
def FSroRAutoTrade_complete(arguments):
global trade_command_received, trade_complete_time
if not cycle_active or state != STATE_RUNNING_TRADE:
log('[%s] Tamamlama komutu aktif kervan disinda yok sayildi.' % pName)
return 0
trade_command_received = True
trade_complete_time = time.time()
_set_message('Kervan tamamlama komutu alindi; sehir kontrol ediliyor.', True)
_try_finish_trade()
return 0
def teleported():
global death_teleport_received, last_teleport_time
last_teleport_time = time.time()
if not cycle_active:
return
if state == STATE_RETURNING:
_schedule_action(_begin_party_leave_or_equip,
'Sehre varildi; sonraki islem hazirlaniyor.')
elif state == STATE_DEATH_RECOVERY:
death_teleport_received = True
_set_message('Olum sonrasi teleport algilandi; sehir dogrulaniyor.', True)
def joined_game():
global profile_name
profile_name = 'default'
def handle_chat(t, player, msg):
global sync_run_id, sync_phase, sync_phase_since
if (t != CHAT_PARTY or not player or not msg or
not msg.startswith(SYNC_PROTOCOL + '|')):
return False
parts = msg.split('|', 4)
if len(parts) < 4:
return True
run_id = parts[1]
command = parts[2].upper()
identity = parts[3].strip().lower()
argument = parts[4] if len(parts) > 4 else ''
sender = str(player).lower()
coordinator = _coordinator_name().lower()
if not QtBind.isChecked(gui, chk_sync_enabled):
return True
if command == 'CHECK':
if (identity != coordinator or cycle_active or
QtBind.isChecked(gui, chk_sync_coordinator)):
return True
if run_id != sync_run_id:
sync_run_id = run_id
sync_phase = 'WAIT_START'
sync_phase_since = time.time()
ready, reason, count = _local_sync_readiness()
if ready:
_send_sync('READY', str(count))
_set_sync_text('READY %d; coordinator command pending' % count, '#1f9d63')
else:
_send_sync('WAIT', '%s:%d' % (reason, count))
_set_sync_text('WAIT: %s' % reason, '#c98a1a')
return True
if not sync_run_id or run_id != sync_run_id:
return True
if QtBind.isChecked(gui, chk_sync_coordinator):
if identity not in sync_expected_members:
return True
sync_member_aliases[identity] = sender
if command == 'READY' and sync_phase == 'WAIT_READY':
sync_ready_members[identity] = time.time()
sync_wait_details.pop(identity, None)
_set_sync_text('Waiting READY: %d/%d' % (
len(sync_ready_members), len(sync_expected_members)), '#c98a1a')
elif command == 'WAIT' and sync_phase == 'WAIT_READY':
sync_ready_members.pop(identity, None)
sync_wait_details[identity] = argument
_set_sync_text('%s waiting: %s' % (player, argument), '#c98a1a')
elif command == 'ACK' and sync_phase == 'WAIT_ACK':
sync_ack_members.add(identity)
_set_sync_text('Waiting ACK: %d/%d' % (
len(sync_ack_members), len(sync_expected_members)), '#c98a1a')
elif command == 'FAILED':
_send_sync('ABORT', argument or player)
_sync_reset('Sync failed: %s' % (argument or player))
return True
if identity != coordinator:
return True
if command == 'PREPARE' and sync_phase == 'WAIT_START':
ready, reason, count = _local_sync_readiness()
if ready:
_send_sync('ACK', str(count))
_set_sync_text('Final ACK sent; START pending', '#c98a1a')
else:
_send_sync('FAILED', '%s:%s' % (_own_name(), reason))
_set_sync_text('PREPARE rejected: %s' % reason, '#e74c3c')
elif command == 'START' and sync_phase == 'WAIT_START':
_sync_start_local()
elif command == 'ABORT':
_sync_reset('Sync aborted by coordinator: %s' % argument)
return True
def handle_event(event_type, data):
global pending_action, cycle_armed, death_teleport_received
global pending_transport_death_time, pending_transport_death_region
global respawn_attempts, last_respawn_request
if not cycle_active:
return
if event_type == EVENT_DIED:
pending_action = None
cycle_armed = True
death_teleport_received = False
respawn_attempts = 0
last_respawn_request = 0.0
pending_transport_death_time = 0.0
pending_transport_death_region = 0
_reset_transport_tracking()
try:
stop_script()
except Exception:
pass
try:
stop_bot()
except Exception:
pass
_set_state(STATE_DEATH_RECOVERY,
'Karakter oldu; sehirde yeniden dogma hazirlaniyor.')
try:
character = get_character_data()
except Exception:
character = None
if character and character.get('dead'):
_send_respawn_request()
elif event_type == EVENT_TRANSPORT_DIED:
pending_transport_death_time = time.time()
try:
position = get_position()
pending_transport_death_region = int(
position.get('region', 0) or 0) if position else 0
except Exception:
pending_transport_death_region = 0
_set_message('Transport eventi alindi (ID: %s); dogrulama bekleniyor.' % data,
True)
def disconnected():
global trade_profile_active
if cycle_active:
_fail('Kervan dongusu sirasinda baglanti kesildi; otomatik devam iptal edildi.',
False)
trade_profile_active = False
_sync_reset('Disconnected; sync reset.')
def finished():
"""Plugin reload/kapanisinda yarim kalan otomasyonu guvenli durdurur."""
if not cycle_active:
return
try:
stop_script()
except Exception:
pass
try:
stop_bot()
except Exception:
pass
log('[%s] Plugin kapatildi/reload edildi; aktif kervan islemi durduruldu.' % pName)
def btn_check_clicked():
count = _read_box_count(True)
if count is not None:
_set_message('Specialty Goods Box toplam: %d' % count, True)
def btn_status_tab_clicked():
global status_panel_open
status_panel_open = not status_panel_open
if status_panel_open:
_read_box_count(False)
for widget, x, y in main_panel_positions:
QtBind.move(gui, widget, STATUS_OFFSCREEN_X, y)
for widget, x, y in sync_panel_positions:
QtBind.move(gui, widget, x, y)
QtBind.setText(gui, btn_status_tab, _ui('back'))
_refresh_status_panel()
else:
for widget, x, y in sync_panel_positions:
QtBind.move(gui, widget, STATUS_OFFSCREEN_X, y)
for widget, x, y in main_panel_positions:
QtBind.move(gui, widget, x, y)
QtBind.setText(gui, btn_status_tab, _ui('status'))
def btn_language_clicked():
global current_language
current_language = 'tr' if current_language == 'en' else 'en'
_apply_language()
if not settings_loading:
_save_settings(True, False)
def discord_clicked():
try:
webbrowser.open(DISCORD_URL)
_set_message('Opening Discord invite...', True)
except Exception as error:
log('[%s] Discord link error: %s' % (pName, error))
_set_message('Could not open Discord invite', False)
def btn_refresh_profiles_clicked():
farm = _selected_phbot_profile(cmb_farm_profile)
trade = _selected_phbot_profile(cmb_trade_profile)
if _refresh_profiles(farm, trade):
_set_sync_text('%d phBot profiles found.' % len(profile_candidates), '#1f9d63')
else:
_set_sync_text('No phBot profiles found for this character.', '#e74c3c')
def btn_refresh_jobs_clicked():
current = _selected_job() or {}
if _refresh_jobs(current.get('servername', ''), current.get('model', 0)):
_set_message('%d job itemi bulundu.' % len(job_candidates), True)
else:
_set_message('OUTFIT/job itemi bulunamadi.', True)
def btn_refresh_scripts_clicked():
current = _selected_script_name()
if _refresh_scripts(current):
_set_message('%d script bulundu.' % len(script_candidates), True)
else:
_set_message('Script bulunamadi: %s' % _scripts_directory(), True)
def btn_save_clicked():
_save_settings(False)
def btn_manual_clicked():
_begin_cycle(True)
def btn_abort_clicked():
global cycle_active, cycle_armed, pending_action, pending_transport_death_time
global trade_profile_active
cycle_active = False
cycle_armed = False
pending_action = None
pending_transport_death_time = 0.0
try:
stop_script()
except Exception:
pass
try:
stop_bot()
except Exception:
pass
if trade_profile_active:
if _apply_phbot_profile(_selected_phbot_profile(cmb_farm_profile), 'Farm'):
trade_profile_active = False
else:
_sync_reset('Local operation aborted; Farm Profile restore failed.')
_set_state(STATE_ERROR,
'Local operation stopped, but Farm Profile could not be restored.')
return
_sync_reset('Local operation aborted; this client is not armed.')
_set_state(STATE_IDLE,
'Local operation aborted; use Re-arm This Client after fixing the issue.')
def btn_rearm_clicked():
global cycle_armed, pending_action, pending_transport_death_time
if cycle_active or state != STATE_IDLE:
_set_message('Cannot re-arm while a local operation is active or not IDLE.', True)
return
if trade_profile_active:
_set_message('Cannot re-arm until the Farm Profile is restored.', True)
return
pending_action = None
pending_transport_death_time = 0.0
_sync_reset('Local client re-armed; waiting for synchronization.')
cycle_armed = True
_set_state(STATE_IDLE,
'This client is re-armed and ready for normal synchronization checks.')
def chk_enabled_changed(checked):
if settings_loading:
return
if not checked and cycle_active:
btn_abort_clicked()
_save_settings(True)
_set_message('Plugin %s.' % ('aktif' if checked else 'pasif'), True)
def chk_grind_with_job_changed(checked):
if not settings_loading:
_save_settings(True)
def chk_sync_enabled_changed(checked):
if settings_loading:
return
if not checked:
if sync_run_id and QtBind.isChecked(gui, chk_sync_coordinator):
_send_sync('ABORT', 'SYNC_DISABLED')
_sync_reset('Party synchronization disabled.')
_save_settings(True)
def chk_sync_coordinator_changed(checked):
if settings_loading:
return
if checked:
QtBind.setText(gui, txt_sync_coordinator, _own_name())
_sync_reset('Coordinator setting changed.')
_save_settings(True)
def chk_error_recovery_changed(checked):
if not settings_loading:
_save_settings(True)
def _poll_state(now):
global last_action, pending_action
global respawn_attempts, last_respawn_request
global unequip_request_sent, unequip_destination
elapsed = now - state_since
identity = _job_identity()
if state == STATE_WAITING_ACTION:
if elapsed >= _action_delay_seconds():
action = pending_action
pending_action = None
if action:
action()
else:
_fail('Bekleyen komut bulunamadi.')
elif state == STATE_RETURNING:
if elapsed > RETURN_TIMEOUT:
_fail('Sehre donus zaman asimina ugradi.')
elif state == STATE_LEAVING_PARTY:
try:
party = get_party()
except Exception:
party = None
if not party:
_schedule_action(_begin_equip, 'Partyden cikildi; job itemi hazirlaniyor.')
elif elapsed > PARTY_TIMEOUT:
_fail('Partyden cikis zaman asimina ugradi.')
elif now - last_action >= max(ACTION_RETRY_SECONDS, _action_delay_seconds()):
last_action = now
inject_joymax(0x7061, b'', False)
elif state == STATE_EQUIPPING:
item = _find_job(identity)
if item and int(item.get('slot', -1)) == 8:
_schedule_action(_start_trade_script,
'Job itemi giyildi; kervan scripti hazirlaniyor.')
elif elapsed > EQUIP_TIMEOUT:
_fail('Job itemi giyme zaman asimina ugradi.')
elif state == STATE_RUNNING_TRADE:
if trade_command_received:
_try_finish_trade()
elif elapsed > TRADE_TIMEOUT:
_fail('Kervan scripti zaman asimina ugradi.')
elif state == STATE_SETTLING_POUCH:
_poll_pouch_settle(now)
elif state == STATE_ERROR_RECOVERY:
if elapsed > ERROR_RECOVERY_TIMEOUT:
_fail('Hata kurtarma: karakter %d saniyede canli ve sehirde dogrulanamadi.' %
ERROR_RECOVERY_TIMEOUT, False)
return
try:
character = get_character_data()
except Exception:
character = None
if not character or character.get('dead') or not _is_in_town():
return
_set_message('Kurtarilabilir hata: karakter sehirde ve canli dogrulandi.', True)
if QtBind.isChecked(gui, chk_grind_with_job):
_schedule_action(
_start_grinding,
'Hata kurtarma tamamlandi; farm profili ve bot hazirlaniyor.')
else:
_schedule_action(
_begin_unequip,
'Hata kurtarma tamamlandi; job itemi kontrol ediliyor.')
elif state == STATE_DEATH_RECOVERY:
if elapsed > DEATH_RECOVERY_TIMEOUT:
_fail('Olum sonrasi sehre donus %d saniyede tamamlanmadi.' %
DEATH_RECOVERY_TIMEOUT, False)
return
try:
character = get_character_data()
except Exception:
character = None
if not character:
return
if character.get('dead'):
if (respawn_attempts < RESPAWN_MAX_ATTEMPTS and
now - last_respawn_request >= RESPAWN_RETRY_SECONDS):
_send_respawn_request(now)
return
if (respawn_attempts >= RESPAWN_MAX_ATTEMPTS and
now - last_respawn_request >= RESPAWN_FINAL_WAIT_SECONDS):
_fail('Sehirde yeniden dogma basarisiz: %d deneme cevapsiz kaldi.' %
RESPAWN_MAX_ATTEMPTS, False)
return
if not death_teleport_received or not _is_in_town():
return
_set_message('Karakter sehirde ve canli; kasilma duzeni hazirlaniyor.', True)
if QtBind.isChecked(gui, chk_grind_with_job):
_schedule_action(_start_grinding,
'Olum sonrasi sehir dogrulandi; bot hazirlaniyor.')
else:
_schedule_action(_begin_unequip,
'Olum sonrasi sehir dogrulandi; job itemi kontrol ediliyor.')
elif state == STATE_UNEQUIPPING:
item = _find_job(identity)
if not item or int(item.get('slot', -1)) != 8:
_schedule_action(_start_grinding,
'Job itemi cikarildi; bot hazirlaniyor.')
elif elapsed > EQUIP_TIMEOUT:
if unequip_request_sent:
_fail('Job itemi %d saniyede cikarilamadi; slot %d icin gonderilen '
'tek tasima istegi tamamlanmadi.' %
(EQUIP_TIMEOUT, unequip_destination), False)
else:
_fail('Job itemi %d saniyede cikarilamadi; API bos envanter '
'slotu gostermedi.' % EQUIP_TIMEOUT, False)
elif (not unequip_request_sent and
now - last_action >= max(ACTION_RETRY_SECONDS,
_action_delay_seconds())):
last_action = now
destination = _empty_inventory_slot()
if destination < 0:
_set_message('Bos envanter slotunun API\'de gorunmesi bekleniyor.')
else:
_move_item(8, destination)
unequip_request_sent = True
unequip_destination = destination
_set_message('Job itemi envanter slotu %d konumuna cikariliyor.' %
destination)
def event_loop():
global last_poll, cycle_armed, profile_name, training_inside_streak
now = time.time()
if now - last_poll < POLL_SECONDS:
return
last_poll = now
current_profile = _character_name()
if current_profile != profile_name:
_load_profile()
_refresh_status_panel()
if cycle_active and state == STATE_RUNNING_TRADE:
_poll_transport_load(now)
_confirm_pending_transport_death(now)
if cycle_active:
_poll_state(now)
return
if not QtBind.isChecked(gui, chk_enabled):
training_inside_streak = 0
_set_training_text('Plugin pasif.')
return
training_ready = _update_training_status()
if state == STATE_ERROR:
if not training_ready:
return
_recover_error_at_training()
count = _read_box_count(False)
if count is None:
return
target = _number(txt_target, 1, 'Kervan hedefi')
if target is None:
return
if count < target:
cycle_armed = True
if QtBind.isChecked(gui, chk_sync_enabled):
_poll_sync(now, target, training_ready)
return
if training_ready and cycle_armed and count >= target:
_begin_cycle(False)
_ensure_directories()
_refresh_jobs()
_refresh_scripts()
_load_profile()
log('[%s] Loaded - ⚜ Made By FascinaTe' % pName)