From f7b81f46a5bf685dd2614aee379ab28613e10d05 Mon Sep 17 00:00:00 2001 From: MagicBot Date: Tue, 27 Oct 2026 20:07:21 -0400 Subject: [PATCH] Parse the map zone tree file (maps/.tree) Reads uid, parent, template, name, isPlayerCity, offset, peaceZone and the three icons. Skips childCount (derived), objectId (live NEWWORLD ID, not needed yet) and extents (player-city only). A non-zero yaw, a missing offset or a map ID that differs from MB_WORLD_MAPID fails the file. Co-Authored-By: Claude Opus 5.5 --- src/engine/parsers/map/ZoneTreeParser.java | 87 +++++++++++++++++++ src/engine/parsers/objects/ZoneTreeEntry.java | 29 +++++++ 2 files changed, 116 insertions(+) create mode 100644 src/engine/parsers/map/ZoneTreeParser.java create mode 100644 src/engine/parsers/objects/ZoneTreeEntry.java diff --git a/src/engine/parsers/map/ZoneTreeParser.java b/src/engine/parsers/map/ZoneTreeParser.java new file mode 100644 index 00000000..96503454 --- /dev/null +++ b/src/engine/parsers/map/ZoneTreeParser.java @@ -0,0 +1,87 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2026 +// https://www.magicbane.com + +package engine.parsers.map; + +import engine.gameManager.ConfigManager; +import engine.parsers.cache.CacheParserUtil; +import engine.parsers.objects.ZoneTreeEntry; +import org.json.JSONArray; +import org.json.JSONObject; +import org.pmw.tinylog.Logger; + +import java.io.File; +import java.util.ArrayList; + +// Loads the zone tree for a map (maps/.tree), in file order: parents always precede their children. + +public class ZoneTreeParser { + + private static final String mapPath = ConfigManager.DEFAULT_DATA_DIR + "maps/"; + + public static ArrayList parseZoneTree(int mapID) { + + ArrayList zoneTree = new ArrayList<>(); + File treeFile = new File(mapPath + mapID + ".tree"); + JSONObject treeJson; + JSONArray zonesJson; + + if (!treeFile.exists()) { + Logger.error("Zone tree not found: " + treeFile.getAbsolutePath()); + return null; + } + + try { + treeJson = CacheParserUtil.readJsonFile(treeFile); + + if (treeJson.getInt("map") != mapID) { + Logger.error(treeFile.getName() + ": map " + treeJson.getInt("map") + " does not match MB_WORLD_MAPID " + mapID); + return null; + } + + zonesJson = treeJson.getJSONArray("zones"); + + for (int i = 0; i < zonesJson.length(); i++) + zoneTree.add(parseZoneEntry(zonesJson.getJSONObject(i))); + + } catch (Exception e) { + Logger.error(treeFile.getName() + ": " + e); + return null; + } + + Logger.info("Loaded " + zoneTree.size() + " zones from " + treeFile.getName()); + + return zoneTree; + } + + private static ZoneTreeEntry parseZoneEntry(JSONObject zoneJson) { + + ZoneTreeEntry zone = new ZoneTreeEntry(); + + zone.uid = zoneJson.getInt("uid"); + zone.parent = zoneJson.getInt("parent"); + zone.template = zoneJson.getInt("template"); + zone.name = zoneJson.getString("name"); + zone.isPlayerCity = zoneJson.getBoolean("isPlayerCity"); + zone.offset = CacheParserUtil.readVector3(zoneJson.getJSONArray("offset")); + zone.peaceZone = zoneJson.getBoolean("peaceZone"); + zone.terrain = zoneJson.getString("terrain"); + zone.biome = zoneJson.getString("biome"); + zone.height = zoneJson.getString("height"); + + // The server places zones by offset alone; a rotated zone would put everything in it out of place + if (zoneJson.getFloat("yaw") != 0.0f) + throw new IllegalStateException("zone " + zone.uid + " has a non-zero yaw"); + + if (zone.offset == null) + throw new IllegalStateException("zone " + zone.uid + " has no offset"); + + return zone; + } + +} diff --git a/src/engine/parsers/objects/ZoneTreeEntry.java b/src/engine/parsers/objects/ZoneTreeEntry.java new file mode 100644 index 00000000..198b1baa --- /dev/null +++ b/src/engine/parsers/objects/ZoneTreeEntry.java @@ -0,0 +1,29 @@ +// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . +// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· +// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ +// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ +// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ +// Magicbane Emulator Project © 2013 - 2026 +// https://www.magicbane.com + +package engine.parsers.objects; + +import engine.math.Vector3fImmutable; + +// One zone of a .tree file, exported by mbScryer from a recording's NEWWORLD message. +// Field names match the JSON keys; terrain, biome and height are the zone's three icons. + +public class ZoneTreeEntry { + + public int uid; + public int parent; + public int template; + public String name; + public boolean isPlayerCity; + public Vector3fImmutable offset; + public boolean peaceZone; + public String terrain; + public String biome; + public String height; + +}