- Sound on condition met on item.
plugin
iSRO-R
v2.0
Wheel of Fortune
by @CK3 · 2d ago · updated 6h ago · 24 views
WheelOfFortune - plugin for phBot (iSRO-R)
- Automatically uses "Wheel of Fortune" on an item and stops when YOURconditions (line count per attribute) are met.
- Example: STR >= 2 lines AND INT >= 2 lines (other lines do not matter).
- Logic is AND: it stops only when ALL the conditions you set (> 0) are met.
Changelog
▸ WheelOfFortune.py v2.0 Download
# -*- coding: utf-8 -*-
#
# WheelOfFortune - plugin for phBot (iSRO-R)
# -------------------------------------------
# Automatically uses "Wheel of Fortune" on an item and stops when YOUR
# conditions (line count per attribute) are met.
# Example: STR >= 2 lines AND INT >= 2 lines (other lines do not matter).
# Logic is AND by default: it stops only when ALL the conditions you set (> 0)
# are met. Tick "When one condition met" to stop as soon as ONE of them is met.
#
# It resolves each magic line's attribute from the phBot database
# (Data/*.db3, table magicoption). Separate from AutoWheels and WheelOfFate.
#
# NOTE: every spin consumes one Wheel of Fortune.
#
from phBot import *
from threading import Timer, Thread
import struct
import QtBind
import os
import json
import sqlite3
try:
import winsound
except Exception:
winsound = None
pName = 'WheelOfFortune'
pVersion = '2.0'
WHEEL_OPCODE = 0x7151
WHEEL_RESULT_OPCODE = 0xB151
MAGICLINES_OFFSET = 26 # number_of_magiclines in the 0xB151 response
WHEEL_ITEM_NAME = 'Wheel of Fortune'
INSTALL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.path.join(INSTALL_DIR, 'Data')
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), pName + '.json')
started = False
_timer = None
current_items = {}
attempts = 0
queue = [] # item slots in the queue (multi-item)
qi = 0 # index of the current item in the queue
_db = None
history = [] # result lines, newest first
# ============================================================================
# GUI
# ============================================================================
gui = QtBind.init(__name__, pName)
# ---- Title ----
QtBind.createLabel(gui, 'W H E E L O F F O R T U N E v%s' % pVersion, 10, 6)
# ---- Item selection ----
QtBind.createLabel(gui, 'Item to spin:', 10, 32)
combo_items = QtBind.createCombobox(gui, 10, 52, 180, 22)
QtBind.createButton(gui, 'on_refresh', 'Refresh', 196, 51)
QtBind.createButton(gui, 'on_add_queue', '+Queue', 300, 51)
QtBind.createButton(gui, 'on_clear_queue', 'Clear', 404, 51)
lblMode = QtBind.createLabel(gui, 'Stop when lines >= (0 = ignore). ALL set conditions must be met:', 10, 82)
# column 1
QtBind.createLabel(gui, 'STR', 10, 106)
txtSTR = QtBind.createLineEdit(gui, '0', 70, 104, 35, 20)
QtBind.createLabel(gui, 'INT', 10, 130)
txtINT = QtBind.createLineEdit(gui, '0', 70, 128, 35, 20)
QtBind.createLabel(gui, 'Critical', 10, 154)
txtCRIT = QtBind.createLineEdit(gui, '0', 70, 152, 35, 20)
QtBind.createLabel(gui, 'Attack R.', 10, 178)
txtHR = QtBind.createLineEdit(gui, '0', 70, 176, 35, 20)
# column 2
QtBind.createLabel(gui, 'HP', 130, 106)
txtHP = QtBind.createLineEdit(gui, '0', 195, 104, 35, 20)
QtBind.createLabel(gui, 'MP', 130, 130)
txtMP = QtBind.createLineEdit(gui, '0', 195, 128, 35, 20)
QtBind.createLabel(gui, 'Block R.', 130, 154)
txtBLOCK = QtBind.createLineEdit(gui, '0', 195, 152, 35, 20)
QtBind.createLabel(gui, 'Parry R.', 130, 178)
txtER = QtBind.createLineEdit(gui, '0', 195, 176, 35, 20)
QtBind.createLabel(gui, 'Delay (ms):', 250, 106)
txtDelay = QtBind.createLineEdit(gui, '2000', 320, 104, 55, 20)
QtBind.createButton(gui, 'on_save', ' Save ', 250, 130)
chkAny = QtBind.createCheckBox(gui, 'on_mode_changed', 'When one condition met', 250, 152)
btnStartStop = QtBind.createButton(gui, 'on_startstop', ' Start ', 250, 172)
lblStatus = QtBind.createLabel(gui, 'stopped', 250, 204)
# ---- Sound alert ----
chkSound = QtBind.createCheckBox(gui, 'on_dummy', 'Sound on finish (WAV below, blank = beep)', 410, 6)
txtWav = QtBind.createLineEdit(gui, '', 410, 28, 270, 20)
QtBind.createButton(gui, 'on_browse_wav', ' Browse ', 490, 51)
QtBind.createButton(gui, 'on_test_sound', ' Test ', 595, 51)
QtBind.createLabel(gui, 'History:', 10, 204)
lstResult = QtBind.createList(gui, 10, 222, 380, 40)
# right panel: current blue lines (updated every wheel)
BLUE_ORDER = [('STR', 'STR'), ('INT', 'INT'), ('HP', 'HP'), ('MP', 'MP'),
('CRIT', 'Crit'), ('BLOCK', 'Block'), ('HR', 'AtkR'), ('ER', 'ParR')]
QtBind.createLabel(gui, 'Blue:', 410, 84)
lstBlue = QtBind.createList(gui, 410, 104, 85, 158)
QtBind.createLabel(gui, 'Queue (remaining):', 505, 84)
lstQueue = QtBind.createList(gui, 505, 104, 180, 158)
# ---- Footer ----
QtBind.createLabel(gui, 'Author : CK3 @ iLog1K.net', 10, 272)
# condition key -> GUI field mapping
COND_FIELDS = [
('STR', 'txtSTR'), ('INT', 'txtINT'), ('CRIT', 'txtCRIT'), ('HR', 'txtHR'),
('HP', 'txtHP'), ('MP', 'txtMP'), ('BLOCK', 'txtBLOCK'), ('ER', 'txtER'),
]
# ============================================================================
# Helpers
# ============================================================================
def set_status(txt):
QtBind.setText(gui, lblStatus, txt)
def add_history(text):
"""Newest result on the first row; older ones below (scroll to see them)."""
history.insert(0, text)
try:
QtBind.clear(gui, lstResult)
for line in history:
QtBind.append(gui, lstResult, line)
except Exception:
pass
def clear_history():
history[:] = []
try:
QtBind.clear(gui, lstResult)
except Exception:
pass
def update_blues(values):
try:
QtBind.clear(gui, lstBlue)
for _k, _d in BLUE_ORDER:
QtBind.append(gui, lstBlue, '%s = %d' % (_d, values.get(_k, 0)))
except Exception:
pass
def delay_sec():
try:
return max(0.0, int(QtBind.text(gui, txtDelay).strip()) / 1000.0)
except Exception:
return 2.0
def on_dummy(*_):
pass
def play_alert():
"""Beep / play a WAV when an item is finished. Never blocks the bot."""
try:
if not QtBind.isChecked(gui, chkSound):
return
path = QtBind.text(gui, txtWav).strip().strip('"')
except Exception:
return
if winsound is None:
return
def _run():
try:
if path and os.path.isfile(path):
winsound.PlaySound(path, winsound.SND_FILENAME | winsound.SND_ASYNC)
else:
for freq, dur in ((880, 150), (1175, 150), (1568, 350)):
winsound.Beep(freq, dur)
except Exception as e:
log('[%s] Sound error: %s' % (pName, e))
Thread(target=_run, daemon=True).start()
def on_test_sound(*_):
try:
if not QtBind.isChecked(gui, chkSound):
log('[%s] Tick "Sound on finish" first.' % pName)
return
except Exception:
pass
play_alert()
def _ask_wav_file():
"""Native Windows "Open file" dialog - no extra dependencies."""
try:
import ctypes
from ctypes import wintypes
except Exception:
return None
class OPENFILENAMEW(ctypes.Structure):
_fields_ = [
('lStructSize', wintypes.DWORD),
('hwndOwner', wintypes.HWND),
('hInstance', wintypes.HINSTANCE),
('lpstrFilter', wintypes.LPCWSTR),
('lpstrCustomFilter', wintypes.LPWSTR),
('nMaxCustFilter', wintypes.DWORD),
('nFilterIndex', wintypes.DWORD),
('lpstrFile', wintypes.LPWSTR),
('nMaxFile', wintypes.DWORD),
('lpstrFileTitle', wintypes.LPWSTR),
('nMaxFileTitle', wintypes.DWORD),
('lpstrInitialDir', wintypes.LPCWSTR),
('lpstrTitle', wintypes.LPCWSTR),
('Flags', wintypes.DWORD),
('nFileOffset', wintypes.WORD),
('nFileExtension', wintypes.WORD),
('lpstrDefExt', wintypes.LPCWSTR),
('lCustData', wintypes.LPARAM),
('lpfnHook', ctypes.c_void_p),
('lpTemplateName', wintypes.LPCWSTR),
('pvReserved', ctypes.c_void_p),
('dwReserved', wintypes.DWORD),
('FlagsEx', wintypes.DWORD),
]
cur = ''
try:
cur = QtBind.text(gui, txtWav).strip().strip('"')
except Exception:
pass
buf = ctypes.create_unicode_buffer(1024)
if cur:
buf.value = cur
ofn = OPENFILENAMEW()
ofn.lStructSize = ctypes.sizeof(OPENFILENAMEW)
ofn.lpstrFilter = 'WAV files\0*.wav\0All files\0*.*\0\0'
ofn.lpstrFile = ctypes.cast(buf, wintypes.LPWSTR)
ofn.nMaxFile = 1024
ofn.lpstrTitle = 'Choose the alert sound (.wav)'
ofn.lpstrInitialDir = (os.path.dirname(cur) if cur else
os.path.join(os.environ.get('WINDIR', 'C:\\Windows'), 'Media'))
# NOCHANGEDIR | PATHMUSTEXIST | FILEMUSTEXIST | EXPLORER
ofn.Flags = 0x00000008 | 0x00000800 | 0x00001000 | 0x00080000
try:
ok = ctypes.windll.comdlg32.GetOpenFileNameW(ctypes.byref(ofn))
except Exception as e:
log('[%s] File dialog error: %s' % (pName, e))
return None
return buf.value if ok else None
def on_browse_wav(*_):
path = _ask_wav_file()
if not path:
return
QtBind.setText(gui, txtWav, path)
log('[%s] Sound file: %s' % (pName, path))
on_save()
def any_mode():
"""True = stop as soon as ONE condition is met (OR). False = ALL (AND)."""
try:
return bool(QtBind.isChecked(gui, chkAny))
except Exception:
return False
def on_mode_changed(*_):
try:
if any_mode():
QtBind.setText(gui, lblMode, 'Stop when lines >= (0 = ignore). ANY set condition is enough:')
else:
QtBind.setText(gui, lblMode, 'Stop when lines >= (0 = ignore). ALL set conditions must be met:')
except Exception:
pass
def read_conditions():
out = {}
for key, fld in COND_FIELDS:
try:
out[key] = max(0, int(QtBind.text(gui, globals()[fld]).strip()))
except Exception:
out[key] = 0
return out
def attr_key(name):
"""magicoption servername -> tracked attribute key (or None)."""
if not name:
return None
n = name.upper()
if 'MATTR_STR' in n:
return 'STR'
if 'MATTR_INT' in n:
return 'INT'
if 'MATTR_REGENHPMP' in n:
return None
if 'MATTR_HP' in n:
return 'HP'
if 'MATTR_MP' in n:
return 'MP'
if 'EVADE_CRITICAL' in n:
return None
if 'MATTR_CRITICAL' in n:
return 'CRIT'
if 'MATTR_BLOCKRATE' in n:
return 'BLOCK'
if 'MATTR_HR' in n:
return 'HR'
if 'MATTR_ER' in n:
return 'ER'
return None
# ============================================================================
# Database (resolve magic line id -> servername)
# ============================================================================
def _db3_files():
try:
return [os.path.join(DATA_DIR, fn) for fn in os.listdir(DATA_DIR)
if fn.lower().endswith('.db3')]
except Exception:
return []
def _lookup(con, mid):
try:
r = con.cursor().execute('SELECT name FROM magicoption WHERE id=?', (mid,)).fetchone()
return r[0] if r else None
except Exception:
return None
def magic_name(mid):
"""Pick AUTOMATICALLY the db3 that resolves the current server's ids."""
global _db
if _db is not None:
n = _lookup(_db, mid)
if n:
return n
for f in _db3_files():
try:
con = sqlite3.connect(f, check_same_thread=False)
except Exception:
continue
n = _lookup(con, mid)
if n:
if _db is not None:
try:
_db.close()
except Exception:
pass
_db = con
log('[%s] Matched DB: %s' % (pName, os.path.basename(f)))
return n
try:
con.close()
except Exception:
pass
return None
# ============================================================================
# Config
# ============================================================================
def on_save(*_):
try:
data = {
'delay': int(delay_sec() * 1000),
'any_mode': any_mode(),
'sound': bool(QtBind.isChecked(gui, chkSound)),
'wav': QtBind.text(gui, txtWav),
}
for key, fld in COND_FIELDS:
data[key] = QtBind.text(gui, globals()[fld])
with open(CONFIG_FILE, 'w') as f:
json.dump(data, f, indent=2)
log('[%s] Saved.' % pName)
except Exception as e:
log('[%s] Save error: %s' % (pName, e))
def load_config():
try:
QtBind.setChecked(gui, chkSound, True) # default: alert on
except Exception:
pass
try:
if os.path.isfile(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
data = json.load(f)
QtBind.setText(gui, txtDelay, str(data.get('delay', 2000)))
for key, fld in COND_FIELDS:
QtBind.setText(gui, globals()[fld], str(data.get(key, '0')))
try:
QtBind.setChecked(gui, chkAny, bool(data.get('any_mode', False)))
QtBind.setChecked(gui, chkSound, bool(data.get('sound', True)))
QtBind.setText(gui, txtWav, str(data.get('wav', '')))
except Exception:
pass
except Exception as e:
log('[%s] Config read error: %s' % (pName, e))
on_mode_changed()
# ============================================================================
# Items
# ============================================================================
def on_refresh():
current_items.clear()
QtBind.clear(gui, combo_items)
inv = get_inventory()
if not inv or 'items' not in inv:
log('[%s] You are not in game.' % pName)
return
idx = 0
for slot, item in enumerate(inv['items']):
if not item or slot <= 13:
continue
try:
ref = get_item(item['model'])
except Exception:
ref = None
if ref and ref.get('tid1') == 1:
current_items[idx] = slot
idx += 1
QtBind.append(gui, combo_items, '%s +%s' % (item['name'], item.get('plus', 0)))
log('[%s] %d equippable items.' % (pName, idx))
def selected_item_slot():
if not current_items:
return None
return current_items.get(QtBind.currentIndex(gui, combo_items))
def current_target_slot():
if queue:
return queue[0][0]
return selected_item_slot()
def on_add_queue():
slot = selected_item_slot()
if slot is None:
log('[%s] Select an item (Refresh) before adding it to the queue.' % pName)
return
inv = get_inventory()
it = inv['items'][slot] if (inv and 'items' in inv and slot < len(inv['items'])) else None
label = ('%s +%s' % (it.get('name', '?'), it.get('plus', 0))) if isinstance(it, dict) else ('slot %d' % slot)
queue.append((slot, label))
QtBind.append(gui, lstQueue, label)
log('[%s] Added to queue: %s. Total: %d.' % (pName, label, len(queue)))
def on_clear_queue():
queue[:] = []
try:
QtBind.clear(gui, lstQueue)
except Exception:
pass
log('[%s] Queue cleared.' % pName)
def get_wheel_slot():
inv = get_inventory()
if not inv or 'items' not in inv:
return -1
for slot, item in enumerate(inv['items']):
if item and WHEEL_ITEM_NAME in (item.get('name') or ''):
return slot
return -1
# ============================================================================
# Start / Stop
# ============================================================================
def on_startstop():
global started, qi
if not started:
if not queue and selected_item_slot() is None:
log('[%s] Select an item (Refresh) or add one to the queue.' % pName)
return
if all(v == 0 for v in read_conditions().values()):
log('[%s] Set at least one condition (> 0).' % pName)
return
started = True
qi = 0
QtBind.setText(gui, btnStartStop, ' Stop ')
clear_history()
set_status('running')
use_wheel()
else:
stop()
def stop():
global started, _timer
started = False
QtBind.setText(gui, btnStartStop, ' Start ')
if _timer:
try:
_timer.cancel()
except Exception:
pass
_timer = None
set_status('stopped')
# ============================================================================
# Use wheel
# ============================================================================
def use_wheel():
global attempts
if not started:
return
item_slot = current_target_slot()
if item_slot is None:
stop()
return
wheel_slot = get_wheel_slot()
if wheel_slot == -1:
log('[%s] No %s left in inventory. Stopping.' % (pName, WHEEL_ITEM_NAME))
stop()
return
p = b'\x02\x19\x02' + struct.pack('B', item_slot) + struct.pack('B', wheel_slot)
inject_joymax(WHEEL_OPCODE, p, False)
attempts += 1
log('[%s] %s #%d (item slot %d).' % (pName, WHEEL_ITEM_NAME, attempts, item_slot))
# ============================================================================
# Response: count lines per attribute and check the conditions
# ============================================================================
def eval_conditions(counts):
"""Check the conditions set (> 0). Returns (met, progress_text).
AND mode (checkbox off): met only when EVERY set condition has enough
lines (e.g. STR 2 AND INT 2 -> both must reach the target).
OR mode (checkbox 'When one condition met' on): met as soon as ONE set
condition reaches its target."""
conds = read_conditions()
parts = []
any_set = False
all_met = True
one_met = False
for key, _fld in COND_FIELDS:
needed = conds.get(key, 0)
if needed > 0:
any_set = True
have = counts.get(key, 0)
if have < needed:
all_met = False
else:
one_met = True
parts.append('%s %d/%d' % (key, have, needed))
met = one_met if any_mode() else all_met
progress = (' '.join(parts) if parts else '(no conditions set)')
if any_set and parts:
progress += ' [%s]' % ('ANY' if any_mode() else 'ALL')
return (any_set and met), progress
def handle_joymax(opcode, data):
global _timer, qi
if opcode == WHEEL_RESULT_OPCODE and started:
try:
is_success = data[2]
if is_success == 1 and len(data) > MAGICLINES_OFFSET:
n_lines = data[MAGICLINES_OFFSET]
counts = {}
values = {}
idx = MAGICLINES_OFFSET + 1
for x in range(n_lines):
if idx + 8 > len(data):
break
mid = struct.unpack_from('<I', data, idx)[0]
amount = struct.unpack_from('<I', data, idx + 4)[0]
idx += 8 # id (4) + amount (4)
if x == 0:
continue # first line is the base one
sn = magic_name(mid)
key = attr_key(sn)
if key:
counts[key] = counts.get(key, 0) + 1
values[key] = values.get(key, 0) + amount
summary = ' '.join('%s:%d (%dp)' % (k, counts[k], values.get(k, 0)) for k in counts) or '(no tracked attributes)'
met, progress = eval_conditions(counts)
add_history('Attempt %d: %s' % (attempts, summary))
set_status('attempt %d | %s' % (attempts, progress))
log('[%s] Result #%d (raw lines=%d): %s -> %s' % (pName, attempts, n_lines, summary, progress))
update_blues(values)
if met:
log('[%s] Item done: %s (%s).' % (pName, progress,
'one condition met' if any_mode() else 'all conditions met'))
play_alert()
if queue:
done = queue.pop(0)
try:
QtBind.remove(gui, lstQueue, done[1])
except Exception:
pass
log('[%s] Removed from queue: %s. Remaining: %d.' % (pName, done[1], len(queue)))
if queue:
if started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
else:
log('[%s] DONE! Queue finished.' % pName)
stop()
elif started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
else:
if started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
except Exception as e:
log('[%s] Parse error: %s' % (pName, e))
return True
# ============================================================================
# Init
# ============================================================================
load_config()
log('[%s] v%s loaded OK.' % (pName, pVersion))
▸ WheelOfFortune.py v1.9 Download
# -*- coding: utf-8 -*-
#
# WheelOfFortune - plugin for phBot (iSRO-R)
# -------------------------------------------
# Automatically uses "Wheel of Fortune" on an item and stops when YOUR
# conditions (line count per attribute) are met.
# Example: STR >= 2 lines AND INT >= 2 lines (other lines do not matter).
# Logic is AND by default: it stops only when ALL the conditions you set (> 0)
# are met. Tick "When one condition met" to stop as soon as ONE of them is met.
#
# It resolves each magic line's attribute from the phBot database
# (Data/*.db3, table magicoption). Separate from WheelOfFate.
#
# NOTE: every spin consumes one Wheel of Fortune.
#
from phBot import *
from threading import Timer
import struct
import QtBind
import os
import json
import sqlite3
pName = 'WheelOfFortune'
pVersion = '1.9'
WHEEL_OPCODE = 0x7151
WHEEL_RESULT_OPCODE = 0xB151
MAGICLINES_OFFSET = 26 # number_of_magiclines in the 0xB151 response
WHEEL_ITEM_NAME = 'Wheel of Fortune'
INSTALL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.path.join(INSTALL_DIR, 'Data')
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), pName + '.json')
started = False
_timer = None
current_items = {}
attempts = 0
queue = [] # item slots in the queue (multi-item)
qi = 0 # index of the current item in the queue
_db = None
history = [] # result lines, newest first
# ============================================================================
# GUI
# ============================================================================
gui = QtBind.init(__name__, pName)
# ---- Title ----
QtBind.createLabel(gui, 'W H E E L O F F O R T U N E v%s' % pVersion, 10, 6)
# ---- Item selection ----
QtBind.createLabel(gui, 'Item to spin:', 10, 32)
combo_items = QtBind.createCombobox(gui, 10, 52, 180, 22)
QtBind.createButton(gui, 'on_refresh', 'Refresh', 196, 51)
QtBind.createButton(gui, 'on_add_queue', '+Queue', 300, 51)
QtBind.createButton(gui, 'on_clear_queue', 'Clear', 404, 51)
lblMode = QtBind.createLabel(gui, 'Stop when lines >= (0 = ignore). ALL set conditions must be met:', 10, 82)
# column 1
QtBind.createLabel(gui, 'STR', 10, 106)
txtSTR = QtBind.createLineEdit(gui, '0', 70, 104, 35, 20)
QtBind.createLabel(gui, 'INT', 10, 130)
txtINT = QtBind.createLineEdit(gui, '0', 70, 128, 35, 20)
QtBind.createLabel(gui, 'Critical', 10, 154)
txtCRIT = QtBind.createLineEdit(gui, '0', 70, 152, 35, 20)
QtBind.createLabel(gui, 'Attack R.', 10, 178)
txtHR = QtBind.createLineEdit(gui, '0', 70, 176, 35, 20)
# column 2
QtBind.createLabel(gui, 'HP', 130, 106)
txtHP = QtBind.createLineEdit(gui, '0', 195, 104, 35, 20)
QtBind.createLabel(gui, 'MP', 130, 130)
txtMP = QtBind.createLineEdit(gui, '0', 195, 128, 35, 20)
QtBind.createLabel(gui, 'Block R.', 130, 154)
txtBLOCK = QtBind.createLineEdit(gui, '0', 195, 152, 35, 20)
QtBind.createLabel(gui, 'Parry R.', 130, 178)
txtER = QtBind.createLineEdit(gui, '0', 195, 176, 35, 20)
QtBind.createLabel(gui, 'Delay (ms):', 250, 106)
txtDelay = QtBind.createLineEdit(gui, '2000', 320, 104, 55, 20)
QtBind.createButton(gui, 'on_save', ' Save ', 250, 130)
chkAny = QtBind.createCheckBox(gui, 'on_mode_changed', 'When one condition met', 250, 152)
btnStartStop = QtBind.createButton(gui, 'on_startstop', ' Start ', 250, 172)
lblStatus = QtBind.createLabel(gui, 'stopped', 250, 204)
QtBind.createLabel(gui, 'History:', 10, 204)
lstResult = QtBind.createList(gui, 10, 222, 380, 40)
# right panel: current blue lines (updated every wheel)
BLUE_ORDER = [('STR', 'STR'), ('INT', 'INT'), ('HP', 'HP'), ('MP', 'MP'),
('CRIT', 'Crit'), ('BLOCK', 'Block'), ('HR', 'AtkR'), ('ER', 'ParR')]
QtBind.createLabel(gui, 'Blue:', 410, 84)
lstBlue = QtBind.createList(gui, 410, 104, 85, 158)
QtBind.createLabel(gui, 'Queue (remaining):', 505, 84)
lstQueue = QtBind.createList(gui, 505, 104, 180, 158)
# ---- Footer ----
QtBind.createLabel(gui, 'Author : CK3 @ iLog1K.net', 10, 272)
# condition key -> GUI field mapping
COND_FIELDS = [
('STR', 'txtSTR'), ('INT', 'txtINT'), ('CRIT', 'txtCRIT'), ('HR', 'txtHR'),
('HP', 'txtHP'), ('MP', 'txtMP'), ('BLOCK', 'txtBLOCK'), ('ER', 'txtER'),
]
# ============================================================================
# Helpers
# ============================================================================
def set_status(txt):
QtBind.setText(gui, lblStatus, txt)
def add_history(text):
"""Newest result on the first row; older ones below (scroll to see them)."""
history.insert(0, text)
try:
QtBind.clear(gui, lstResult)
for line in history:
QtBind.append(gui, lstResult, line)
except Exception:
pass
def clear_history():
history[:] = []
try:
QtBind.clear(gui, lstResult)
except Exception:
pass
def update_blues(values):
try:
QtBind.clear(gui, lstBlue)
for _k, _d in BLUE_ORDER:
QtBind.append(gui, lstBlue, '%s = %d' % (_d, values.get(_k, 0)))
except Exception:
pass
def delay_sec():
try:
return max(0.0, int(QtBind.text(gui, txtDelay).strip()) / 1000.0)
except Exception:
return 2.0
def any_mode():
"""True = stop as soon as ONE condition is met (OR). False = ALL (AND)."""
try:
return bool(QtBind.isChecked(gui, chkAny))
except Exception:
return False
def on_mode_changed(*_):
try:
if any_mode():
QtBind.setText(gui, lblMode, 'Stop when lines >= (0 = ignore). ANY set condition is enough:')
else:
QtBind.setText(gui, lblMode, 'Stop when lines >= (0 = ignore). ALL set conditions must be met:')
except Exception:
pass
def read_conditions():
out = {}
for key, fld in COND_FIELDS:
try:
out[key] = max(0, int(QtBind.text(gui, globals()[fld]).strip()))
except Exception:
out[key] = 0
return out
def attr_key(name):
"""magicoption servername -> tracked attribute key (or None)."""
if not name:
return None
n = name.upper()
if 'MATTR_STR' in n:
return 'STR'
if 'MATTR_INT' in n:
return 'INT'
if 'MATTR_REGENHPMP' in n:
return None
if 'MATTR_HP' in n:
return 'HP'
if 'MATTR_MP' in n:
return 'MP'
if 'EVADE_CRITICAL' in n:
return None
if 'MATTR_CRITICAL' in n:
return 'CRIT'
if 'MATTR_BLOCKRATE' in n:
return 'BLOCK'
if 'MATTR_HR' in n:
return 'HR'
if 'MATTR_ER' in n:
return 'ER'
return None
# ============================================================================
# Database (resolve magic line id -> servername)
# ============================================================================
def _db3_files():
try:
return [os.path.join(DATA_DIR, fn) for fn in os.listdir(DATA_DIR)
if fn.lower().endswith('.db3')]
except Exception:
return []
def _lookup(con, mid):
try:
r = con.cursor().execute('SELECT name FROM magicoption WHERE id=?', (mid,)).fetchone()
return r[0] if r else None
except Exception:
return None
def magic_name(mid):
"""Pick AUTOMATICALLY the db3 that resolves the current server's ids."""
global _db
if _db is not None:
n = _lookup(_db, mid)
if n:
return n
for f in _db3_files():
try:
con = sqlite3.connect(f, check_same_thread=False)
except Exception:
continue
n = _lookup(con, mid)
if n:
if _db is not None:
try:
_db.close()
except Exception:
pass
_db = con
log('[%s] Matched DB: %s' % (pName, os.path.basename(f)))
return n
try:
con.close()
except Exception:
pass
return None
# ============================================================================
# Config
# ============================================================================
def on_save(*_):
try:
data = {'delay': int(delay_sec() * 1000), 'any_mode': any_mode()}
for key, fld in COND_FIELDS:
data[key] = QtBind.text(gui, globals()[fld])
with open(CONFIG_FILE, 'w') as f:
json.dump(data, f, indent=2)
log('[%s] Saved.' % pName)
except Exception as e:
log('[%s] Save error: %s' % (pName, e))
def load_config():
try:
if os.path.isfile(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
data = json.load(f)
QtBind.setText(gui, txtDelay, str(data.get('delay', 2000)))
for key, fld in COND_FIELDS:
QtBind.setText(gui, globals()[fld], str(data.get(key, '0')))
try:
QtBind.setChecked(gui, chkAny, bool(data.get('any_mode', False)))
except Exception:
pass
except Exception as e:
log('[%s] Config read error: %s' % (pName, e))
on_mode_changed()
# ============================================================================
# Items
# ============================================================================
def on_refresh():
current_items.clear()
QtBind.clear(gui, combo_items)
inv = get_inventory()
if not inv or 'items' not in inv:
log('[%s] You are not in game.' % pName)
return
idx = 0
for slot, item in enumerate(inv['items']):
if not item or slot <= 13:
continue
try:
ref = get_item(item['model'])
except Exception:
ref = None
if ref and ref.get('tid1') == 1:
current_items[idx] = slot
idx += 1
QtBind.append(gui, combo_items, '%s +%s' % (item['name'], item.get('plus', 0)))
log('[%s] %d equippable items.' % (pName, idx))
def selected_item_slot():
if not current_items:
return None
return current_items.get(QtBind.currentIndex(gui, combo_items))
def current_target_slot():
if queue:
return queue[0][0]
return selected_item_slot()
def on_add_queue():
slot = selected_item_slot()
if slot is None:
log('[%s] Select an item (Refresh) before adding it to the queue.' % pName)
return
inv = get_inventory()
it = inv['items'][slot] if (inv and 'items' in inv and slot < len(inv['items'])) else None
label = ('%s +%s' % (it.get('name', '?'), it.get('plus', 0))) if isinstance(it, dict) else ('slot %d' % slot)
queue.append((slot, label))
QtBind.append(gui, lstQueue, label)
log('[%s] Added to queue: %s. Total: %d.' % (pName, label, len(queue)))
def on_clear_queue():
queue[:] = []
try:
QtBind.clear(gui, lstQueue)
except Exception:
pass
log('[%s] Queue cleared.' % pName)
def get_wheel_slot():
inv = get_inventory()
if not inv or 'items' not in inv:
return -1
for slot, item in enumerate(inv['items']):
if item and WHEEL_ITEM_NAME in (item.get('name') or ''):
return slot
return -1
# ============================================================================
# Start / Stop
# ============================================================================
def on_startstop():
global started, qi
if not started:
if not queue and selected_item_slot() is None:
log('[%s] Select an item (Refresh) or add one to the queue.' % pName)
return
if all(v == 0 for v in read_conditions().values()):
log('[%s] Set at least one condition (> 0).' % pName)
return
started = True
qi = 0
QtBind.setText(gui, btnStartStop, ' Stop ')
clear_history()
set_status('running')
use_wheel()
else:
stop()
def stop():
global started, _timer
started = False
QtBind.setText(gui, btnStartStop, ' Start ')
if _timer:
try:
_timer.cancel()
except Exception:
pass
_timer = None
set_status('stopped')
# ============================================================================
# Use wheel
# ============================================================================
def use_wheel():
global attempts
if not started:
return
item_slot = current_target_slot()
if item_slot is None:
stop()
return
wheel_slot = get_wheel_slot()
if wheel_slot == -1:
log('[%s] No %s left in inventory. Stopping.' % (pName, WHEEL_ITEM_NAME))
stop()
return
p = b'\x02\x19\x02' + struct.pack('B', item_slot) + struct.pack('B', wheel_slot)
inject_joymax(WHEEL_OPCODE, p, False)
attempts += 1
log('[%s] %s #%d (item slot %d).' % (pName, WHEEL_ITEM_NAME, attempts, item_slot))
# ============================================================================
# Response: count lines per attribute and check the conditions
# ============================================================================
def eval_conditions(counts):
"""Check the conditions set (> 0). Returns (met, progress_text).
AND mode (checkbox off): met only when EVERY set condition has enough
lines (e.g. STR 2 AND INT 2 -> both must reach the target).
OR mode (checkbox 'When one condition met' on): met as soon as ONE set
condition reaches its target."""
conds = read_conditions()
parts = []
any_set = False
all_met = True
one_met = False
for key, _fld in COND_FIELDS:
needed = conds.get(key, 0)
if needed > 0:
any_set = True
have = counts.get(key, 0)
if have < needed:
all_met = False
else:
one_met = True
parts.append('%s %d/%d' % (key, have, needed))
met = one_met if any_mode() else all_met
progress = (' '.join(parts) if parts else '(no conditions set)')
if any_set and parts:
progress += ' [%s]' % ('ANY' if any_mode() else 'ALL')
return (any_set and met), progress
def handle_joymax(opcode, data):
global _timer, qi
if opcode == WHEEL_RESULT_OPCODE and started:
try:
is_success = data[2]
if is_success == 1 and len(data) > MAGICLINES_OFFSET:
n_lines = data[MAGICLINES_OFFSET]
counts = {}
values = {}
idx = MAGICLINES_OFFSET + 1
for x in range(n_lines):
if idx + 8 > len(data):
break
mid = struct.unpack_from('<I', data, idx)[0]
amount = struct.unpack_from('<I', data, idx + 4)[0]
idx += 8 # id (4) + amount (4)
if x == 0:
continue # first line is the base one
sn = magic_name(mid)
key = attr_key(sn)
if key:
counts[key] = counts.get(key, 0) + 1
values[key] = values.get(key, 0) + amount
summary = ' '.join('%s:%d (%dp)' % (k, counts[k], values.get(k, 0)) for k in counts) or '(no tracked attributes)'
met, progress = eval_conditions(counts)
add_history('Attempt %d: %s' % (attempts, summary))
set_status('attempt %d | %s' % (attempts, progress))
log('[%s] Result #%d (raw lines=%d): %s -> %s' % (pName, attempts, n_lines, summary, progress))
update_blues(values)
if met:
log('[%s] Item done: %s (%s).' % (pName, progress,
'one condition met' if any_mode() else 'all conditions met'))
if queue:
done = queue.pop(0)
try:
QtBind.remove(gui, lstQueue, done[1])
except Exception:
pass
log('[%s] Removed from queue: %s. Remaining: %d.' % (pName, done[1], len(queue)))
if queue:
if started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
else:
log('[%s] DONE! Queue finished.' % pName)
stop()
elif started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
else:
if started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
except Exception as e:
log('[%s] Parse error: %s' % (pName, e))
return True
# ============================================================================
# Init
# ============================================================================
load_config()
log('[%s] v%s loaded OK.' % (pName, pVersion))
▸ WheelOfFortune.py v1.8 Download
# -*- coding: utf-8 -*-
#
# WheelOfFortune - plugin for phBot (iSRO-R)
# -------------------------------------------
# Automatically uses "Wheel of Fortune" on an item and stops when YOUR
# conditions (line count per attribute) are met.
# Example: STR >= 2 lines AND INT >= 2 lines (other lines do not matter).
# Logic is AND: it stops only when ALL the conditions you set (> 0) are met.
#
# It resolves each magic line's attribute from the phBot database
# (Data/*.db3, table magicoption). Separate from AutoWheels and WheelOfFate.
#
# NOTE: every spin consumes one Wheel of Fortune.
#
from phBot import *
from threading import Timer
import struct
import QtBind
import os
import json
import sqlite3
pName = 'WheelOfFortune'
pVersion = '1.8'
WHEEL_OPCODE = 0x7151
WHEEL_RESULT_OPCODE = 0xB151
MAGICLINES_OFFSET = 26 # number_of_magiclines in the 0xB151 response
WHEEL_ITEM_NAME = 'Wheel of Fortune'
INSTALL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.path.join(INSTALL_DIR, 'Data')
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), pName + '.json')
started = False
_timer = None
current_items = {}
attempts = 0
queue = [] # item slots in the queue (multi-item)
qi = 0 # index of the current item in the queue
_db = None
history = [] # result lines, newest first
# ============================================================================
# GUI
# ============================================================================
gui = QtBind.init(__name__, pName)
# ---- Title ----
QtBind.createLabel(gui, 'W H E E L O F F O R T U N E v%s' % pVersion, 10, 6)
# ---- Item selection ----
QtBind.createLabel(gui, 'Item to spin:', 10, 32)
combo_items = QtBind.createCombobox(gui, 10, 52, 180, 22)
QtBind.createButton(gui, 'on_refresh', 'Refresh', 196, 51)
QtBind.createButton(gui, 'on_add_queue', '+Queue', 300, 51)
QtBind.createButton(gui, 'on_clear_queue', 'Clear', 404, 51)
QtBind.createLabel(gui, 'Stop when lines >= (0 = ignore). ALL set conditions must be met:', 10, 82)
# column 1
QtBind.createLabel(gui, 'STR', 10, 106)
txtSTR = QtBind.createLineEdit(gui, '0', 70, 104, 35, 20)
QtBind.createLabel(gui, 'INT', 10, 130)
txtINT = QtBind.createLineEdit(gui, '0', 70, 128, 35, 20)
QtBind.createLabel(gui, 'Critical', 10, 154)
txtCRIT = QtBind.createLineEdit(gui, '0', 70, 152, 35, 20)
QtBind.createLabel(gui, 'Attack R.', 10, 178)
txtHR = QtBind.createLineEdit(gui, '0', 70, 176, 35, 20)
# column 2
QtBind.createLabel(gui, 'HP', 130, 106)
txtHP = QtBind.createLineEdit(gui, '0', 195, 104, 35, 20)
QtBind.createLabel(gui, 'MP', 130, 130)
txtMP = QtBind.createLineEdit(gui, '0', 195, 128, 35, 20)
QtBind.createLabel(gui, 'Block R.', 130, 154)
txtBLOCK = QtBind.createLineEdit(gui, '0', 195, 152, 35, 20)
QtBind.createLabel(gui, 'Parry R.', 130, 178)
txtER = QtBind.createLineEdit(gui, '0', 195, 176, 35, 20)
QtBind.createLabel(gui, 'Delay (ms):', 250, 106)
txtDelay = QtBind.createLineEdit(gui, '2000', 320, 104, 55, 20)
QtBind.createButton(gui, 'on_save', ' Save ', 250, 130)
btnStartStop = QtBind.createButton(gui, 'on_startstop', ' Start ', 250, 172)
lblStatus = QtBind.createLabel(gui, 'stopped', 250, 204)
QtBind.createLabel(gui, 'History:', 10, 204)
lstResult = QtBind.createList(gui, 10, 222, 380, 40)
# right panel: current blue lines (updated every wheel)
BLUE_ORDER = [('STR', 'STR'), ('INT', 'INT'), ('HP', 'HP'), ('MP', 'MP'),
('CRIT', 'Crit'), ('BLOCK', 'Block'), ('HR', 'AtkR'), ('ER', 'ParR')]
QtBind.createLabel(gui, 'Blue:', 410, 84)
lstBlue = QtBind.createList(gui, 410, 104, 85, 158)
QtBind.createLabel(gui, 'Queue (remaining):', 505, 84)
lstQueue = QtBind.createList(gui, 505, 104, 180, 158)
# ---- Footer ----
QtBind.createLabel(gui, 'Author : CK3 @ iLog1K.net', 10, 272)
# condition key -> GUI field mapping
COND_FIELDS = [
('STR', 'txtSTR'), ('INT', 'txtINT'), ('CRIT', 'txtCRIT'), ('HR', 'txtHR'),
('HP', 'txtHP'), ('MP', 'txtMP'), ('BLOCK', 'txtBLOCK'), ('ER', 'txtER'),
]
# ============================================================================
# Helpers
# ============================================================================
def set_status(txt):
QtBind.setText(gui, lblStatus, txt)
def add_history(text):
"""Newest result on the first row; older ones below (scroll to see them)."""
history.insert(0, text)
try:
QtBind.clear(gui, lstResult)
for line in history:
QtBind.append(gui, lstResult, line)
except Exception:
pass
def clear_history():
history[:] = []
try:
QtBind.clear(gui, lstResult)
except Exception:
pass
def update_blues(values):
try:
QtBind.clear(gui, lstBlue)
for _k, _d in BLUE_ORDER:
QtBind.append(gui, lstBlue, '%s = %d' % (_d, values.get(_k, 0)))
except Exception:
pass
def delay_sec():
try:
return max(0.0, int(QtBind.text(gui, txtDelay).strip()) / 1000.0)
except Exception:
return 2.0
def read_conditions():
out = {}
for key, fld in COND_FIELDS:
try:
out[key] = max(0, int(QtBind.text(gui, globals()[fld]).strip()))
except Exception:
out[key] = 0
return out
def attr_key(name):
"""magicoption servername -> tracked attribute key (or None)."""
if not name:
return None
n = name.upper()
if 'MATTR_STR' in n:
return 'STR'
if 'MATTR_INT' in n:
return 'INT'
if 'MATTR_REGENHPMP' in n:
return None
if 'MATTR_HP' in n:
return 'HP'
if 'MATTR_MP' in n:
return 'MP'
if 'EVADE_CRITICAL' in n:
return None
if 'MATTR_CRITICAL' in n:
return 'CRIT'
if 'MATTR_BLOCKRATE' in n:
return 'BLOCK'
if 'MATTR_HR' in n:
return 'HR'
if 'MATTR_ER' in n:
return 'ER'
return None
# ============================================================================
# Database (resolve magic line id -> servername)
# ============================================================================
def _db3_files():
try:
return [os.path.join(DATA_DIR, fn) for fn in os.listdir(DATA_DIR)
if fn.lower().endswith('.db3')]
except Exception:
return []
def _lookup(con, mid):
try:
r = con.cursor().execute('SELECT name FROM magicoption WHERE id=?', (mid,)).fetchone()
return r[0] if r else None
except Exception:
return None
def magic_name(mid):
"""Pick AUTOMATICALLY the db3 that resolves the current server's ids."""
global _db
if _db is not None:
n = _lookup(_db, mid)
if n:
return n
for f in _db3_files():
try:
con = sqlite3.connect(f, check_same_thread=False)
except Exception:
continue
n = _lookup(con, mid)
if n:
if _db is not None:
try:
_db.close()
except Exception:
pass
_db = con
log('[%s] Matched DB: %s' % (pName, os.path.basename(f)))
return n
try:
con.close()
except Exception:
pass
return None
# ============================================================================
# Config
# ============================================================================
def on_save(*_):
try:
data = {'delay': int(delay_sec() * 1000)}
for key, fld in COND_FIELDS:
data[key] = QtBind.text(gui, globals()[fld])
with open(CONFIG_FILE, 'w') as f:
json.dump(data, f, indent=2)
log('[%s] Saved.' % pName)
except Exception as e:
log('[%s] Save error: %s' % (pName, e))
def load_config():
try:
if os.path.isfile(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
data = json.load(f)
QtBind.setText(gui, txtDelay, str(data.get('delay', 2000)))
for key, fld in COND_FIELDS:
QtBind.setText(gui, globals()[fld], str(data.get(key, '0')))
except Exception as e:
log('[%s] Config read error: %s' % (pName, e))
# ============================================================================
# Items
# ============================================================================
def on_refresh():
current_items.clear()
QtBind.clear(gui, combo_items)
inv = get_inventory()
if not inv or 'items' not in inv:
log('[%s] You are not in game.' % pName)
return
idx = 0
for slot, item in enumerate(inv['items']):
if not item or slot <= 13:
continue
try:
ref = get_item(item['model'])
except Exception:
ref = None
if ref and ref.get('tid1') == 1:
current_items[idx] = slot
idx += 1
QtBind.append(gui, combo_items, '%s +%s' % (item['name'], item.get('plus', 0)))
log('[%s] %d equippable items.' % (pName, idx))
def selected_item_slot():
if not current_items:
return None
return current_items.get(QtBind.currentIndex(gui, combo_items))
def current_target_slot():
if queue:
return queue[0][0]
return selected_item_slot()
def on_add_queue():
slot = selected_item_slot()
if slot is None:
log('[%s] Select an item (Refresh) before adding it to the queue.' % pName)
return
inv = get_inventory()
it = inv['items'][slot] if (inv and 'items' in inv and slot < len(inv['items'])) else None
label = ('%s +%s' % (it.get('name', '?'), it.get('plus', 0))) if isinstance(it, dict) else ('slot %d' % slot)
queue.append((slot, label))
QtBind.append(gui, lstQueue, label)
log('[%s] Added to queue: %s. Total: %d.' % (pName, label, len(queue)))
def on_clear_queue():
queue[:] = []
try:
QtBind.clear(gui, lstQueue)
except Exception:
pass
log('[%s] Queue cleared.' % pName)
def get_wheel_slot():
inv = get_inventory()
if not inv or 'items' not in inv:
return -1
for slot, item in enumerate(inv['items']):
if item and WHEEL_ITEM_NAME in (item.get('name') or ''):
return slot
return -1
# ============================================================================
# Start / Stop
# ============================================================================
def on_startstop():
global started, qi
if not started:
if not queue and selected_item_slot() is None:
log('[%s] Select an item (Refresh) or add one to the queue.' % pName)
return
if all(v == 0 for v in read_conditions().values()):
log('[%s] Set at least one condition (> 0).' % pName)
return
started = True
qi = 0
QtBind.setText(gui, btnStartStop, ' Stop ')
clear_history()
set_status('running')
use_wheel()
else:
stop()
def stop():
global started, _timer
started = False
QtBind.setText(gui, btnStartStop, ' Start ')
if _timer:
try:
_timer.cancel()
except Exception:
pass
_timer = None
set_status('stopped')
# ============================================================================
# Use wheel
# ============================================================================
def use_wheel():
global attempts
if not started:
return
item_slot = current_target_slot()
if item_slot is None:
stop()
return
wheel_slot = get_wheel_slot()
if wheel_slot == -1:
log('[%s] No %s left in inventory. Stopping.' % (pName, WHEEL_ITEM_NAME))
stop()
return
p = b'\x02\x19\x02' + struct.pack('B', item_slot) + struct.pack('B', wheel_slot)
inject_joymax(WHEEL_OPCODE, p, False)
attempts += 1
log('[%s] %s #%d (item slot %d).' % (pName, WHEEL_ITEM_NAME, attempts, item_slot))
# ============================================================================
# Response: count lines per attribute and check the conditions
# ============================================================================
def eval_conditions(counts):
"""AND over every condition set (> 0). Returns (all_met, progress_text).
all_met is True only when at least one condition is set AND every set
condition has enough lines (e.g. STR 2 AND INT 2 -> both must reach the
target, not just one of them)."""
conds = read_conditions()
parts = []
any_set = False
all_met = True
for key, _fld in COND_FIELDS:
needed = conds.get(key, 0)
if needed > 0:
any_set = True
have = counts.get(key, 0)
if have < needed:
all_met = False
parts.append('%s %d/%d' % (key, have, needed))
return (any_set and all_met), (' '.join(parts) if parts else '(no conditions set)')
def handle_joymax(opcode, data):
global _timer, qi
if opcode == WHEEL_RESULT_OPCODE and started:
try:
is_success = data[2]
if is_success == 1 and len(data) > MAGICLINES_OFFSET:
n_lines = data[MAGICLINES_OFFSET]
counts = {}
values = {}
idx = MAGICLINES_OFFSET + 1
for x in range(n_lines):
if idx + 8 > len(data):
break
mid = struct.unpack_from('<I', data, idx)[0]
amount = struct.unpack_from('<I', data, idx + 4)[0]
idx += 8 # id (4) + amount (4)
if x == 0:
continue # first line is the base one
sn = magic_name(mid)
key = attr_key(sn)
if key:
counts[key] = counts.get(key, 0) + 1
values[key] = values.get(key, 0) + amount
summary = ' '.join('%s:%d (%dp)' % (k, counts[k], values.get(k, 0)) for k in counts) or '(no tracked attributes)'
met, progress = eval_conditions(counts)
add_history('Attempt %d: %s' % (attempts, summary))
set_status('attempt %d | %s' % (attempts, progress))
log('[%s] Result #%d (raw lines=%d): %s -> %s' % (pName, attempts, n_lines, summary, progress))
update_blues(values)
if met:
log('[%s] Item done: %s (all conditions met).' % (pName, progress))
if queue:
done = queue.pop(0)
try:
QtBind.remove(gui, lstQueue, done[1])
except Exception:
pass
log('[%s] Removed from queue: %s. Remaining: %d.' % (pName, done[1], len(queue)))
if queue:
if started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
else:
log('[%s] DONE! Queue finished.' % pName)
stop()
elif started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
else:
if started:
_timer = Timer(delay_sec(), use_wheel)
_timer.start()
except Exception as e:
log('[%s] Parse error: %s' % (pName, e))
return True
# ============================================================================
# Init
# ============================================================================
load_config()
log('[%s] v%s loaded OK.' % (pName, pVersion))
Sign in to propose something or back an existing proposal.
No proposals yet. Be the first to suggest something.
Sign in to join the discussion.
No comments yet.
Sign in to report a bug.
No bug reports. Nice.