mine_bot.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # -*- coding: utf-8 -*-
  2. """
  3. mine_bot.py —— 无客户端挖矿链路: 创角 → 进世界 → 喝药 → NPC领锄头 → 挖矿。
  4. 协议要点(本轮实测/逆向):
  5. CS1010 NPC对话: body=JSON{"UserID":"<cfgID字符串>","index":"<typeIndex>"}
  6. — UserID 是 SC10 info[1] 的NPC配置ID字符串, 不是actorID数字
  7. CS3014 挖矿: recog=skillID(999=SKILL_INDEX_DIG), body=JSON{skillID,dir,srcX,srcY,destX,destY,targetID}
  8. CS1014 NPC购买: body=JSON{"UserID":npcCfgID,"Name":货品名,"MakeIndex":0}
  9. 用法:
  10. .venv/bin/python mine_bot.py --zone 14492165 [--name 名字] [--minutes 6]
  11. """
  12. import argparse
  13. import json
  14. import random
  15. import socket
  16. import struct
  17. import sys
  18. import threading
  19. import time
  20. import zlib
  21. import requests as rq
  22. import urllib3
  23. urllib3.disable_warnings()
  24. HERE = __file__.rsplit("/", 1)[0]
  25. sys.path.insert(0, HERE)
  26. from encode6bitbuf_final import encode6, decode6 # noqa: E402
  27. import login_996pc as L # noqa: E402
  28. import cq2_pure_enter as P # noqa: E402
  29. from combat_bot import (CombatBot, enter_game_gate_v2, # noqa: E402
  30. send_init_frames, log, DIRS, dir_to)
  31. SKILL_DIG = 999
  32. MINING_MONSTER_RACE = 304 # ACTOR_RACE? 待实测修正: 矿石作为 type=50/race=304 采集物
  33. def npc_talk(bot, npc_cfg_id, index, timeout=6):
  34. body = json.dumps({"UserID": str(npc_cfg_id), "index": str(index)},
  35. separators=(",", ":")).encode()
  36. bot.send(1010, 0, 0, 0, bot.sid, body)
  37. deadline = time.time() + timeout
  38. while time.time() < deadline and bot.alive:
  39. for e in bot.pump(0.3):
  40. if e[0] == "npc_talk":
  41. return e[1]
  42. return None
  43. def npc_store_buy(bot, npc_cfg_id, item_name):
  44. body = json.dumps({"UserID": str(npc_cfg_id), "Name": item_name,
  45. "MakeIndex": 0}, separators=(",", ":")).encode()
  46. bot.send(1014, 0, 0, 0, bot.sid, body)
  47. def npc_task_click(bot, npc_cfg_id, act, index, common_input="", input_id=""):
  48. payload = {"UserID": str(npc_cfg_id), "Act": act,
  49. "commonInput": common_input, "inputID": input_id,
  50. "index": str(index)}
  51. body = json.dumps(payload, separators=(",", ":")).encode()
  52. bot.send(1011, 0, 0, 0, bot.sid, body)
  53. def mine(bot, tx, ty):
  54. d = dir_to(bot.me["x"], bot.me["y"], tx, ty)
  55. payload = {"skillID": SKILL_DIG, "dir": d,
  56. "srcX": bot.me["x"], "srcY": bot.me["y"],
  57. "destX": tx, "destY": ty, "targetID": None}
  58. body = json.dumps(payload, separators=(",", ":")).encode()
  59. bot.send(3014, 0, 0, 0, 0, body)
  60. def setup():
  61. ap = argparse.ArgumentParser()
  62. ap.add_argument("-u", "--user", default="longwu988")
  63. ap.add_argument("-p", "--password", default="Longwu1988")
  64. ap.add_argument("--zone", type=int, default=14492165)
  65. ap.add_argument("--name", default=None)
  66. ap.add_argument("--port", type=int, default=0)
  67. ap.add_argument("--minutes", type=float, default=6)
  68. return ap.parse_args()
  69. def http_login_with_retry(args):
  70. for attempt in range(5):
  71. try:
  72. s = rq.Session()
  73. j, _ = L.do_login(s, args.user, args.password)
  74. if j.get("code") == 200:
  75. return s, j
  76. except Exception as e:
  77. log(f" 登录异常(第{attempt+1}次): {str(e)[:60]}")
  78. time.sleep(12)
  79. raise SystemExit("[!] 登录失败")
  80. def enter_game_with_retry(s, jwt, zone):
  81. for attempt in range(5):
  82. try:
  83. j2 = P.enter_game(s, jwt, zone)
  84. if j2.get("code") == 200:
  85. return
  86. except Exception as e:
  87. log(f" enter_game 异常(第{attempt+1}次): {str(e)[:60]}")
  88. time.sleep(12)
  89. raise SystemExit("[!] enter_game 失败")
  90. def login_gate_with_role(zone, jwt, name, uid):
  91. ports = [8041, 8021, 8061, 8001]
  92. for pt in ports:
  93. try:
  94. key32, sid, rname, tkey, gp = P.login_gate(zone, jwt, name, pt, uid)
  95. if key32:
  96. return key32, sid, rname, tkey, gp
  97. except SystemExit as e:
  98. log(f" 端口{pt}: {e}")
  99. except Exception as e:
  100. log(f" 端口{pt}: {str(e)[:50]}")
  101. raise SystemExit("[!] 所有端口失败")
  102. def main():
  103. args = setup()
  104. name = args.name or ("矿工" + str(random.randint(1000, 9999)))
  105. log(f"[1/6] HTTP登录...")
  106. s, j = http_login_with_retry(args)
  107. jwt = j["data"]["token"]
  108. uid = str(j["data"].get("userInfo", {}).get("id") or "")
  109. log(f"[2/6] 选区 {args.zone}...")
  110. enter_game_with_retry(s, jwt, args.zone)
  111. log(f"[3/6] 登录服+创角/复用 {name}...")
  112. key32, sid, rname, tkey, gp = login_gate_with_role(args.zone, jwt, name, uid)
  113. log(f" 角色={rname} 游戏端口={gp}")
  114. log("[4/6] 进世界...")
  115. g = enter_game_gate_v2(args.zone, rname, tkey, sid, gp, uid)
  116. bot = CombatBot(g, sid, rname)
  117. P.heartbeat_loop(g, sid, bot.lock)
  118. ok658 = False
  119. t0 = time.time()
  120. while time.time() - t0 < 10:
  121. bot.pump(0.3)
  122. if 658 in bot.frame_log:
  123. ok658 = True
  124. break
  125. log(f" SC658 {'✓' if ok658 else '✗'}")
  126. send_init_frames(bot, uid)
  127. for _ in range(20):
  128. bot.pump(0.5)
  129. if bot.me["id"] and bot.me.get("mapname"):
  130. break
  131. log(f" 主玩家 #{bot.me['id']} @({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')}")
  132. if not bot.me["id"]:
  133. g.close()
  134. raise SystemExit("[!] 未收到 SC50")
  135. log("[5/6] 开喝药 + 收集NPC列表...")
  136. # NPC 收集: SC10 type != 50 的进视野项
  137. npcs = {}
  138. for e in bot.pump(2):
  139. if e[0] == "npc_in":
  140. npcs[e[1]] = e[2]
  141. if not npcs:
  142. # SC10 已在进世界时消费, 重扫视野: 用 _dispatch 已丢原始信息, 这里从 bot.frame_log 无法还原
  143. log(f" (视野内NPC: 等待图应含 矿场总兵/新手接待; frame_log={sorted(bot.frame_log.items())[:10]})")
  144. state = P.enable_auto_heal(g, sid, want_on=True)
  145. log(f" 喝药={state}")
  146. log("[6/6] 尝试NPC对话(矿场总兵)...")
  147. # 矿场总兵 cfgID=10041 (SC10 info[1]), typeIndex=p1; 新手接待=10032/10033
  148. for cfg_id, idx in [(10041, 10041), (10032, 10032), (10033, 10033)]:
  149. log(f" 对话 NPC {cfg_id}...")
  150. resp = npc_talk(bot, cfg_id, idx)
  151. if resp:
  152. log(f" ✓ NPC {cfg_id} 回应: {json.dumps(resp, ensure_ascii=False)[:300]}")
  153. break
  154. log(f" ✗ NPC {cfg_id} 无回应")
  155. else:
  156. log(" 所有NPC均无回应, 保存现场帧继续等...")
  157. bot.pump(3)
  158. log(f"[等待] 挂机 {args.minutes} 分钟收帧观察...")
  159. deadline = time.time() + args.minutes * 60
  160. report_n = 0
  161. while time.time() < deadline and bot.alive:
  162. evs = bot.pump(0.5)
  163. for e in evs:
  164. if e[0] == "map":
  165. log(f" !! 地图切换: {e}")
  166. report_n += 1
  167. if report_n > 3:
  168. break
  169. log(f"[完成] 收帧统计: {sorted(bot.frame_log.items(), key=lambda kv: -kv[1])[:15]}")
  170. g.close()
  171. if __name__ == "__main__":
  172. main()