Files
ShagaTweaks-extras/tools/gen-worldmap-reveal-data.py
shaga b5cd216880 Fix Reveal World Map drawing overlays in the wrong place
The module drew every overlay from a hand-maintained Turtle WoW table that no
longer matches the client: the OctoWoW client redrew the overlays of every
zone, so revealed areas came out misplaced, stretched or missing, worst of
all in Stonetalon Mountains. It also redrew the explored overlays from that
table, so even explored areas moved.

- mods/worldmap-reveal-data.lua lists the overlays of every zone, generated
  from the client's own WorldMapArea.dbc and WorldMapOverlay.dbc by
  tools/gen-worldmap-reveal-data.py. It replaces the Turtle WoW table.
- Explored overlays are left to the game. The module only adds the
  unexplored ones after the textures the game used, and resets the dimming
  on textures the game reuses.
- If the data disagrees with an overlay the game reports, the zone is left
  as the game drew it. A client patch that changes the map then shows the
  plain map until the data is regenerated, not misplaced textures.
- Overlays are matched by file name ignoring case, since some of the
  client's overlay names are mixed case.
2026-10-13 17:15:39 +00:00

85 lines
3.3 KiB
Python

#!/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()