87 lines
3.0 KiB
GDScript
87 lines
3.0 KiB
GDScript
# DungeonState —— GC_DUNGEON 目的地状态(40250/ClientVS22)。
|
||
#
|
||
# 参考端 PythonNetworkStreamPhaseGame.cpp:3875-3901 收到目的地后调用
|
||
# CPythonPlayer::SetDungeonDestinationPosition;PythonPlayer.cpp:147-167、
|
||
# :1255-1285 保存全局像素/厘米坐标、立即提示,并在接近目标后清除,未到达时
|
||
# 每 20 秒再次提示。本工程把目标状态与 UI 解耦,UI 只消费信号。
|
||
extends Node
|
||
|
||
signal destination_changed(active: bool, world_pos: Vector3)
|
||
signal destination_reached(world_pos: Vector3)
|
||
signal alarm_requested(world_pos: Vector3)
|
||
|
||
const TIME_ATTACK_START := 0
|
||
const DESTINATION_POSITION := 1
|
||
const ARRIVAL_THRESHOLD_CM := 10000.0
|
||
const ALARM_INTERVAL_SEC := 20.0
|
||
|
||
var client: Node
|
||
var player_getter: Callable
|
||
var active := false
|
||
var destination_cm := Vector2.ZERO
|
||
var destination_world := Vector3.ZERO
|
||
var last_subheader := -1
|
||
var alarm_count := 0
|
||
var _last_alarm_ms := -1
|
||
|
||
func setup(m2client: Node, get_player: Callable) -> void:
|
||
client = m2client
|
||
player_getter = get_player
|
||
if client and client.has_signal("dungeon_event"):
|
||
client.dungeon_event.connect(_on_dungeon_event)
|
||
if client and client.has_signal("world_reset"):
|
||
client.world_reset.connect(clear)
|
||
|
||
func _on_dungeon_event(subheader: int, x: int, y: int, has_destination: bool) -> void:
|
||
last_subheader = subheader
|
||
# ClientVS22's TIME_ATTACK_START branch is intentionally empty. Consume the
|
||
# packet but do not invent a local timer that the reference client does not run.
|
||
if subheader != DESTINATION_POSITION or not has_destination:
|
||
return
|
||
destination_cm = Vector2(float(x), float(y))
|
||
# GC_DUNGEON carries TPixelPosition x/y in global server centimetres. The
|
||
# network frame uses -Y for Godot Z before MapCoord applies the map base.
|
||
destination_world = MapCoord.to_world(Vector3(float(x) * 0.01, 0.0, -float(y) * 0.01))
|
||
active = true
|
||
_last_alarm_ms = -1
|
||
destination_changed.emit(true, destination_world)
|
||
_alarm()
|
||
|
||
func clear() -> void:
|
||
var was_active := active
|
||
active = false
|
||
destination_cm = Vector2.ZERO
|
||
destination_world = Vector3.ZERO
|
||
_last_alarm_ms = -1
|
||
if was_active:
|
||
destination_changed.emit(false, Vector3.ZERO)
|
||
|
||
func _player() -> Node3D:
|
||
if not player_getter.is_valid():
|
||
return null
|
||
var p: Variant = player_getter.call()
|
||
return p as Node3D
|
||
|
||
func _alarm() -> void:
|
||
_last_alarm_ms = Time.get_ticks_msec()
|
||
alarm_count += 1
|
||
alarm_requested.emit(destination_world)
|
||
|
||
func _process(_dt: float) -> void:
|
||
if not active:
|
||
return
|
||
var p := _player()
|
||
if p:
|
||
# Match PythonPlayer.cpp:155: integer Manhattan distance, not Euclidean.
|
||
var p_cm := MapCoord.to_server_cm(p.global_position)
|
||
var distance_cm := absf(p_cm.x - destination_cm.x) + absf(p_cm.y - destination_cm.y)
|
||
if distance_cm < ARRIVAL_THRESHOLD_CM:
|
||
var reached := destination_world
|
||
active = false
|
||
destination_changed.emit(false, reached)
|
||
destination_reached.emit(reached)
|
||
return
|
||
var now := Time.get_ticks_msec()
|
||
if _last_alarm_ms < 0 or now - _last_alarm_ms > int(ALARM_INTERVAL_SEC * 1000.0):
|
||
_alarm()
|