Build static zones from the zone tree at world server boot

ZoneManager.loadZones builds the 1,062 static zones from
maps/<MB_WORLD_MAPID>.tree through Zone.createStaticZone, flagged static
and keeping their positive tree UIDs (negative IDs come with W10 in 2.4).
Player-city zones still load from obj_zone (template 0). Boot aborts on a
missing tree file, a missing CZONE template or a missing parent.
GET_ALL_ZONES is kept until the removal task.

Value changes vs obj_zone (parity: scripts/task14_zone_parity.py):
- 44 zones: x/z offsets take the live values; obj_zone had them rounded
  to whole units (e.g. 279: 192.0 -> 192.48, 274: -238.0 -> -237.653).
- Static zones no longer write their hash to obj_zone at boot; the hash
  is computed in memory (same value).
- min/max level 30/60 for the 50 icon zones now comes from constants
  (no client data carries zone levels); same values as obj_zone.
Everything else identical: parent (except the unused root parent),
template, name, peace flag, y offset, icons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-27 20:07:21 -04:00
1 parent f7b81f46a5
commit c66ce6e777
4 files changed
+126 -11

No files matched your search

+17
View File
@@ -44,6 +44,23 @@ public class dbZoneHandler extends dbHandlerBase {
return zoneList;
}
public ArrayList<Zone> GET_PLAYER_CITY_ZONES() {
ArrayList<Zone> zoneList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_zone`.*, `object`.`parent` FROM `object` INNER JOIN `obj_zone` ON `obj_zone`.`UID` = `object`.`UID` WHERE `obj_zone`.`template` = 0 ORDER BY `object`.`UID` ASC;")) {
ResultSet rs = preparedStatement.executeQuery();
zoneList = getObjectsFromRs(rs, 200);
} catch (SQLException e) {
Logger.error(e);
}
return zoneList;
}
public Zone GET_BY_UID(long ID) {
Zone zone = (Zone) DbManager.getFromCache(mbEnums.GameObjectType.Zone, (int) ID);
+51
View File
@@ -17,7 +17,9 @@ import engine.mbEnums;
import engine.objects.Building;
import engine.objects.City;
import engine.objects.Zone;
import engine.parsers.map.ZoneTreeParser;
import engine.parsers.objects.CZoneTemplate;
import engine.parsers.objects.ZoneTreeEntry;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
@@ -74,6 +76,55 @@ public enum ZoneManager {
Logger.info("Zone templates: " + _zone_templates.size());
}
// Static zones from the map's zone tree, then player-city zones from the DB. False aborts boot.
public static boolean loadZones(int mapID) {
ArrayList<ZoneTreeEntry> zoneTree;
ArrayList<Zone> cityZones;
Zone zone;
int staticCount = 0;
boolean templatesMissing = false;
zoneTree = ZoneTreeParser.parseZoneTree(mapID);
if (zoneTree == null)
return false;
for (ZoneTreeEntry treeEntry : zoneTree)
if (!treeEntry.isPlayerCity && !_zone_templates.containsKey(treeEntry.template)) {
Logger.error("Zone " + treeEntry.uid + " (" + treeEntry.name + ") has no CZONE template " + treeEntry.template);
templatesMissing = true;
}
if (templatesMissing)
return false;
// Player-city zones in the tree are a recording's cities; the live ones come from the DB
for (ZoneTreeEntry treeEntry : zoneTree) {
if (treeEntry.isPlayerCity)
continue;
if (treeEntry.parent != 0 && getZoneByUUID(treeEntry.parent) == null) {
Logger.error("Zone " + treeEntry.uid + " (" + treeEntry.name + ") has no parent zone " + treeEntry.parent);
return false;
}
zone = Zone.createStaticZone(treeEntry);
DbManager.addToCache(zone);
zone.runAfterLoad();
staticCount++;
}
cityZones = DbManager.ZoneQueries.GET_PLAYER_CITY_ZONES();
Logger.info("Zones: " + staticCount + " static, " + cityZones.size() + " player city");
return true;
}
// Find all zones coordinates fit into, starting with Sea Floor
public static ArrayList<Zone> getAllZonesIn(final Vector3fImmutable loc) {
+52 -10
View File
@@ -18,6 +18,7 @@ import engine.math.Vector3fImmutable;
import engine.mbEnums;
import engine.net.ByteBufferWriter;
import engine.parsers.objects.CZoneTemplate;
import engine.parsers.objects.ZoneTreeEntry;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
@@ -34,20 +35,24 @@ public class Zone extends AbstractWorldObject {
public final Set<NPC> zoneNPCSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final Set<Mob> zoneMobSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
// Server-only hotzone range for zones with icons; no client data carries zone levels
public static final int MACRO_ZONE_MIN_LEVEL = 30;
public static final int MACRO_ZONE_MAX_LEVEL = 60;
public CZoneTemplate template;
public final int playerCityUUID;
public final String zoneName;
public int playerCityUUID;
public String zoneName;
public float major_radius;
public float minor_radius;
public final float xOffset;
public final float zOffset;
public final float yOffset;
public final int templateID;
public final byte peace_zone;
public final String icon1;
public final String icon2;
public final String icon3;
public float xOffset;
public float zOffset;
public float yOffset;
public int templateID;
public byte peace_zone;
public String icon1;
public String icon2;
public String icon3;
public float absX = 0.0f;
public float absY = 0.0f;
public float absZ = 0.0f;
@@ -66,6 +71,13 @@ public class Zone extends AbstractWorldObject {
public Terrain terrain = null;
/**
* No Id Constructor
*/
public Zone() {
super();
}
/**
* ResultSet Constructor
*/
@@ -98,6 +110,36 @@ public class Zone extends AbstractWorldObject {
}
// Static zones keep their positive tree UIDs until the W10 switch to negative IDs
public static Zone createStaticZone(ZoneTreeEntry treeEntry) {
Zone zone = new Zone();
zone.setObjectUUID(treeEntry.uid);
zone.setStaticWorldObject(true);
zone.parentZoneID = treeEntry.parent;
zone.templateID = treeEntry.template;
zone.zoneName = treeEntry.name;
zone.peace_zone = treeEntry.peaceZone ? (byte) 1 : (byte) 0;
zone.xOffset = treeEntry.offset.x;
zone.yOffset = treeEntry.offset.y;
zone.zOffset = treeEntry.offset.z;
zone.playerCityUUID = 0;
zone.guild_zone = false;
zone.icon1 = treeEntry.terrain;
zone.icon2 = treeEntry.biome;
zone.icon3 = treeEntry.height;
if (!zone.icon1.isEmpty()) {
zone.min_level = MACRO_ZONE_MIN_LEVEL;
zone.max_level = MACRO_ZONE_MAX_LEVEL;
}
zone.hash = DataWarehouse.hasher.encrypt(zone.getObjectUUID());
return zone;
}
public static void serializeForClientMsg(Zone zone, ByteBufferWriter writer) {
if (zone.templateID == 0 && zone.playerCityUUID == 0)
+6 -1
View File
@@ -536,7 +536,12 @@ public class WorldServer {
long start = System.currentTimeMillis();
Logger.info("Loading all Zones");
DbManager.ZoneQueries.GET_ALL_ZONES();
if (!ZoneManager.loadZones(worldMapID)) {
Logger.error("ABORT! Zone tree failed to load!");
exit(1);
}
Logger.info("Loading all Buildings");
BuildingManager._npcBuildingOwners = DbManager.BuildingQueries.GET_NPC_BUILDING_OWNERS();
DbManager.BuildingQueries.GET_ALL_BUILDINGS();