| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- # -*- coding: utf-8 -*-
- """
- run_mine998.py —— T04/T05 定案协议的首次无客户端实机验证:
- 创角(最新区) → 开喝药 → 领锄头(NPC 10041/43 → @func38_get_hzc → CS1003 穿锄)
- → 原地挖矿(CS3014 skillID=999 @0.68s 面壁) → 统计产物播报/金币。
- 协议依据: protocol_bot/docs/协议定案-神通开启与五行之力.md 附录A/B。
- """
- import argparse
- import json
- import random
- import re
- import sys
- 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 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)
- def send_qf(bot, act):
- body = json.dumps({"UserID": "999999999", "Act": act,
- "index": 999999999}, separators=(",", ":")).encode()
- bot.send(1011, 0, 0, 0, bot.sid, body)
- def click_npc(bot, cfg_id, index):
- body = json.dumps({"UserID": str(cfg_id), "index": index},
- separators=(",", ":")).encode()
- bot.send(1010, 0, 0, 0, bot.sid, body)
- def mine_once(bot, px, py, d, mx, my):
- payload = {"skillID": 999, "dir": d, "srcX": px, "srcY": py,
- "destX": mx, "destY": my}
- bot.send(3014, 0, 0, 0, 0,
- json.dumps(payload, separators=(",", ":")).encode())
- def main():
- ap = argparse.ArgumentParser()
- ap.add_argument("-u", "--user", default="longwu998")
- ap.add_argument("-p", "--password", default="Longwu1988")
- ap.add_argument("--zone", type=int, required=True)
- ap.add_argument("--name", default=None)
- ap.add_argument("--minutes", type=float, default=5.0)
- ap.add_argument("--mine-until", default="",
- help="挖矿绝对截止时刻 HH:MM(本地时间); 优先于 --minutes")
- a = ap.parse_args()
- name = a.name or ("矿工" + str(random.randint(1000, 9999)))
- log("[1/7] HTTP登录...")
- s = rq.Session()
- j = None
- for att in range(5):
- try:
- j, _ = L.do_login(s, a.user, a.password)
- if j.get("code") == 200:
- break
- except Exception as e:
- log(f" 异常({att+1}): {str(e)[:60]}")
- time.sleep(10)
- if not j or j.get("code") != 200:
- raise SystemExit("[!] 登录失败")
- jwt = j["data"]["token"]
- uid = str((j["data"].get("userInfo") or {}).get("id") or P.UID)
- log(f"[2/7] 选区 {a.zone}...")
- j2 = None
- for att in range(5):
- try:
- j2 = P.enter_game(s, jwt, a.zone)
- if j2.get("code") == 200:
- break
- except Exception as e:
- log(f" 异常({att+1}): {str(e)[:60]}")
- time.sleep(10)
- if not j2 or j2.get("code") != 200:
- raise SystemExit("[!] enter_game 失败")
- log(f"[3/7] 登录服+创角/复用 {name}...")
- ok = None
- for pt in (8041, 8021, 8061, 8001, 8063, 8043):
- try:
- r = P.login_gate(a.zone, jwt, name, pt, uid)
- if r and r[0]:
- ok = r
- break
- except SystemExit as e:
- log(f" 端口{pt}: {e}")
- except Exception as e:
- log(f" 端口{pt}: {str(e)[:50]}")
- if not ok:
- raise SystemExit("[!] 所有端口失败")
- key32, sid, rname, tkey, gp = ok
- log(f" 角色={rname} 游戏端口={gp}")
- log("[4/7] 进世界...")
- g = enter_game_gate_v2(a.zone, rname, tkey, sid, gp, uid)
- bot = CombatBot(g, sid, rname)
- P.heartbeat_loop(g, sid, sender=bot.send)
- t0 = time.time()
- while time.time() - t0 < 10:
- bot.pump(0.3)
- if 658 in bot.frame_log:
- break
- send_init_frames(bot, uid)
- for _ in range(24):
- bot.pump(0.5)
- if bot.me["id"] and bot.me.get("mapname"):
- break
- if not bot.me["id"]:
- g.close()
- raise SystemExit("[!] 未收到 SC50")
- px, py = bot.me["x"], bot.me["y"]
- log(f" 主玩家 @({px},{py}) map={bot.me.get('mapname')}")
- log("[5/7] 开喝药...")
- st = P.enable_auto_heal(g, sid, want_on=True)
- log(f" 喝药={st}")
- time.sleep(1)
- bot.pump(0.5)
- state = {"hoe": False, "wait5": False, "over": False,
- "loot": 0, "gold": 0}
- def on_frame(mid, recog, p1, p2, p3, body):
- if mid in (51, 50, 1, 28):
- log(f" [pos] SC{mid} raw p1={p1} p2={p2} recog={recog} → me=({bot.me['x']},{bot.me['y']})")
- if mid not in (-1, 100):
- return
- try:
- if body[:2] == b"x\x9c":
- body = zlib.decompress(body)
- txt = decode6(body).decode("utf-8", "replace")
- except Exception:
- return
- if '"Index":50001' in txt and "makeindex" in txt:
- state["hoe"] = True
- elif "5分钟后" in txt:
- state["wait5"] = True
- elif "挖矿活动已结束" in txt:
- state["over"] = True
- elif "挖矿获得" in txt and rname in txt:
- state["loot"] += 1
- m = re.search(r"挖矿获得:.*?>([^<>]+)</font", txt)
- log(f" 🎁 产物: {m.group(1) if m else txt[:70]}")
- else:
- mg = re.search(r'"ID":1,"Value":(\d+)', txt)
- if mg and int(mg.group(1)) > state["gold"]:
- state["gold"] = int(mg.group(1))
- bot.raw_frame_hook = on_frame
- log("[6/7] 走到矿场总兵(17,23)旁 + 领锄头...")
- log(f" 视野内NPC: {[(v['aid'],v['name'],v['x'],v['y']) for v in bot.npcs.values()]}")
- # 目标 (17,24)=总兵(17,23)相邻可走格(0139.map 实证); M2 封速: 移动间隔须≥1.4s
- guard = 0
- while (abs(bot.me["x"] - 17) > 1 or abs(bot.me["y"] - 23) > 1) and guard < 60 and bot.alive:
- bot.step_toward(17, 24)
- evs = bot.pump(0.6)
- acks = [e for e in evs if e[0] == "pull_back"]
- time.sleep(1.1)
- guard += 1
- if guard % 10 == 0:
- log(f" 走步{guard}: me=({bot.me['x']},{bot.me['y']}) 拉回={len(acks)}")
- px, py = bot.me["x"], bot.me["y"]
- log(f" 现在站位 ({px},{py}) 距总兵(17,23) dx={abs(px-17)} dy={abs(py-23)}")
- got = False
- for att in range(10):
- state["hoe"] = state["wait5"] = state["over"] = False
- click_npc(bot, 10041, 43)
- time.sleep(1.2)
- bot.pump(0.6)
- send_qf(bot, "@func38_get_hzc")
- t1 = time.time()
- while time.time() - t1 < 4:
- bot.pump(0.3)
- if state["hoe"] or state["wait5"] or state["over"]:
- break
- if state["hoe"]:
- got = True
- log(" ✓ 鹤嘴锄已入包(Index:50001)")
- break
- if state["wait5"]:
- wait_s = 65 if att < 6 else 30
- log(f" ⏳ 未到开区5分钟, 等{wait_s}s重试...")
- time.sleep(wait_s)
- continue
- if state["over"]:
- log(" ✗ 挖矿活动已结束(20分钟窗口过)")
- break
- log(f" 尝试{att+1}无响应, 5s后重试...")
- time.sleep(5)
- if not got and not state["over"]:
- log(" [!] 未确认入包(可能已领过), 继续穿装备尝试")
- if state["over"]:
- g.close()
- raise SystemExit("[!] 20分钟窗口已过 — 等下一轮新区")
- log(" CS1003 穿锄头...")
- bot.send(1003, 0, 0, 0, bot.sid, b"")
- time.sleep(1)
- bot.pump(0.5)
- log("[7/7] 挖矿循环 (0.68s/锄)...")
- mx, my, d = px, py - 1, 0
- if abs(px - 24) <= 1 and abs(py - 30) <= 1:
- mx, my, d = 24, 29, 0
- log(f" 站({px},{py}) 挖({mx},{my}) dir={d}")
- if a.mine_until:
- hh, mm = a.mine_until.split(":")
- nw = time.localtime()
- dl = time.mktime((nw.tm_year, nw.tm_mon, nw.tm_mday, int(hh), int(mm), 0, 0, 0, -1))
- if dl < time.time():
- dl += 86400
- deadline = dl
- log(f" 挖矿至 {a.mine_until} ({int(deadline-time.time())}s)")
- else:
- deadline = time.time() + a.minutes * 60
- swings = 0
- while time.time() < deadline and bot.alive:
- mine_once(bot, px, py, d, mx, my)
- swings += 1
- bot.pump(0.6)
- time.sleep(0.08)
- log(f"[完成] 挥锄 {swings} 次 | 自身产物播报 {state['loot']} 条 | 金币累计 {state['gold']}")
- log(f" 收帧: {sorted(bot.frame_log.items(), key=lambda kv: -kv[1])[:12]}")
- g.close()
- if __name__ == "__main__":
- main()
|