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.
85 lines
3.3 KiB
Python
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()
|