#!/usr/bin/env python3 """Generate mods/worldmap-reveal-data.lua from the client's world map DBCs. The Reveal World Map module needs the overlays of every zone, but the game only reports the ones a character has already explored. This script reads the complete list from DBFilesClient\\WorldMapArea.dbc and DBFilesClient\\WorldMapOverlay.dbc and writes it as a Lua table. Extract both files from the highest-priority client MPQ that contains them (any MPQ tool will do), then run: python gen-worldmap-reveal-data.py WorldMapArea.dbc WorldMapOverlay.dbc [out.lua] The output defaults to ../mods/worldmap-reveal-data.lua. Run it again whenever a client patch ships a new WorldMapOverlay.dbc. """ import hashlib import struct import sys from pathlib import Path # 1.12 layouts: WorldMapArea = id, map, area, name, 4 bounds (8 fields); # WorldMapOverlay = id, mapArea, area[4], mapPointX, mapPointY, textureName, # width, height, offsetX, offsetY, hitRect[4] (17 fields) AREA_FIELDS, AREA_NAME = 8, 3 OVERLAY_FIELDS = 17 OV_AREA, OV_TEXTURE, OV_WIDTH, OV_HEIGHT, OV_X, OV_Y = 1, 8, 9, 10, 11, 12 def read_dbc(path, fields): raw = Path(path).read_bytes() magic, count, nfields, size, _ = struct.unpack_from("<4s4I", raw, 0) if magic != b"WDBC" or nfields != fields or size != fields * 4: sys.exit(f"{path}: expected a WDBC file with {fields} fields, got {magic!r} {nfields} fields") strings = raw[20 + count * size:] def string(offset): return strings[offset:strings.index(b"\0", offset)].decode("utf-8") rows = [struct.unpack_from(f"<{fields}I", raw, 20 + i * size) for i in range(count)] return rows, string, hashlib.sha1(raw).hexdigest() def lua_string(text): return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"' def main(): if len(sys.argv) not in (3, 4): sys.exit(__doc__) out = Path(sys.argv[3]) if len(sys.argv) == 4 else Path(__file__).resolve().parent.parent / "mods" / "worldmap-reveal-data.lua" areas, area_string, area_sha = read_dbc(sys.argv[1], AREA_FIELDS) overlays, overlay_string, overlay_sha = read_dbc(sys.argv[2], OVERLAY_FIELDS) zone_of = {row[0]: area_string(row[AREA_NAME]) for row in areas} zones, skipped = {}, 0 for row in sorted(overlays): zone = zone_of.get(row[OV_AREA], "") texture = overlay_string(row[OV_TEXTURE]) if not zone or not texture or not row[OV_WIDTH] or not row[OV_HEIGHT]: skipped += 1 continue geometry = ":".join(str(row[i]) for i in (OV_WIDTH, OV_HEIGHT, OV_X, OV_Y)) zones.setdefault(zone, []).append(f"{texture}:{geometry}") lines = [ "-- Generated by tools/gen-worldmap-reveal-data.py, do not edit by hand.", f"-- WorldMapArea.dbc sha1 {area_sha}", f"-- WorldMapOverlay.dbc sha1 {overlay_sha}", "ShaguTweaks.MapOverlayDataOctoWoW = {", ] for zone in sorted(zones, key=str.lower): entries = ", ".join(lua_string(entry) for entry in zones[zone]) lines.append(f" [{lua_string(zone)}] = {{ {entries}, }},") lines.append("}") out.write_text("\n".join(lines) + "\n", encoding="utf-8", newline="\n") total = sum(len(v) for v in zones.values()) print(f"{out}: {len(zones)} zones, {total} overlays, {skipped} rows skipped") if __name__ == "__main__": main()