run_mine998.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # -*- coding: utf-8 -*-
  2. """
  3. run_mine998.py —— T04/T05 定案协议的首次无客户端实机验证:
  4. 创角(最新区) → 开喝药 → 领锄头(NPC 10041/43 → @func38_get_hzc → CS1003 穿锄)
  5. → 原地挖矿(CS3014 skillID=999 @0.68s 面壁) → 统计产物播报/金币。
  6. 协议依据: protocol_bot/docs/协议定案-神通开启与五行之力.md 附录A/B。
  7. """
  8. import argparse
  9. import json
  10. import random
  11. import re
  12. import sys
  13. import time
  14. import zlib
  15. import requests as rq
  16. import urllib3
  17. urllib3.disable_warnings()
  18. HERE = __file__.rsplit("/", 1)[0]
  19. sys.path.insert(0, HERE)
  20. from encode6bitbuf_final import decode6 # noqa: E402
  21. import login_996pc as L # noqa: E402
  22. import cq2_pure_enter as P # noqa: E402
  23. from combat_bot import (CombatBot, enter_game_gate_v2, # noqa: E402
  24. send_init_frames, log)
  25. def send_qf(bot, act):
  26. body = json.dumps({"UserID": "999999999", "Act": act,
  27. "index": 999999999}, separators=(",", ":")).encode()
  28. bot.send(1011, 0, 0, 0, bot.sid, body)
  29. def click_npc(bot, cfg_id, index):
  30. body = json.dumps({"UserID": str(cfg_id), "index": index},
  31. separators=(",", ":")).encode()
  32. bot.send(1010, 0, 0, 0, bot.sid, body)
  33. def mine_once(bot, px, py, d, mx, my):
  34. payload = {"skillID": 999, "dir": d, "srcX": px, "srcY": py,
  35. "destX": mx, "destY": my}
  36. bot.send(3014, 0, 0, 0, 0,
  37. json.dumps(payload, separators=(",", ":")).encode())
  38. def main():
  39. ap = argparse.ArgumentParser()
  40. ap.add_argument("-u", "--user", default="longwu998")
  41. ap.add_argument("-p", "--password", default="Longwu1988")
  42. ap.add_argument("--zone", type=int, required=True)
  43. ap.add_argument("--name", default=None)
  44. ap.add_argument("--minutes", type=float, default=5.0)
  45. ap.add_argument("--mine-until", default="",
  46. help="挖矿绝对截止时刻 HH:MM(本地时间); 优先于 --minutes")
  47. a = ap.parse_args()
  48. name = a.name or ("矿工" + str(random.randint(1000, 9999)))
  49. log("[1/7] HTTP登录...")
  50. s = rq.Session()
  51. j = None
  52. for att in range(5):
  53. try:
  54. j, _ = L.do_login(s, a.user, a.password)
  55. if j.get("code") == 200:
  56. break
  57. except Exception as e:
  58. log(f" 异常({att+1}): {str(e)[:60]}")
  59. time.sleep(10)
  60. if not j or j.get("code") != 200:
  61. raise SystemExit("[!] 登录失败")
  62. jwt = j["data"]["token"]
  63. uid = str((j["data"].get("userInfo") or {}).get("id") or P.UID)
  64. log(f"[2/7] 选区 {a.zone}...")
  65. j2 = None
  66. for att in range(5):
  67. try:
  68. j2 = P.enter_game(s, jwt, a.zone)
  69. if j2.get("code") == 200:
  70. break
  71. except Exception as e:
  72. log(f" 异常({att+1}): {str(e)[:60]}")
  73. time.sleep(10)
  74. if not j2 or j2.get("code") != 200:
  75. raise SystemExit("[!] enter_game 失败")
  76. log(f"[3/7] 登录服+创角/复用 {name}...")
  77. ok = None
  78. for pt in (8041, 8021, 8061, 8001, 8063, 8043):
  79. try:
  80. r = P.login_gate(a.zone, jwt, name, pt, uid)
  81. if r and r[0]:
  82. ok = r
  83. break
  84. except SystemExit as e:
  85. log(f" 端口{pt}: {e}")
  86. except Exception as e:
  87. log(f" 端口{pt}: {str(e)[:50]}")
  88. if not ok:
  89. raise SystemExit("[!] 所有端口失败")
  90. key32, sid, rname, tkey, gp = ok
  91. log(f" 角色={rname} 游戏端口={gp}")
  92. log("[4/7] 进世界...")
  93. g = enter_game_gate_v2(a.zone, rname, tkey, sid, gp, uid)
  94. bot = CombatBot(g, sid, rname)
  95. P.heartbeat_loop(g, sid, sender=bot.send)
  96. t0 = time.time()
  97. while time.time() - t0 < 10:
  98. bot.pump(0.3)
  99. if 658 in bot.frame_log:
  100. break
  101. send_init_frames(bot, uid)
  102. for _ in range(24):
  103. bot.pump(0.5)
  104. if bot.me["id"] and bot.me.get("mapname"):
  105. break
  106. if not bot.me["id"]:
  107. g.close()
  108. raise SystemExit("[!] 未收到 SC50")
  109. px, py = bot.me["x"], bot.me["y"]
  110. log(f" 主玩家 @({px},{py}) map={bot.me.get('mapname')}")
  111. log("[5/7] 开喝药...")
  112. st = P.enable_auto_heal(g, sid, want_on=True)
  113. log(f" 喝药={st}")
  114. time.sleep(1)
  115. bot.pump(0.5)
  116. state = {"hoe": False, "wait5": False, "over": False,
  117. "loot": 0, "gold": 0}
  118. def on_frame(mid, recog, p1, p2, p3, body):
  119. if mid in (51, 50, 1, 28):
  120. log(f" [pos] SC{mid} raw p1={p1} p2={p2} recog={recog} → me=({bot.me['x']},{bot.me['y']})")
  121. if mid not in (-1, 100):
  122. return
  123. try:
  124. if body[:2] == b"x\x9c":
  125. body = zlib.decompress(body)
  126. txt = decode6(body).decode("utf-8", "replace")
  127. except Exception:
  128. return
  129. if '"Index":50001' in txt and "makeindex" in txt:
  130. state["hoe"] = True
  131. elif "5分钟后" in txt:
  132. state["wait5"] = True
  133. elif "挖矿活动已结束" in txt:
  134. state["over"] = True
  135. elif "挖矿获得" in txt and rname in txt:
  136. state["loot"] += 1
  137. m = re.search(r"挖矿获得:.*?>([^<>]+)</font", txt)
  138. log(f" 🎁 产物: {m.group(1) if m else txt[:70]}")
  139. else:
  140. mg = re.search(r'"ID":1,"Value":(\d+)', txt)
  141. if mg and int(mg.group(1)) > state["gold"]:
  142. state["gold"] = int(mg.group(1))
  143. bot.raw_frame_hook = on_frame
  144. log("[6/7] 走到矿场总兵(17,23)旁 + 领锄头...")
  145. log(f" 视野内NPC: {[(v['aid'],v['name'],v['x'],v['y']) for v in bot.npcs.values()]}")
  146. # 目标 (17,24)=总兵(17,23)相邻可走格(0139.map 实证); M2 封速: 移动间隔须≥1.4s
  147. guard = 0
  148. while (abs(bot.me["x"] - 17) > 1 or abs(bot.me["y"] - 23) > 1) and guard < 60 and bot.alive:
  149. bot.step_toward(17, 24)
  150. evs = bot.pump(0.6)
  151. acks = [e for e in evs if e[0] == "pull_back"]
  152. time.sleep(1.1)
  153. guard += 1
  154. if guard % 10 == 0:
  155. log(f" 走步{guard}: me=({bot.me['x']},{bot.me['y']}) 拉回={len(acks)}")
  156. px, py = bot.me["x"], bot.me["y"]
  157. log(f" 现在站位 ({px},{py}) 距总兵(17,23) dx={abs(px-17)} dy={abs(py-23)}")
  158. got = False
  159. for att in range(10):
  160. state["hoe"] = state["wait5"] = state["over"] = False
  161. click_npc(bot, 10041, 43)
  162. time.sleep(1.2)
  163. bot.pump(0.6)
  164. send_qf(bot, "@func38_get_hzc")
  165. t1 = time.time()
  166. while time.time() - t1 < 4:
  167. bot.pump(0.3)
  168. if state["hoe"] or state["wait5"] or state["over"]:
  169. break
  170. if state["hoe"]:
  171. got = True
  172. log(" ✓ 鹤嘴锄已入包(Index:50001)")
  173. break
  174. if state["wait5"]:
  175. wait_s = 65 if att < 6 else 30
  176. log(f" ⏳ 未到开区5分钟, 等{wait_s}s重试...")
  177. time.sleep(wait_s)
  178. continue
  179. if state["over"]:
  180. log(" ✗ 挖矿活动已结束(20分钟窗口过)")
  181. break
  182. log(f" 尝试{att+1}无响应, 5s后重试...")
  183. time.sleep(5)
  184. if not got and not state["over"]:
  185. log(" [!] 未确认入包(可能已领过), 继续穿装备尝试")
  186. if state["over"]:
  187. g.close()
  188. raise SystemExit("[!] 20分钟窗口已过 — 等下一轮新区")
  189. log(" CS1003 穿锄头...")
  190. bot.send(1003, 0, 0, 0, bot.sid, b"")
  191. time.sleep(1)
  192. bot.pump(0.5)
  193. log("[7/7] 挖矿循环 (0.68s/锄)...")
  194. mx, my, d = px, py - 1, 0
  195. if abs(px - 24) <= 1 and abs(py - 30) <= 1:
  196. mx, my, d = 24, 29, 0
  197. log(f" 站({px},{py}) 挖({mx},{my}) dir={d}")
  198. if a.mine_until:
  199. hh, mm = a.mine_until.split(":")
  200. nw = time.localtime()
  201. dl = time.mktime((nw.tm_year, nw.tm_mon, nw.tm_mday, int(hh), int(mm), 0, 0, 0, -1))
  202. if dl < time.time():
  203. dl += 86400
  204. deadline = dl
  205. log(f" 挖矿至 {a.mine_until} ({int(deadline-time.time())}s)")
  206. else:
  207. deadline = time.time() + a.minutes * 60
  208. swings = 0
  209. while time.time() < deadline and bot.alive:
  210. mine_once(bot, px, py, d, mx, my)
  211. swings += 1
  212. bot.pump(0.6)
  213. time.sleep(0.08)
  214. log(f"[完成] 挥锄 {swings} 次 | 自身产物播报 {state['loot']} 条 | 金币累计 {state['gold']}")
  215. log(f" 收帧: {sorted(bot.frame_log.items(), key=lambda kv: -kv[1])[:12]}")
  216. g.close()
  217. if __name__ == "__main__":
  218. main()