mir_map.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. # -*- coding: utf-8 -*-
  2. """
  3. mir_map.py —— Mir2 .map 地图解析 + A* 寻路(供 bot 使用)。
  4. 格式(与文件尺寸互证): 52B头 + w*h*12B/格
  5. 头: u16 w, u16 h, 12B "Legend of mir..", 其余保留
  6. 每格 12B: u16 bk, u16 mid, u16 fr, u16 doorIdx, u16 doorOff, u16 aniFrame/aniTick/light
  7. 可走判定(Mir2 惯例, 与 C++ mapData2D::isObstacle 对应):
  8. (bk & 0x8000) or (mid != 0) → 障碍 [fr 高位为可站立掩码]
  9. doorIdx&0x8000 → 门/传送点
  10. """
  11. import heapq
  12. import struct
  13. import os
  14. MAP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
  15. "..", "996传奇盒子资源包", "03-游戏客户端",
  16. "jycq_206923", "scene", "map")
  17. class MirMap:
  18. def __init__(self, path):
  19. raw = open(path, "rb").read()
  20. self.w, self.h = struct.unpack_from("<HH", raw, 0)
  21. self.cells = raw[52:]
  22. self.name = os.path.basename(path)
  23. def in_bounds(self, x, y):
  24. return 0 <= x < self.w and 0 <= y < self.h
  25. def cell(self, x, y):
  26. off = (y * self.w + x) * 12
  27. bk, mid, fr, door, dooroff, ani = struct.unpack_from(
  28. "<6H", self.cells, off)
  29. return bk, mid, fr, door, dooroff, ani
  30. def walkable(self, x, y):
  31. if not self.in_bounds(x, y):
  32. return False
  33. bk, mid, fr, door, _, _ = self.cell(x, y)
  34. if bk & 0x8000:
  35. return False
  36. if mid != 0:
  37. return False
  38. return True
  39. def door(self, x, y):
  40. if not self.in_bounds(x, y):
  41. return 0
  42. return self.cell(x, y)[3]
  43. def doors(self):
  44. """全图门/传送格列表(仅开门状态的入口参考)。"""
  45. out = []
  46. for y in range(self.h):
  47. for x in range(self.w):
  48. d = self.cell(x, y)[3]
  49. if d & 0x8000: # Mir2: 0x8000 位标记门
  50. out.append((x, y, d & 0x7FFF))
  51. return out
  52. def find_path(self, sx, sy, tx, ty, max_expand=20000):
  53. """A*(与 PathFindAStar.lua 同估价: 直10斜14)。返回 [(x,y),...] 或 None。"""
  54. if not self.walkable(tx, ty):
  55. # 目标不可走 → 找最近可走邻格
  56. best = None
  57. for r in range(1, 6):
  58. for dx in range(-r, r + 1):
  59. for dy in range(-r, r + 1):
  60. nx, ny = tx + dx, ty + dy
  61. if self.walkable(nx, ny):
  62. d = abs(dx) + abs(dy)
  63. if best is None or d < best[0]:
  64. best = (d, nx, ny)
  65. if best:
  66. break
  67. if not best:
  68. return None
  69. tx, ty = best[1], best[2]
  70. openq = [(0.0, sx, sy)]
  71. came = {(sx, sy): None}
  72. cost = {(sx, sy): 0}
  73. expand = 0
  74. while openq and expand < max_expand:
  75. _, cx, cy = heapq.heappop(openq)
  76. if (cx, cy) == (tx, ty):
  77. path = []
  78. cur = (tx, ty)
  79. while cur:
  80. path.append(cur)
  81. cur = came[cur]
  82. return path[::-1]
  83. expand += 1
  84. for dx in (-1, 0, 1):
  85. for dy in (-1, 0, 1):
  86. if dx == 0 and dy == 0:
  87. continue
  88. nx, ny = cx + dx, cy + dy
  89. if not self.walkable(nx, ny):
  90. continue
  91. # 斜穿检查: 两相邻直格须可走
  92. if dx and dy and not (self.walkable(cx + dx, cy) and self.walkable(cx, cy + dy)):
  93. continue
  94. nc = cost[(cx, cy)] + (10 if (dx == 0 or dy == 0) else 14)
  95. if (nx, ny) not in cost or nc < cost[(nx, ny)]:
  96. cost[(nx, ny)] = nc
  97. came[(nx, ny)] = (cx, cy)
  98. pri = nc + (abs(tx - nx) + abs(ty - ny)) * 10
  99. heapq.heappush(openq, (pri, nx, ny))
  100. return None
  101. def load(map_name):
  102. """map_name: '0139' / '3' / '0' 等(地图文件主名)。"""
  103. p = os.path.join(MAP_DIR, f"{map_name}.map")
  104. return MirMap(p)
  105. if __name__ == "__main__":
  106. # 验证: 等待图 0139 (46x50), NPC(10,0) 玩家(32,0)
  107. m = load("0139")
  108. print(f"{m.name}: {m.w}x{m.h}")
  109. for pt in [(32, 0), (10, 0), (33, 0), (20, 10), (27, 2), (36, 2)]:
  110. print(f" {pt} walkable={m.walkable(*pt)} cell={m.cell(*pt)}")
  111. path = m.find_path(32, 0, 10, 0)
  112. print("path (32,0)→(10,0):", path[:8] if path else None,
  113. f"... len={len(path)}" if path else "")
  114. # 大图连通性测试: 比奇 3.map
  115. b = load("3")
  116. print(f"\n比奇 {b.name}: {b.w}x{b.h}")
  117. print(" (293,619) walkable:", b.walkable(293, 619))
  118. print(" (620,2048低16位=620? Y校验):", b.in_bounds(620, 619))
  119. doors = b.doors()
  120. print(f" doors: {len(doors)} 个, 前10: {doors[:10]}")