| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- # -*- coding: utf-8 -*-
- """
- mir_map.py —— Mir2 .map 地图解析 + A* 寻路(供 bot 使用)。
- 格式(与文件尺寸互证): 52B头 + w*h*12B/格
- 头: u16 w, u16 h, 12B "Legend of mir..", 其余保留
- 每格 12B: u16 bk, u16 mid, u16 fr, u16 doorIdx, u16 doorOff, u16 aniFrame/aniTick/light
- 可走判定(Mir2 惯例, 与 C++ mapData2D::isObstacle 对应):
- (bk & 0x8000) or (mid != 0) → 障碍 [fr 高位为可站立掩码]
- doorIdx&0x8000 → 门/传送点
- """
- import heapq
- import struct
- import os
- MAP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
- "..", "996传奇盒子资源包", "03-游戏客户端",
- "jycq_206923", "scene", "map")
- class MirMap:
- def __init__(self, path):
- raw = open(path, "rb").read()
- self.w, self.h = struct.unpack_from("<HH", raw, 0)
- self.cells = raw[52:]
- self.name = os.path.basename(path)
- def in_bounds(self, x, y):
- return 0 <= x < self.w and 0 <= y < self.h
- def cell(self, x, y):
- off = (y * self.w + x) * 12
- bk, mid, fr, door, dooroff, ani = struct.unpack_from(
- "<6H", self.cells, off)
- return bk, mid, fr, door, dooroff, ani
- def walkable(self, x, y):
- if not self.in_bounds(x, y):
- return False
- bk, mid, fr, door, _, _ = self.cell(x, y)
- if bk & 0x8000:
- return False
- if mid != 0:
- return False
- return True
- def door(self, x, y):
- if not self.in_bounds(x, y):
- return 0
- return self.cell(x, y)[3]
- def doors(self):
- """全图门/传送格列表(仅开门状态的入口参考)。"""
- out = []
- for y in range(self.h):
- for x in range(self.w):
- d = self.cell(x, y)[3]
- if d & 0x8000: # Mir2: 0x8000 位标记门
- out.append((x, y, d & 0x7FFF))
- return out
- def find_path(self, sx, sy, tx, ty, max_expand=20000):
- """A*(与 PathFindAStar.lua 同估价: 直10斜14)。返回 [(x,y),...] 或 None。"""
- if not self.walkable(tx, ty):
- # 目标不可走 → 找最近可走邻格
- best = None
- for r in range(1, 6):
- for dx in range(-r, r + 1):
- for dy in range(-r, r + 1):
- nx, ny = tx + dx, ty + dy
- if self.walkable(nx, ny):
- d = abs(dx) + abs(dy)
- if best is None or d < best[0]:
- best = (d, nx, ny)
- if best:
- break
- if not best:
- return None
- tx, ty = best[1], best[2]
- openq = [(0.0, sx, sy)]
- came = {(sx, sy): None}
- cost = {(sx, sy): 0}
- expand = 0
- while openq and expand < max_expand:
- _, cx, cy = heapq.heappop(openq)
- if (cx, cy) == (tx, ty):
- path = []
- cur = (tx, ty)
- while cur:
- path.append(cur)
- cur = came[cur]
- return path[::-1]
- expand += 1
- for dx in (-1, 0, 1):
- for dy in (-1, 0, 1):
- if dx == 0 and dy == 0:
- continue
- nx, ny = cx + dx, cy + dy
- if not self.walkable(nx, ny):
- continue
- # 斜穿检查: 两相邻直格须可走
- if dx and dy and not (self.walkable(cx + dx, cy) and self.walkable(cx, cy + dy)):
- continue
- nc = cost[(cx, cy)] + (10 if (dx == 0 or dy == 0) else 14)
- if (nx, ny) not in cost or nc < cost[(nx, ny)]:
- cost[(nx, ny)] = nc
- came[(nx, ny)] = (cx, cy)
- pri = nc + (abs(tx - nx) + abs(ty - ny)) * 10
- heapq.heappush(openq, (pri, nx, ny))
- return None
- def load(map_name):
- """map_name: '0139' / '3' / '0' 等(地图文件主名)。"""
- p = os.path.join(MAP_DIR, f"{map_name}.map")
- return MirMap(p)
- if __name__ == "__main__":
- # 验证: 等待图 0139 (46x50), NPC(10,0) 玩家(32,0)
- m = load("0139")
- print(f"{m.name}: {m.w}x{m.h}")
- for pt in [(32, 0), (10, 0), (33, 0), (20, 10), (27, 2), (36, 2)]:
- print(f" {pt} walkable={m.walkable(*pt)} cell={m.cell(*pt)}")
- path = m.find_path(32, 0, 10, 0)
- print("path (32,0)→(10,0):", path[:8] if path else None,
- f"... len={len(path)}" if path else "")
- # 大图连通性测试: 比奇 3.map
- b = load("3")
- print(f"\n比奇 {b.name}: {b.w}x{b.h}")
- print(" (293,619) walkable:", b.walkable(293, 619))
- print(" (620,2048低16位=620? Y校验):", b.in_bounds(620, 619))
- doors = b.doors()
- print(f" doors: {len(doors)} 个, 前10: {doors[:10]}")
|