完善客户端功能并同步差距文档
This commit is contained in:
+138
-2
@@ -11,9 +11,16 @@ extends Node
|
||||
|
||||
const RADIUS := 70.0
|
||||
const SCALE := 0.25 # 像素/米(越大越放大)
|
||||
# CPythonMiniMap 的 waypoint 夹边留边:c_fMiniMapWindowRadius = min(w,h)/2 - 9.0f
|
||||
# (m2dev PythonMiniMap.cpp:441)。信标越出此半径时,标记贴到圆周并改画方向箭头。
|
||||
const SIGNAL_RIM_INSET := 9.0
|
||||
const NPC_WARP_TYPE := 2 # NPCMark.type: 2 = 传送
|
||||
const MIN_SCALE := 0.10
|
||||
const MAX_SCALE := 1.25
|
||||
const OBSERVER_ADD := 0
|
||||
const OBSERVER_REMOVE := 1
|
||||
const OBSERVER_MOVE := 2
|
||||
const OBSERVER_INTERPOLATION_MS := 1000.0
|
||||
|
||||
var client: Node
|
||||
var player_getter: Callable
|
||||
@@ -21,6 +28,8 @@ var _root: Control
|
||||
var _view: Control
|
||||
var _ch_label: Label
|
||||
var _scale := SCALE
|
||||
var _signal_points: Array[Vector2] = []
|
||||
var _observer_tracks: Dictionary = {}
|
||||
|
||||
func setup(m2client: Node, parent: Node, get_player: Callable) -> void:
|
||||
client = m2client
|
||||
@@ -28,6 +37,11 @@ func setup(m2client: Node, parent: Node, get_player: Callable) -> void:
|
||||
_build(parent)
|
||||
if client and client.has_signal("channel_changed"):
|
||||
client.channel_changed.connect(func(c): _ch_label.text = "CH %d" % c)
|
||||
if client and client.has_signal("observer_event"):
|
||||
client.observer_event.connect(_on_observer_event)
|
||||
if client and client.has_signal("world_reset"):
|
||||
client.world_reset.connect(_clear_observers)
|
||||
_sync_observers_snapshot()
|
||||
|
||||
func _build(parent: Node) -> void:
|
||||
_root = Control.new()
|
||||
@@ -63,6 +77,105 @@ func set_scale(pixels_per_meter: float) -> void:
|
||||
func get_scale() -> float:
|
||||
return _scale
|
||||
|
||||
# CPythonMiniMap::AddSignalPoint stores quest-script waypoints separately from
|
||||
# server target markers. EventManager supplies absolute server centimetres.
|
||||
func add_signal_point(x: float, y: float) -> void:
|
||||
_signal_points.append(Vector2(x, y))
|
||||
if _view:
|
||||
_view.queue_redraw()
|
||||
|
||||
func clear_signal_points() -> void:
|
||||
_signal_points.clear()
|
||||
if _view:
|
||||
_view.queue_redraw()
|
||||
|
||||
func signal_point_count() -> int:
|
||||
return _signal_points.size()
|
||||
|
||||
# 1:1 复刻 m2dev CPythonMiniMap::Render 的 waypoint 夹边分支(PythonMiniMap.cpp:445-462):
|
||||
# fDistanceFromCenter = sqrt(dx*dx + dy*dy)
|
||||
# if fDistanceFromCenter >= radius:
|
||||
# fRadian = atan2f(dy, dx); dx = radius*cos; dy = radius*sin
|
||||
# 返回 {clamped, offset(相对圆心的像素偏移), angle(指向真实目标的弧度)}。
|
||||
static func signal_marker_geometry(delta_px: Vector2, rim_radius: float) -> Dictionary:
|
||||
var angle := atan2(delta_px.y, delta_px.x)
|
||||
if delta_px.length() >= rim_radius:
|
||||
return {
|
||||
"clamped": true,
|
||||
"offset": Vector2(rim_radius * cos(angle), rim_radius * sin(angle)),
|
||||
"angle": angle,
|
||||
}
|
||||
return {"clamped": false, "offset": delta_px, "angle": angle}
|
||||
|
||||
func _signal_offset(world: Vector3, origin: Vector3) -> Dictionary:
|
||||
var d := world - origin
|
||||
# 与 _to_map 相同的世界→图轴向:world +x = 东(右),world −z = 北(上)。
|
||||
var delta_px := Vector2(d.x, d.z) * _scale
|
||||
return signal_marker_geometry(delta_px, RADIUS - SIGNAL_RIM_INSET)
|
||||
|
||||
# CPythonMiniMap::AddObserver / MoveObserver / RemoveObserver keep observer
|
||||
# positions separate from ordinary character instances. The reference client
|
||||
# interpolates each move over one second, so a packet burst does not make the
|
||||
# purple observer marker teleport across the minimap.
|
||||
func _observer_track(pos: Vector3, now_ms: int) -> Dictionary:
|
||||
return {"cur": pos, "src": pos, "dst": pos, "started_ms": now_ms}
|
||||
|
||||
func _set_observer_track(vid: int, pos: Vector3) -> void:
|
||||
_observer_tracks[vid] = _observer_track(pos, Time.get_ticks_msec())
|
||||
|
||||
func _on_observer_event(kind: int, vid: int, pos: Vector2) -> void:
|
||||
var net_pos := Vector3(pos.x, 0.0, pos.y)
|
||||
match kind:
|
||||
OBSERVER_ADD:
|
||||
# AddObserver resets an existing observer to the packet position too.
|
||||
_set_observer_track(vid, net_pos)
|
||||
OBSERVER_REMOVE:
|
||||
_observer_tracks.erase(vid)
|
||||
OBSERVER_MOVE:
|
||||
# Match MoveObserver: a move without a prior add is ignored. The
|
||||
# snapshot sync below covers observers present before setup.
|
||||
if not _observer_tracks.has(vid):
|
||||
return
|
||||
var cur := _observer_position(vid, net_pos)
|
||||
var track: Dictionary = _observer_tracks[vid]
|
||||
track["src"] = cur
|
||||
track["dst"] = net_pos
|
||||
track["cur"] = cur
|
||||
track["started_ms"] = Time.get_ticks_msec()
|
||||
_observer_tracks[vid] = track
|
||||
|
||||
func _clear_observers() -> void:
|
||||
_observer_tracks.clear()
|
||||
|
||||
func _sync_observers_snapshot() -> void:
|
||||
if client == null or not client.has_method("get_observers"):
|
||||
return
|
||||
# This seeds observers that arrived before the minimap connected. Once an
|
||||
# observer_event stream exists, its transitions own the track's destination.
|
||||
var has_events := client.has_signal("observer_event")
|
||||
for o in client.get_observers():
|
||||
var vid := int(o.get("vid", 0))
|
||||
if vid == 0:
|
||||
continue
|
||||
var pos: Vector3 = o.get("pos", Vector3.ZERO)
|
||||
if not has_events:
|
||||
_set_observer_track(vid, pos)
|
||||
elif not _observer_tracks.has(vid):
|
||||
_set_observer_track(vid, pos)
|
||||
|
||||
func _observer_position(vid: int, fallback: Vector3 = Vector3.ZERO) -> Vector3:
|
||||
if not _observer_tracks.has(vid):
|
||||
_set_observer_track(vid, fallback)
|
||||
var track: Dictionary = _observer_tracks[vid]
|
||||
var src: Vector3 = track.get("src", fallback)
|
||||
var dst: Vector3 = track.get("dst", src)
|
||||
var started_ms := float(track.get("started_ms", Time.get_ticks_msec()))
|
||||
var f := clampf((float(Time.get_ticks_msec()) - started_ms) / OBSERVER_INTERPOLATION_MS, 0.0, 1.0)
|
||||
var cur := src.lerp(dst, f)
|
||||
track["cur"] = cur
|
||||
_observer_tracks[vid] = track
|
||||
return cur
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.pressed:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
@@ -83,6 +196,16 @@ func _to_map(world: Vector3, origin: Vector3) -> Vector2:
|
||||
p = p.normalized() * (RADIUS - 4.0)
|
||||
return Vector2(RADIUS, RADIUS) + p
|
||||
|
||||
# 圆周方向箭头(对应 __RenderTargetMark 的边缘指示;本工程无 .sub 资源,用三角形代替)。
|
||||
func _draw_signal_arrow(pos: Vector2, angle: float) -> void:
|
||||
var dir := Vector2(cos(angle), sin(angle))
|
||||
var perp := Vector2(-dir.y, dir.x)
|
||||
var tip := pos + dir * 7.0
|
||||
var base := pos - dir * 3.0
|
||||
_view.draw_colored_polygon(
|
||||
PackedVector2Array([tip, base + perp * 4.0, base - perp * 4.0]),
|
||||
Color(1.0, 0.6, 0.1, 0.95))
|
||||
|
||||
func _draw_map() -> void:
|
||||
var c := Vector2(RADIUS, RADIUS)
|
||||
_view.draw_circle(c, RADIUS, Color(0.05, 0.06, 0.08, 0.82))
|
||||
@@ -120,8 +243,11 @@ func _draw_map() -> void:
|
||||
|
||||
# 观战者:原版在小地图上显示独立的观察点(紫色菱形)。
|
||||
if client.has_method("get_observers"):
|
||||
_sync_observers_snapshot()
|
||||
for o in client.get_observers():
|
||||
var op := _to_map(MapCoord.to_world(o.get("pos", Vector3.ZERO)), origin)
|
||||
var vid := int(o.get("vid", 0))
|
||||
var observer_world := _observer_position(vid, o.get("pos", Vector3.ZERO))
|
||||
var op := _to_map(MapCoord.to_world(observer_world), origin)
|
||||
var diamond := PackedVector2Array([op + Vector2(0, -4), op + Vector2(4, 0),
|
||||
op + Vector2(0, 4), op + Vector2(-4, 0)])
|
||||
_view.draw_colored_polygon(diamond, Color(0.8, 0.35, 1.0, 0.95))
|
||||
@@ -142,7 +268,17 @@ func _draw_map() -> void:
|
||||
else Color(1.0, 0.75, 0.2, 0.85)
|
||||
_view.draw_rect(rect, col, false, 1.0)
|
||||
|
||||
# 任务标记:橙星(小方块代替)
|
||||
# 任务标记(EventManager quest signals,本地 waypoint,不是 GC_TARGET):
|
||||
# 圆内画橙星;越出小地图半径则贴到圆周画指向目标的方向箭头(世界箭头 / 屏幕边缘指示)。
|
||||
for signal_point in _signal_points:
|
||||
var signal_pos := MapCoord.to_world(Vector3(signal_point.x * 0.01, 0, -signal_point.y * 0.01))
|
||||
var geo := _signal_offset(signal_pos, origin)
|
||||
var smp: Vector2 = c + geo["offset"]
|
||||
if geo["clamped"]:
|
||||
_draw_signal_arrow(smp, geo["angle"])
|
||||
else:
|
||||
_view.draw_circle(smp, 5.0, Color(1.0, 0.75, 0.15, 0.95))
|
||||
_view.draw_arc(smp, 8.0, 0.0, TAU, 16, Color(1.0, 0.4, 0.1, 0.9), 1.5)
|
||||
if client.has_method("get_world_markers"):
|
||||
for m in client.get_world_markers():
|
||||
var mp := _to_map(MapCoord.to_world(m.get("pos", Vector3.ZERO)), origin)
|
||||
|
||||
Reference in New Issue
Block a user