publish eve_rock_watcher.py
This commit is contained in:
parent
19880dbf36
commit
c356fc4fd3
1 changed files with 71 additions and 74 deletions
|
|
@ -26,6 +26,7 @@ import re
|
|||
import socket
|
||||
import sys
|
||||
import time
|
||||
from collections import deque
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
|
|
@ -80,6 +81,35 @@ def parse_selected(text):
|
|||
return int(digits) if digits and len(digits) >= 2 else None
|
||||
|
||||
|
||||
# The selected rock's info popup shows "Quantity 47,765 Units". That popup FLOATS (it
|
||||
# follows the rock on screen), so a fixed region can't track it. Instead OCR the whole
|
||||
# game window and find that text wherever it is — anchored on "Quantity ... Units" so it
|
||||
# won't false-match the overview (which lists distance/size, never a unit quantity).
|
||||
QUANTITY_RE = re.compile(r"quantit[yvſ]\s*[:.]?\s*([\d.,]{2,})\s*units?", re.I)
|
||||
|
||||
|
||||
def read_selected_quantity(cp):
|
||||
"""Return (units, ore) for the currently-selected rock from its floating info popup,
|
||||
found anywhere in the EVE window. (None, None) if no rock is selected/visible."""
|
||||
import pytesseract
|
||||
tcmd = cp.get("ocr", "tesseract_cmd", fallback="").strip()
|
||||
if tcmd:
|
||||
pytesseract.pytesseract.tesseract_cmd = tcmd
|
||||
img = w.capture_window()
|
||||
if img is None:
|
||||
return None, None
|
||||
text = pytesseract.image_to_string(img, config="--psm 6")
|
||||
flat = text.replace("\n", " ")
|
||||
m = QUANTITY_RE.search(flat)
|
||||
if not m:
|
||||
return None, None
|
||||
digits = re.sub(r"\D", "", m.group(1))
|
||||
if len(digits) < 2:
|
||||
return None, None
|
||||
ore = match_ore(flat.lower()) or "rock"
|
||||
return int(digits), ore
|
||||
|
||||
|
||||
def save_rock_region(cp, l, t, wd, ht, mode="survey"):
|
||||
if not cp.has_section("rock"):
|
||||
cp.add_section("rock")
|
||||
|
|
@ -273,19 +303,14 @@ def main():
|
|||
# wrong "~1s, switch now" ETAs. Until OCR is trustworthy, DON'T post from here; the
|
||||
# reliable switch-rocks signal is the gamelog mining-stall alert in the combat watcher.
|
||||
# Flip [rock] post_rock=true to re-enable once OCR reads are validated.
|
||||
post_rock = cp.getboolean("rock", "post_rock", fallback=False) if sec else False
|
||||
post_rock = cp.getboolean("rock", "post_rock", fallback=True) if sec else True
|
||||
|
||||
if "--test" in sys.argv:
|
||||
import pytesseract
|
||||
tcmd = cp.get("ocr", "tesseract_cmd", fallback="").strip()
|
||||
if tcmd:
|
||||
pytesseract.pytesseract.tesseract_cmd = tcmd
|
||||
region, mode = get_region(cp)
|
||||
if not region:
|
||||
print("Couldn't find a Survey Scanner window or a Selected-Item 'Quantity N "
|
||||
"Units'. Select a rock (or open the survey results) and retry.")
|
||||
return
|
||||
print(f"mode={mode} rows:", read_rows(cp, region, mode))
|
||||
units, ore = read_selected_quantity(cp)
|
||||
if units is None:
|
||||
print("READ none — no 'Quantity N Units' popup visible (select your rock).")
|
||||
else:
|
||||
print(f"READ ore={ore} units={units:,}")
|
||||
return
|
||||
|
||||
import pytesseract
|
||||
|
|
@ -293,81 +318,53 @@ def main():
|
|||
if tcmd:
|
||||
pytesseract.pytesseract.tesseract_cmd = tcmd
|
||||
|
||||
poll = max(poll, 8) # full-window OCR is heavy; don't spin
|
||||
print(f"[rock] started; switch<{switch_secs}s, poll {poll}s (waits for !mining on)")
|
||||
tracker = Tracker()
|
||||
last_alert = {}
|
||||
hist = deque() # (t, units) for the currently-selected rock
|
||||
last_status = 0.0
|
||||
region = mode = None
|
||||
empty_streak = 0
|
||||
last_alert = 0.0
|
||||
while True:
|
||||
if not w.bot_mining(cp): # only during a mining session
|
||||
tracker.h.clear()
|
||||
hist.clear()
|
||||
time.sleep(15)
|
||||
continue
|
||||
if region is None:
|
||||
region, mode = get_region(cp)
|
||||
if region is None:
|
||||
print("[rock] no survey window / selected rock visible — select a rock. "
|
||||
"Retrying...")
|
||||
time.sleep(max(poll, 15))
|
||||
continue
|
||||
w.heartbeat(cp, "rock")
|
||||
try:
|
||||
rows = read_rows(cp, region, mode)
|
||||
if not rows:
|
||||
# The saved region may be stale (you compressed, reselected, or the window
|
||||
# moved), so a fixed box reads empty forever. After a short empty streak,
|
||||
# re-acquire fresh: prefer the Survey Scanner window (selection-independent —
|
||||
# survives compression), then fall back to the Selected-Item panel.
|
||||
empty_streak += 1
|
||||
if empty_streak % 6 == 0:
|
||||
nm = None
|
||||
r = detect_survey_region(cp)
|
||||
if r:
|
||||
region, mode, nm = r, "survey", "survey"
|
||||
else:
|
||||
r = detect_selected_region(cp)
|
||||
if r:
|
||||
region, mode, nm = r, "selected", "selected"
|
||||
print(f"[rock] empty {empty_streak}x — re-detect: {nm or 'still nothing visible'}")
|
||||
units, ore = read_selected_quantity(cp)
|
||||
now = time.time()
|
||||
if units is None: # no rock selected / popup not shown
|
||||
time.sleep(poll)
|
||||
continue
|
||||
empty_streak = 0
|
||||
now = time.time()
|
||||
keyed = tracker.update(rows, now)
|
||||
hr = hold_rate()
|
||||
actives = [] # (key, ore, units, tleft)
|
||||
for key, ore, units in keyed:
|
||||
r = tracker.rate(key) # measured units/sec
|
||||
if r <= 0 and len(keyed) == 1 and hr > 0 and ORE_VOL.get(ore):
|
||||
r = hr / 60.0 / ORE_VOL[ore] # single-rock hold-fill fallback
|
||||
if r <= 0:
|
||||
continue
|
||||
actives.append((key, ore, units, units / r))
|
||||
# the rock you're emptying = the one with the least time left
|
||||
actives.sort(key=lambda x: x[3])
|
||||
for key, ore, units, tleft in actives:
|
||||
if post_rock and tleft <= switch_secs and now - last_alert.get(key, 0) > cooldown:
|
||||
last_alert[key] = now
|
||||
others = [(o, u) for (k, o, u) in keyed if k != key]
|
||||
nxt = max(others, key=lambda x: x[1]) if others else None
|
||||
# quantity jumped UP -> you locked a fresh rock; restart the measurement
|
||||
if hist and units > hist[-1][1] + 50:
|
||||
hist.clear()
|
||||
hist.append((now, units))
|
||||
while hist and now - hist[0][0] > 180: # keep a ~3 min window
|
||||
hist.popleft()
|
||||
# true depletion rate from the number falling — captures ALL miners on the rock
|
||||
rate = 0.0 # units/sec
|
||||
if len(hist) >= 2 and (hist[-1][0] - hist[0][0]) >= 20:
|
||||
du, dt = hist[0][1] - hist[-1][1], hist[-1][0] - hist[0][0]
|
||||
rate = du / dt if du > 0 else 0.0
|
||||
tleft = units / rate if rate > 0 else None
|
||||
# live readout (edit-in-place, no spam)
|
||||
if post_rock and not w.bot_muted(cp) and now - last_status >= status_secs:
|
||||
if tleft:
|
||||
empty_at = int(now + tleft)
|
||||
msg = f"{ore.title()} rock empties in ~{_fmt_dur(tleft)} (<t:{empty_at}:R>) — {units:,} u left."
|
||||
if nxt:
|
||||
msg += f" Switch to {nxt[0].title()} ({nxt[1]:,} u)."
|
||||
w.notify(cp, "Switch rocks", msg, priority="high", tags="pick,gem")
|
||||
print(f"[rock] ALERT {msg}")
|
||||
# periodic live readout of the rock you're on (so you see the countdown)
|
||||
if post_rock and actives and not w.bot_muted(cp) and now - last_status >= status_secs:
|
||||
key, ore, units, tleft = actives[0]
|
||||
empty_at = int(now + tleft)
|
||||
others = [(o, u) for (k, o, u) in keyed if k != key]
|
||||
nxt = max(others, key=lambda x: x[1]) if others else None
|
||||
line = f"🪨 {ore.title()} {units:,} u · ~{_fmt_dur(tleft)} left (empties <t:{empty_at}:R>)"
|
||||
if nxt:
|
||||
line += f" · next: {nxt[0].title()} ({nxt[1]:,} u)"
|
||||
line = (f"🪨 {ore.title()} {units:,} u · ~{_fmt_dur(tleft)} left "
|
||||
f"(empties <t:{empty_at}:R>) · {rate*60:,.0f} u/min")
|
||||
else:
|
||||
line = f"🪨 {ore.title()} {units:,} u · measuring rate…"
|
||||
w._discord_live(cp, "rock", f"⛏️ {socket.gethostname()} current rock", line)
|
||||
last_status = now
|
||||
# switch-rocks alert when it's genuinely about to empty
|
||||
if post_rock and tleft and tleft <= switch_secs and now - last_alert > cooldown:
|
||||
last_alert = now
|
||||
empty_at = int(now + tleft)
|
||||
w.notify(cp, "Switch rocks",
|
||||
f"{ore.title()} rock empties in ~{_fmt_dur(tleft)} "
|
||||
f"(<t:{empty_at}:R>) — {units:,} u left. Lock a new rock.",
|
||||
priority="high", tags="pick,gem")
|
||||
print(f"[rock] ALERT {ore} {units} u, ~{_fmt_dur(tleft)} left")
|
||||
except Exception as e:
|
||||
print(f"[rock] error: {e}")
|
||||
time.sleep(poll)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue