| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- # -*- coding: utf-8 -*-
- """
- mine_bot.py —— 无客户端挖矿链路: 创角 → 进世界 → 喝药 → NPC领锄头 → 挖矿。
- 协议要点(本轮实测/逆向):
- CS1010 NPC对话: body=JSON{"UserID":"<cfgID字符串>","index":"<typeIndex>"}
- — UserID 是 SC10 info[1] 的NPC配置ID字符串, 不是actorID数字
- CS3014 挖矿: recog=skillID(999=SKILL_INDEX_DIG), body=JSON{skillID,dir,srcX,srcY,destX,destY,targetID}
- CS1014 NPC购买: body=JSON{"UserID":npcCfgID,"Name":货品名,"MakeIndex":0}
- 用法:
- .venv/bin/python mine_bot.py --zone 14492165 [--name 名字] [--minutes 6]
- """
- import argparse
- import json
- import random
- import socket
- import struct
- import sys
- import threading
- import time
- import zlib
- import requests as rq
- import urllib3
- urllib3.disable_warnings()
- HERE = __file__.rsplit("/", 1)[0]
- sys.path.insert(0, HERE)
- from encode6bitbuf_final import encode6, decode6 # noqa: E402
- import login_996pc as L # noqa: E402
- import cq2_pure_enter as P # noqa: E402
- from combat_bot import (CombatBot, enter_game_gate_v2, # noqa: E402
- send_init_frames, log, DIRS, dir_to)
- SKILL_DIG = 999
- MINING_MONSTER_RACE = 304 # ACTOR_RACE? 待实测修正: 矿石作为 type=50/race=304 采集物
- def npc_talk(bot, npc_cfg_id, index, timeout=6):
- body = json.dumps({"UserID": str(npc_cfg_id), "index": str(index)},
- separators=(",", ":")).encode()
- bot.send(1010, 0, 0, 0, bot.sid, body)
- deadline = time.time() + timeout
- while time.time() < deadline and bot.alive:
- for e in bot.pump(0.3):
- if e[0] == "npc_talk":
- return e[1]
- return None
- def npc_store_buy(bot, npc_cfg_id, item_name):
- body = json.dumps({"UserID": str(npc_cfg_id), "Name": item_name,
- "MakeIndex": 0}, separators=(",", ":")).encode()
- bot.send(1014, 0, 0, 0, bot.sid, body)
- def npc_task_click(bot, npc_cfg_id, act, index, common_input="", input_id=""):
- payload = {"UserID": str(npc_cfg_id), "Act": act,
- "commonInput": common_input, "inputID": input_id,
- "index": str(index)}
- body = json.dumps(payload, separators=(",", ":")).encode()
- bot.send(1011, 0, 0, 0, bot.sid, body)
- def mine(bot, tx, ty):
- d = dir_to(bot.me["x"], bot.me["y"], tx, ty)
- payload = {"skillID": SKILL_DIG, "dir": d,
- "srcX": bot.me["x"], "srcY": bot.me["y"],
- "destX": tx, "destY": ty, "targetID": None}
- body = json.dumps(payload, separators=(",", ":")).encode()
- bot.send(3014, 0, 0, 0, 0, body)
- def setup():
- ap = argparse.ArgumentParser()
- ap.add_argument("-u", "--user", default="longwu988")
- ap.add_argument("-p", "--password", default="Longwu1988")
- ap.add_argument("--zone", type=int, default=14492165)
- ap.add_argument("--name", default=None)
- ap.add_argument("--port", type=int, default=0)
- ap.add_argument("--minutes", type=float, default=6)
- return ap.parse_args()
- def http_login_with_retry(args):
- for attempt in range(5):
- try:
- s = rq.Session()
- j, _ = L.do_login(s, args.user, args.password)
- if j.get("code") == 200:
- return s, j
- except Exception as e:
- log(f" 登录异常(第{attempt+1}次): {str(e)[:60]}")
- time.sleep(12)
- raise SystemExit("[!] 登录失败")
- def enter_game_with_retry(s, jwt, zone):
- for attempt in range(5):
- try:
- j2 = P.enter_game(s, jwt, zone)
- if j2.get("code") == 200:
- return
- except Exception as e:
- log(f" enter_game 异常(第{attempt+1}次): {str(e)[:60]}")
- time.sleep(12)
- raise SystemExit("[!] enter_game 失败")
- def login_gate_with_role(zone, jwt, name, uid):
- ports = [8041, 8021, 8061, 8001]
- for pt in ports:
- try:
- key32, sid, rname, tkey, gp = P.login_gate(zone, jwt, name, pt, uid)
- if key32:
- return key32, sid, rname, tkey, gp
- except SystemExit as e:
- log(f" 端口{pt}: {e}")
- except Exception as e:
- log(f" 端口{pt}: {str(e)[:50]}")
- raise SystemExit("[!] 所有端口失败")
- def main():
- args = setup()
- name = args.name or ("矿工" + str(random.randint(1000, 9999)))
- log(f"[1/6] HTTP登录...")
- s, j = http_login_with_retry(args)
- jwt = j["data"]["token"]
- uid = str(j["data"].get("userInfo", {}).get("id") or "")
- log(f"[2/6] 选区 {args.zone}...")
- enter_game_with_retry(s, jwt, args.zone)
- log(f"[3/6] 登录服+创角/复用 {name}...")
- key32, sid, rname, tkey, gp = login_gate_with_role(args.zone, jwt, name, uid)
- log(f" 角色={rname} 游戏端口={gp}")
- log("[4/6] 进世界...")
- g = enter_game_gate_v2(args.zone, rname, tkey, sid, gp, uid)
- bot = CombatBot(g, sid, rname)
- P.heartbeat_loop(g, sid, bot.lock)
- ok658 = False
- t0 = time.time()
- while time.time() - t0 < 10:
- bot.pump(0.3)
- if 658 in bot.frame_log:
- ok658 = True
- break
- log(f" SC658 {'✓' if ok658 else '✗'}")
- send_init_frames(bot, uid)
- for _ in range(20):
- bot.pump(0.5)
- if bot.me["id"] and bot.me.get("mapname"):
- break
- log(f" 主玩家 #{bot.me['id']} @({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')}")
- if not bot.me["id"]:
- g.close()
- raise SystemExit("[!] 未收到 SC50")
- log("[5/6] 开喝药 + 收集NPC列表...")
- # NPC 收集: SC10 type != 50 的进视野项
- npcs = {}
- for e in bot.pump(2):
- if e[0] == "npc_in":
- npcs[e[1]] = e[2]
- if not npcs:
- # SC10 已在进世界时消费, 重扫视野: 用 _dispatch 已丢原始信息, 这里从 bot.frame_log 无法还原
- log(f" (视野内NPC: 等待图应含 矿场总兵/新手接待; frame_log={sorted(bot.frame_log.items())[:10]})")
- state = P.enable_auto_heal(g, sid, want_on=True)
- log(f" 喝药={state}")
- log("[6/6] 尝试NPC对话(矿场总兵)...")
- # 矿场总兵 cfgID=10041 (SC10 info[1]), typeIndex=p1; 新手接待=10032/10033
- for cfg_id, idx in [(10041, 10041), (10032, 10032), (10033, 10033)]:
- log(f" 对话 NPC {cfg_id}...")
- resp = npc_talk(bot, cfg_id, idx)
- if resp:
- log(f" ✓ NPC {cfg_id} 回应: {json.dumps(resp, ensure_ascii=False)[:300]}")
- break
- log(f" ✗ NPC {cfg_id} 无回应")
- else:
- log(" 所有NPC均无回应, 保存现场帧继续等...")
- bot.pump(3)
- log(f"[等待] 挂机 {args.minutes} 分钟收帧观察...")
- deadline = time.time() + args.minutes * 60
- report_n = 0
- while time.time() < deadline and bot.alive:
- evs = bot.pump(0.5)
- for e in evs:
- if e[0] == "map":
- log(f" !! 地图切换: {e}")
- report_n += 1
- if report_n > 3:
- break
- log(f"[完成] 收帧统计: {sorted(bot.frame_log.items(), key=lambda kv: -kv[1])[:15]}")
- g.close()
- if __name__ == "__main__":
- main()
|