Add Ghostbox hardware and logistics automation
This commit is contained in:
@@ -20,14 +20,20 @@ class LogisticsManager:
|
||||
self.route_file = route_file or VENDOR_FILE
|
||||
self.bag_full_hearthstone = False # 包满时用炉石回城而非走路修理
|
||||
self.hearthstone_key = "b" # 炉石按键
|
||||
self.hearthstone_cast_sec = 10.0 # 炉石施法等待秒数
|
||||
self.hearthstone_cast_sec = 12.0 # 炉石施法等待秒数
|
||||
self.enable_bag_full_mail = False
|
||||
self.logistics_full_auto = False
|
||||
self.bag_slot_threshold = 2
|
||||
self.durability_threshold = 0.2
|
||||
self.mailbox_route_file = os.path.join("recorder", "mailbox.json")
|
||||
self.mailbox_interact_key = "8"
|
||||
self.mail_recipient_key = ""
|
||||
self.mail_send_key = "f8"
|
||||
self.mailbox_open_wait_sec = 2.0
|
||||
self.mail_send_wait_sec = 60.0
|
||||
self.repair_target_key = "8"
|
||||
self.repair_interact_key = "4"
|
||||
self.repair_interact_wait_sec = 2.0
|
||||
|
||||
def _resolve_path(self, path):
|
||||
if not path:
|
||||
@@ -47,14 +53,60 @@ class LogisticsManager:
|
||||
return False
|
||||
time.sleep(min(0.1, end_at - time.time()))
|
||||
return True
|
||||
|
||||
def _read_position(self, get_state):
|
||||
if not callable(get_state):
|
||||
return None
|
||||
st = get_state()
|
||||
if not st:
|
||||
return None
|
||||
try:
|
||||
x = st.get("x")
|
||||
y = st.get("y")
|
||||
if x is None or y is None:
|
||||
return None
|
||||
return float(x), float(y)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _state_bag_full(self, state):
|
||||
try:
|
||||
return int(state.get("free_slots", 0) or 0) < int(self.bag_slot_threshold)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _state_durability_low(self, state):
|
||||
try:
|
||||
threshold = float(self.durability_threshold)
|
||||
if threshold > 1.0:
|
||||
threshold = threshold / 100.0
|
||||
durability = state.get("durability", 1.0)
|
||||
if durability is None:
|
||||
return False
|
||||
return float(durability) < threshold
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_pending_tasks(self, state):
|
||||
tasks = []
|
||||
bag_full = self._state_bag_full(state)
|
||||
durability_low = self._state_durability_low(state)
|
||||
|
||||
if bag_full and self.enable_bag_full_mail:
|
||||
tasks.append("mail")
|
||||
if durability_low:
|
||||
tasks.append("repair")
|
||||
if not tasks and bag_full and self.bag_full_hearthstone:
|
||||
tasks.append("hearthstone_stop")
|
||||
return tasks
|
||||
|
||||
def check_logistics(self, state):
|
||||
"""
|
||||
state['free_slots']: 剩余空格数量
|
||||
state['durability']: 0.0 ~ 1.0 的耐久度
|
||||
"""
|
||||
# 触发阈值:空格少于 2 个,或耐久度低于 20%
|
||||
if state['free_slots'] < 2 or state['durability'] < 0.2:
|
||||
# 触发阈值:空格少于配置值,或耐久度低于配置值。
|
||||
if self.get_pending_tasks(state):
|
||||
if not self.is_returning:
|
||||
print(f">>> [后勤警告] 背包/耐久不足!触发回城程序。")
|
||||
self.is_returning = True
|
||||
@@ -67,11 +119,7 @@ class LogisticsManager:
|
||||
success = False
|
||||
|
||||
for i in range(max_retries):
|
||||
start_pos = None
|
||||
if get_state:
|
||||
st = get_state()
|
||||
if st:
|
||||
start_pos = (st.get('x'), st.get('y'))
|
||||
start_pos = self._read_position(get_state)
|
||||
|
||||
print(f">>> [后勤] 第 {i+1} 次尝试使用炉石(按键: {self.hearthstone_key})...")
|
||||
# 先按一下 S 确保停止移动,防止移动中按炉石失败
|
||||
@@ -80,25 +128,34 @@ class LogisticsManager:
|
||||
hw_ctrl.press(self.hearthstone_key)
|
||||
|
||||
# 等待施法过程
|
||||
print(f">>> [后勤] 正在等待施法 {self.hearthstone_cast_sec}s...")
|
||||
time.sleep(self.hearthstone_cast_sec + 2.0) # 多等 2 秒保险
|
||||
print(f">>> [后勤] 正在等待炉石 {self.hearthstone_cast_sec}s...")
|
||||
time.sleep(float(self.hearthstone_cast_sec))
|
||||
|
||||
if get_state and start_pos and start_pos[0] is not None:
|
||||
st_now = get_state()
|
||||
if st_now:
|
||||
end_pos = (st_now.get('x'), st_now.get('y'))
|
||||
if get_state and start_pos is not None:
|
||||
for _ in range(5):
|
||||
end_pos = self._read_position(get_state)
|
||||
if end_pos is None:
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
dist = math.dist(start_pos, end_pos)
|
||||
# 如果坐标发生了明显跳变(大于 2.0),证明回城成功
|
||||
if dist > 2.0:
|
||||
print(f">>> [后勤] 炉石回城成功!位置跳变距离: {dist:.2f}")
|
||||
success = True
|
||||
break
|
||||
else:
|
||||
print(f">>> [后勤] 炉石似乎失败(位置未变化),准备重试...")
|
||||
else:
|
||||
time.sleep(0.5)
|
||||
if success:
|
||||
break
|
||||
print(f">>> [后勤] 炉石似乎失败(位置未变化),准备重试...")
|
||||
elif get_state:
|
||||
st_now = get_state()
|
||||
if st_now is None:
|
||||
# 获取不到状态可能已经卡死或窗口关闭,默认成功以退出循环
|
||||
success = True
|
||||
break
|
||||
else:
|
||||
success = True
|
||||
break
|
||||
else:
|
||||
# 如果没法校验坐标,就只执行一次
|
||||
success = True
|
||||
@@ -130,13 +187,13 @@ class LogisticsManager:
|
||||
self.is_returning = False
|
||||
|
||||
def _do_vendor_interact(self):
|
||||
"""执行与修理商/背包的交互按键(8、4)。"""
|
||||
hw_ctrl.press("8")
|
||||
"""执行与修理商/背包的交互按键。"""
|
||||
hw_ctrl.press(self.repair_target_key)
|
||||
time.sleep(0.5)
|
||||
hw_ctrl.press("4")
|
||||
time.sleep(2)
|
||||
hw_ctrl.press(self.repair_interact_key)
|
||||
time.sleep(float(self.repair_interact_wait_sec))
|
||||
|
||||
def run_bag_full_mail_flow(self, get_state, patrol, stop_check=None):
|
||||
def run_bag_full_mail_flow(self, get_state, patrol, stop_check=None, use_hearthstone=True):
|
||||
"""
|
||||
包满邮寄第一版流程:
|
||||
炉石 -> 跑到邮箱路线终点 -> 交互打开邮箱 -> 按 MailboxCourier 宏键 -> 等待后停止。
|
||||
@@ -152,11 +209,12 @@ class LogisticsManager:
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
ok = self.use_hearthstone_and_stop(get_state=get_state)
|
||||
if not ok:
|
||||
print(">>> [后勤-邮箱] 炉石失败,未继续跑邮箱。")
|
||||
self.is_returning = False
|
||||
return False
|
||||
if use_hearthstone:
|
||||
ok = self.use_hearthstone_and_stop(get_state=get_state)
|
||||
if not ok:
|
||||
print(">>> [后勤-邮箱] 炉石失败,未继续跑邮箱。")
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(route_file, "r", encoding="utf-8") as f:
|
||||
@@ -176,7 +234,13 @@ class LogisticsManager:
|
||||
if old_enable_mount is not None:
|
||||
patrol.enable_mount = False
|
||||
try:
|
||||
ok = patrol.navigate_path(get_state, path, forward=True, arrival_threshold=VENDOR_ARRIVAL_THRESHOLD)
|
||||
ok = patrol.navigate_path(
|
||||
get_state,
|
||||
path,
|
||||
forward=True,
|
||||
arrival_threshold=VENDOR_ARRIVAL_THRESHOLD,
|
||||
snap_to_nearest=True,
|
||||
)
|
||||
finally:
|
||||
if old_enable_mount is not None:
|
||||
patrol.enable_mount = old_enable_mount
|
||||
@@ -213,6 +277,119 @@ class LogisticsManager:
|
||||
self.is_returning = False
|
||||
return True
|
||||
|
||||
def run_repair_flow(self, get_state, patrol, stop_check=None, use_hearthstone=True):
|
||||
"""
|
||||
耐久低修理流程:
|
||||
炉石 -> 从修理路线最近点接入 -> 到达 NPC -> 按目标键 -> 按交互/修理键。
|
||||
"""
|
||||
route_file = self._resolve_path(self.route_file)
|
||||
if not route_file or not os.path.exists(route_file):
|
||||
print(f">>> [后勤-修理] 修理路线不存在,已停止: {route_file}")
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
if callable(stop_check) and stop_check():
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
if use_hearthstone:
|
||||
ok = self.use_hearthstone_and_stop(get_state=get_state)
|
||||
if not ok:
|
||||
print(">>> [后勤-修理] 炉石失败,未继续跑修理路线。")
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(route_file, "r", encoding="utf-8") as f:
|
||||
path = json.load(f)
|
||||
except Exception as exc:
|
||||
print(f">>> [后勤-修理] 修理路线读取失败: {exc}")
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
if not path:
|
||||
print(f">>> [后勤-修理] 修理路线为空,已停止: {route_file}")
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
print(f">>> [后勤-修理] 开始跑修理路线: {route_file}")
|
||||
ok = patrol.navigate_path(
|
||||
get_state,
|
||||
path,
|
||||
forward=True,
|
||||
arrival_threshold=VENDOR_ARRIVAL_THRESHOLD,
|
||||
snap_to_nearest=True,
|
||||
)
|
||||
if not ok:
|
||||
print(">>> [后勤-修理] 修理路线未完成,已停止。")
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
if callable(stop_check) and stop_check():
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
patrol.stop_all()
|
||||
print(f">>> [后勤-修理] 到达 NPC,按目标键: {self.repair_target_key}")
|
||||
hw_ctrl.press(self.repair_target_key)
|
||||
if not self._sleep_with_stop(0.5, stop_check=stop_check):
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
print(f">>> [后勤-修理] 按修理交互键: {self.repair_interact_key}")
|
||||
hw_ctrl.press(self.repair_interact_key)
|
||||
if not self._sleep_with_stop(self.repair_interact_wait_sec, stop_check=stop_check):
|
||||
self.is_returning = False
|
||||
return False
|
||||
|
||||
print(">>> [后勤-修理] 修理流程结束。")
|
||||
self.is_returning = False
|
||||
return True
|
||||
|
||||
def run_pending_tasks(self, tasks, get_state, patrol, stop_check=None):
|
||||
completed = []
|
||||
already_homed = False
|
||||
for task in tasks:
|
||||
if callable(stop_check) and stop_check():
|
||||
self.is_returning = False
|
||||
return False, completed
|
||||
|
||||
if task == "mail":
|
||||
ok = self.run_bag_full_mail_flow(
|
||||
get_state,
|
||||
patrol,
|
||||
stop_check=stop_check,
|
||||
use_hearthstone=not already_homed,
|
||||
)
|
||||
if not ok:
|
||||
return False, completed
|
||||
already_homed = True
|
||||
completed.append(task)
|
||||
continue
|
||||
|
||||
if task == "repair":
|
||||
ok = self.run_repair_flow(
|
||||
get_state,
|
||||
patrol,
|
||||
stop_check=stop_check,
|
||||
use_hearthstone=not already_homed,
|
||||
)
|
||||
if not ok:
|
||||
return False, completed
|
||||
already_homed = True
|
||||
completed.append(task)
|
||||
continue
|
||||
|
||||
if task == "hearthstone_stop":
|
||||
ok = self.use_hearthstone_and_stop(get_state=get_state)
|
||||
if not ok:
|
||||
return False, completed
|
||||
already_homed = True
|
||||
completed.append(task)
|
||||
|
||||
self.is_returning = False
|
||||
return True, completed
|
||||
|
||||
def run_route1_round(self, get_state, patrol, route_file=None):
|
||||
"""
|
||||
读取 route1.json 路径,先正向走完,执行交互(8、4),再反向走完,然后结束。
|
||||
|
||||
Reference in New Issue
Block a user