# ChannelStatus (P10 收尾) —— 频道负载查询(对齐 CServerStateChecker)。 # # var cs := preload("res://net/channel_status.gd").new() # add_child(cs) # cs.query(host, port, func(map): ...) # map: { port:int -> status:int } # # 明文 TCP(无 libsodium):m2dev 发 CG_STATE_CHECKER(0x000F,len 4),40250 classic # 发单字节 CG_STATE_CHECKER(206);服务端均返回频道条目。classic 响应是 # [210][int32 count][count × {i16 port, u8 status}],m2dev 则是带 u16 header/length 的包。 # status:0 关 / 1 正常 / 2 忙 / 3 满(Metin2 约定,非在线人数——那要改服务器)。 extends Node const CG_STATE_CHECKER := 0x000F const GC_RESPOND_CHANNELSTATUS := 0x0010 const CLASSIC_CG_STATE_CHECKER := 206 const CLASSIC_GC_RESPOND_CHANNELSTATUS := 210 const STATUS_TEXT := {0: "关闭", 1: "正常", 2: "拥挤", 3: "爆满"} signal done(status_by_port: Dictionary) var _peer: StreamPeerTCP var _cb: Callable var _deadline := 0.0 var _sent := false var _classic := false var _classic_rx := PackedByteArray() func query(host: String, port: int, callback: Callable = Callable(), timeout_s := 3.0) -> void: _cb = callback _peer = StreamPeerTCP.new() _sent = false _classic = OS.get_environment("MT_PROTOCOL") == "classic" _classic_rx = PackedByteArray() _deadline = _now() + timeout_s if _peer.connect_to_host(host, port) != OK: _finish({}) return set_process(true) func text_for(status: int) -> String: return STATUS_TEXT.get(status, "?") func _now() -> float: return Time.get_ticks_msec() / 1000.0 func _process(_dt: float) -> void: if _peer == null: return _peer.poll() var st := _peer.get_status() if st == StreamPeerTCP.STATUS_ERROR or _now() > _deadline: _finish({}) return if st != StreamPeerTCP.STATUS_CONNECTED: return if not _sent: var req := PackedByteArray([CLASSIC_CG_STATE_CHECKER]) if _classic else PackedByteArray() if not _classic: req.resize(4) req.encode_u16(0, CG_STATE_CHECKER) req.encode_u16(2, 4) _peer.put_data(req) _sent = true # 攒够一个包就解析 if _classic: _try_parse_classic() else: _try_parse() func _try_parse_classic() -> void: var avail := _peer.get_available_bytes() if avail > 0: var r: Array = _peer.get_data(avail) if r[0] != OK: _finish({}) return _classic_rx.append_array(r[1]) # The stock checker reads a byte header, then an int32 count. A regular # game handshake may arrive while waiting, so discard known fixed packets. while not _classic_rx.is_empty() and _classic_rx[0] != CLASSIC_GC_RESPOND_CHANNELSTATUS: var skip := 1 match int(_classic_rx[0]): 0xFE: skip = 13 # GC_HANDSHAKE 0xFC: skip = 1 # GC_TIME_SYNC if _classic_rx.size() < skip: return _classic_rx = _classic_rx.slice(skip) if _classic_rx.size() < 5: return var count := _classic_rx.decode_s32(1) if count < 0 or count > 4096: _finish({}) return var total := 5 + count * 3 if _classic_rx.size() < total: return var out := {} var off := 5 for i in count: out[_classic_rx.decode_s16(off)] = _classic_rx.decode_u8(off + 2) off += 3 _finish(out) func _try_parse() -> void: var avail := _peer.get_available_bytes() if avail < 4: return # 逐包扫,跳到 RESPOND_CHANNELSTATUS while _peer.get_available_bytes() >= 4: var head: Array = _peer.get_partial_data(4) if head[0] != OK: return var hb: PackedByteArray = head[1] var header := hb.decode_u16(0) var length := hb.decode_u16(2) if header == GC_RESPOND_CHANNELSTATUS: _read_body() return # 别的包:按 length 跳过剩余(已吃掉 4 字节头) var rest := maxi(0, length - 4) if rest > 0: if _peer.get_available_bytes() < rest: return # 等更多数据(简化:不缓存半包,靠 timeout 兜底) _peer.get_data(rest) func _read_body() -> void: # 已消费 4 字节头;接着 int32 count + count × {i16 port, u8 status} if _peer.get_available_bytes() < 4: return var cnt_r: Array = _peer.get_data(4) var count: int = (cnt_r[1] as PackedByteArray).decode_s32(0) var out := {} for i in count: if _peer.get_available_bytes() < 3: break var rec: PackedByteArray = _peer.get_data(3)[1] out[rec.decode_s16(0)] = rec.decode_u8(2) _finish(out) func _finish(m: Dictionary) -> void: set_process(false) if _peer: _peer.disconnect_from_host() _peer = null done.emit(m) if _cb.is_valid(): _cb.call(m)