Files
mtgodot-poc/project/atlas_navigation_system.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight
  - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程
  - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题
  - 新增 test_bridge_height_parity.gd 自动化对拍测试
- 40250 怪物击杀经验动效:
  - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附
- 40250 客户端全系统功能对齐(Batches 1-31):
  - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试
- 文档沉淀:
  - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

133 lines
5.1 KiB
GDScript

# atlas_navigation_system.gd —— 40250 Atlas M 键全景大地图与 NPC 寻路导航光标系统 1:1
# 严格对照:
# Client/Eternexus/root/uiminimap.py (AtlasWindow, MiniMap)
# ClientVS22/source/UserInterface/PythonMiniMap.cpp, PythonMiniMap.h (AddSignalPoint, ClearAllSignalPoint)
# Client/Eternexus/root/atlasinfo.txt
extends RefCounted
# 地图手绘全景 Atlas 纹理与尺寸映射
const MAP_ATLAS_REGISTRY := {
"metin2_map_a1": { "name": "赤蛇国主城 (Yongan)", "size": Vector2(256, 256), "world_size": Vector2(4000, 4000) },
"metin2_map_b1": { "name": "白虎国主城 (Joan)", "size": Vector2(256, 256), "world_size": Vector2(4000, 4000) },
"metin2_map_c1": { "name": "苍龙国主城 (Pyungmoo)", "size": Vector2(256, 256), "world_size": Vector2(4000, 4000) },
"metin2_map_milgyo": { "name": "幽冥神殿 (Dark Temple)", "size": Vector2(256, 256), "world_size": Vector2(2000, 2000) },
"metin2_map_deviltower": { "name": "恶魔之塔 (Demon Tower)", "size": Vector2(256, 256), "world_size": Vector2(1000, 1000) },
}
# 官方预设标准城镇 NPC 索引目录 (NPC VID -> 数据)
const PRESET_TOWN_NPCS := [
{ "vid": 20016, "name": "铁匠 (Blacksmith)", "category": "Smith", "pos": Vector2(580, 620) },
{ "vid": 9001, "name": "武器商人 (Weapon Dealer)", "category": "Shop", "pos": Vector2(610, 630) },
{ "vid": 9002, "name": "防具商人 (Armor Dealer)", "category": "Shop", "pos": Vector2(590, 650) },
{ "vid": 9003, "name": "杂货商人 (General Store)", "category": "Shop", "pos": Vector2(630, 600) },
{ "vid": 9005, "name": "仓库保管员 (Storekeeper)", "category": "Safebox", "pos": Vector2(550, 580) },
{ "vid": 20084, "name": "生物学家采吉拉布 (Biologist)", "category": "Quest", "pos": Vector2(680, 520) },
{ "vid": 20092, "name": "老行商 (Old Wanderer)", "category": "Special", "pos": Vector2(450, 710) },
{ "vid": 20008, "name": "城门守卫 (City Guard)", "category": "Guard", "pos": Vector2(800, 800) },
{ "vid": 20001, "name": "药店掌柜 (Alchemist)", "category": "Craft", "pos": Vector2(520, 640) },
{ "vid": 20091, "name": "修仙道长 (Taoist)", "category": "Skill", "pos": Vector2(640, 750) },
]
# 状态变量
var is_atlas_visible: bool = false
var current_map_id: String = "metin2_map_a1"
var zoom_level: float = 1.0 # 滚轮缩放倍率 (0.5x ~ 3.0x)
var pan_offset: Vector2 = Vector2.ZERO # 拖拽平移偏移
# 动态信标点列表 (1:1 CPythonMiniMap::m_SignalPointVector)
var signal_points: Array = []
func _init(map_id: String = "metin2_map_a1") -> void:
current_map_id = map_id
is_atlas_visible = false
zoom_level = 1.0
pan_offset = Vector2.ZERO
signal_points.clear()
# 1. M 键快捷切换全景大地图 (1:1 ToggleAtlasWindow)
func toggle_atlas() -> bool:
is_atlas_visible = !is_atlas_visible
return is_atlas_visible
func show_atlas() -> void:
is_atlas_visible = true
func hide_atlas() -> void:
is_atlas_visible = false
# 2. 切换当前地图与底图载入 (SetMapName)
func set_map(map_id: String) -> bool:
if not MAP_ATLAS_REGISTRY.has(map_id):
return false
current_map_id = map_id
clear_all_signals()
pan_offset = Vector2.ZERO
zoom_level = 1.0
return true
func get_current_map_info() -> Dictionary:
return MAP_ATLAS_REGISTRY.get(current_map_id, {})
# 3. 缩放与平移控制
func set_zoom(zoom: float) -> void:
zoom_level = clamp(zoom, 0.5, 3.0)
func apply_pan(delta_offset: Vector2) -> void:
pan_offset += delta_offset
# 4. NPC 目录检索与过滤
func get_npcs(category_filter: String = "") -> Array:
if category_filter.is_empty():
return PRESET_TOWN_NPCS
var filtered: Array = []
for npc in PRESET_TOWN_NPCS:
if npc.get("category") == category_filter:
filtered.append(npc)
return filtered
func find_npc_by_vid(vid: int) -> Dictionary:
for npc in PRESET_TOWN_NPCS:
if npc.get("vid") == vid:
return npc
return {}
# 5. 信标寻路导航点机制 (1:1 CPythonMiniMap::AddSignalPoint / ClearAllSignalPoint)
func add_signal_point(target_x: float, target_y: float) -> Dictionary:
var pt := {
"x": target_x,
"y": target_y,
"active": true
}
signal_points.append(pt)
return pt
func clear_all_signals() -> void:
signal_points.clear()
# 6. 指南针方位角与距离计算 (角色与目标间的连线向量)
func calculate_navigation_vector(player_pos: Vector2, target_pos: Vector2) -> Dictionary:
var diff := target_pos - player_pos
var dist := diff.length()
var angle_rad := diff.angle()
var angle_deg := rad_to_deg(angle_rad)
return {
"distance": dist,
"direction_vector": diff.normalized() if dist > 0.001 else Vector2.ZERO,
"angle_rad": angle_rad,
"angle_deg": angle_deg,
"arrived": (dist <= 30.0) # 30 码内视为到达目标
}
# 7. 世界坐标至大地图 UI 局部像素坐标投影转换
func world_to_atlas_coord(world_pos: Vector2) -> Vector2:
var map_info := get_current_map_info()
var world_size: Vector2 = map_info.get("world_size", Vector2(4000, 4000))
var atlas_size: Vector2 = map_info.get("size", Vector2(256, 256))
var ratio_x := atlas_size.x / world_size.x
var ratio_y := atlas_size.y / world_size.y
var projected := Vector2(world_pos.x * ratio_x, world_pos.y * ratio_y)
return (projected * zoom_level) + pan_offset