150 lines
4.3 KiB
GDScript
150 lines
4.3 KiB
GDScript
# 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)
|