Parse CZONE zone templates at world server boot

ZoneParser reads mb.data/CZONE/<id>.json into CZoneTemplate with its
props, child props, mobiles and point sets (World-Boot-Plan Task 1.1).
Only the agreed server-needed fields are parsed; textures, water,
weather, sound and always-default fields are skipped. Boot aborts if
any file fails. Nothing uses the data yet (Task 1.2).

New mbEnums: ZoneShapeType, SeaLevelType, TerrainGenType, ZonePropType,
ZoneMobileType, PointSetType. root FSM maps to MobBehaviourType and
monster types to MonsterType.

Value changes: none (load only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-27 19:11:45 -04:00
1 parent a8b8858f0e
commit ac1fcaecf5
9 files changed
+622

No files matched your search

+35
View File
@@ -3147,6 +3147,41 @@ public class mbEnums {
MULTIPLY
}
// CZONE value sets; only the values present in the shipped cache.
public enum ZoneShapeType {
ELLIPTICAL,
RECTANGULAR
}
public enum SeaLevelType {
PARENT,
SELF,
WORLD
}
// NONE is the server's marker for a zone with no zone_terrain_gen block
public enum TerrainGenType {
NONE,
TARGA,
PLANAR
}
public enum ZonePropType {
PROP,
CITY
}
public enum ZoneMobileType {
MOBILE,
TRAINER
}
public enum PointSetType {
POINTS_ORDEREDREV
}
public enum ServerConfig {
MBDB_108("AERYNTH", "ARAC", "MOURNING", 300000),
MBDB_192("DALGOTH", "ARAC", "GOOK", 300001),
+336
View File
@@ -0,0 +1,336 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.CZoneDrop;
import engine.parsers.objects.CZoneMobile;
import engine.parsers.objects.CZonePlacement;
import engine.parsers.objects.CZonePointSet;
import engine.parsers.objects.CZoneProp;
import engine.parsers.objects.CZoneTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads zone templates from the client cache JSON (CZONE/<id>.json).
public class ZoneParser {
private static final String zonePath = ConfigManager.DEFAULT_DATA_DIR + "CZONE/";
public static HashMap<Integer, CZoneTemplate> parseZoneFolder() {
HashMap<Integer, CZoneTemplate> zoneTemplates = new HashMap<>();
File[] zoneFiles = CacheParserUtil.listJsonFiles(zonePath);
int templateId;
JSONObject zoneJson;
int failedCount = 0;
for (File zoneFile : zoneFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(zoneFile);
zoneJson = CacheParserUtil.readJsonFile(zoneFile);
zoneTemplates.put(templateId, parseZoneEntry(templateId, zoneJson));
} catch (Exception e) {
Logger.error(zoneFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + zoneTemplates.size() + " of " + zoneFiles.length + " zone templates");
logCounts(zoneTemplates);
// Partial zone data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return zoneTemplates;
}
private static CZoneTemplate parseZoneEntry(int templateId, JSONObject zoneJson) {
CZoneTemplate zone = new CZoneTemplate();
JSONObject terrainJson;
JSONArray propsJson;
JSONArray mobilesJson;
JSONArray pointSetsJson;
zone.template_id = templateId;
zone.zone_type = CacheParserUtil.readEnum(mbEnums.ZoneShapeType.class, zoneJson.getString("zone_type"), templateId, "zone_type");
zone.zone_name = zoneJson.getString("zone_name");
zone.zone_peace_zone = zoneJson.getBoolean("zone_peace_zone");
zone.zone_major_radius = zoneJson.getFloat("zone_major_radius");
zone.zone_minor_radius = zoneJson.getFloat("zone_minor_radius");
zone.zone_min_blend = zoneJson.getFloat("zone_min_blend");
zone.zone_max_blend = zoneJson.getFloat("zone_max_blend");
zone.zone_sea_level = zoneJson.getFloat("zone_sea_level");
zone.zone_sea_level_type = CacheParserUtil.readEnum(mbEnums.SeaLevelType.class, zoneJson.getString("zone_sea_level_type"),
templateId, "zone_sea_level_type");
zone.zone_has_water = zoneJson.getBoolean("zone_has_water");
zone.zone_has_terrain_gen = zoneJson.getBoolean("zone_has_terrain_gen");
zone.terrain_type = mbEnums.TerrainGenType.NONE;
if (zone.zone_has_terrain_gen) {
terrainJson = zoneJson.getJSONObject("zone_terrain_gen");
zone.terrain_type = CacheParserUtil.readEnum(mbEnums.TerrainGenType.class, terrainJson.getString("terrain_type"),
templateId, "terrain_type");
if (zone.terrain_type == mbEnums.TerrainGenType.TARGA) {
zone.terrain_max_y = terrainJson.getFloat("terrain_max_y");
zone.terrain_image = terrainJson.getInt("terrain_image");
}
}
propsJson = zoneJson.getJSONArray("zone_prop_info");
for (int i = 0; i < propsJson.length(); i++)
zone.zone_prop_info.add(parseProp(propsJson.getJSONObject(i), templateId));
mobilesJson = zoneJson.getJSONArray("zone_mobile_info");
for (int i = 0; i < mobilesJson.length(); i++)
zone.zone_mobile_info.add(parseMobile(mobilesJson.getJSONObject(i), templateId));
pointSetsJson = zoneJson.getJSONArray("zone_point_sets");
for (int i = 0; i < pointSetsJson.length(); i++)
zone.zone_point_sets.add(parsePointSet(pointSetsJson.getJSONObject(i), templateId));
return zone;
}
private static CZonePlacement parsePlacement(JSONObject placementJson, int templateId) {
CZonePlacement placement = new CZonePlacement();
JSONObject sparseJson;
placement.basic_zone_spawn_location = CacheParserUtil.readVector3(placementJson.getJSONArray("basic_zone_spawn_location"));
placement.basic_zone_name_override = placementJson.getString("basic_zone_name_override");
placement.basic_zone_time_to_respawn = placementJson.getFloat("basic_zone_time_to_respawn");
placement.basic_zone_spawn_radius = placementJson.getFloat("basic_zone_spawn_radius");
placement.basic_zone_y_rot = placementJson.getFloat("basic_zone_y_rot");
placement.basic_zone_template_id = (int) placementJson.getLong("basic_zone_template_id");
placement.basic_zone_level_number = placementJson.getInt("basic_zone_level_number");
placement.basic_zone_room_number = placementJson.getInt("basic_zone_room_number");
sparseJson = placementJson.getJSONObject("basic_zone_sparse_data");
for (String key : sparseJson.keySet()) {
if (!key.equals("IMMUNE_TO_STEAL"))
throw new IllegalArgumentException(templateId + ": basic_zone_sparse_data has unknown key '" + key + "'");
placement.basic_zone_immune_to_steal = sparseJson.getBoolean(key);
}
return placement;
}
private static CZoneProp parseProp(JSONObject propJson, int templateId) {
CZoneProp prop = new CZoneProp();
JSONObject dataJson;
JSONArray childrenJson;
JSONArray mobilesJson;
prop.prop_type = CacheParserUtil.readEnum(mbEnums.ZonePropType.class, propJson.getString("prop_type"), templateId, "prop_type");
dataJson = propJson.getJSONObject("prop_data");
prop.prop_basic_zone = parsePlacement(dataJson.getJSONObject("prop_basic_zone"), templateId);
childrenJson = dataJson.getJSONArray("prop_content_props");
for (int i = 0; i < childrenJson.length(); i++)
prop.prop_content_props.add(parseProp(childrenJson.getJSONObject(i), templateId));
mobilesJson = dataJson.getJSONArray("prop_content_mobiles");
for (int i = 0; i < mobilesJson.length(); i++)
prop.prop_content_mobiles.add(parseMobile(mobilesJson.getJSONObject(i), templateId));
prop.prop_registry = dataJson.getInt("prop_registry");
if (prop.prop_registry != 0)
prop.prop_registry_name = dataJson.getString("prop_registry_name");
prop.prop_has_teleporter = dataJson.getBoolean("prop_has_teleporter");
if (prop.prop_has_teleporter)
prop.prop_teleporter = (int) dataJson.getLong("prop_teleporter");
if (prop.prop_type == mbEnums.ZonePropType.CITY) {
prop.city_prop_gm_guild = (int) dataJson.getLong("city_prop_gm_guild");
prop.city_prop_gm_nation = (int) dataJson.getLong("city_prop_gm_nation");
prop.city_prop_asset_rank = dataJson.getInt("city_prop_asset_rank");
prop.city_prop_asset_funds = dataJson.getInt("city_prop_asset_funds");
}
return prop;
}
private static CZoneMobile parseMobile(JSONObject mobileJson, int templateId) {
CZoneMobile mobile = new CZoneMobile();
JSONObject dataJson;
JSONArray equipmentJson;
JSONArray equipJson;
JSONArray bootiesJson;
JSONObject bootyJson;
JSONArray itemsJson;
String rootFsm;
mobile.mobile_type = CacheParserUtil.readEnum(mbEnums.ZoneMobileType.class, mobileJson.getString("mobile_type"), templateId, "mobile_type");
dataJson = mobileJson.getJSONObject("mobile_data");
mobile.mobile_base_zone = parsePlacement(dataJson.getJSONObject("mobile_base_zone"), templateId);
mobile.mobile_number_in_group = dataJson.getInt("mobile_number_in_group");
mobile.mobile_level = dataJson.getInt("mobile_level");
mobile.mobile_dialog_override = (int) dataJson.getLong("mobile_dialog_override");
mobile.mobile_rune_stone_ids.addAll(CacheParserUtil.readIntList(dataJson.getJSONArray("mobile_rune_stone_ids")));
mobile.mobile_spawn_hps.addAll(CacheParserUtil.readIntList(dataJson.getJSONArray("mobile_spawn_hps")));
// Equipment entries are [item id, chance]
equipmentJson = dataJson.getJSONArray("mobile_equipment");
for (int i = 0; i < equipmentJson.length(); i++) {
equipJson = equipmentJson.getJSONArray(i);
mobile.mobile_equipment.add(new CZoneDrop((int) equipJson.getLong(0), equipJson.getFloat(1)));
}
// Booty items are [chance, item id] pairs, the reverse of equipment
bootiesJson = dataJson.getJSONArray("mobile_booties");
for (int i = 0; i < bootiesJson.length(); i++) {
bootyJson = bootiesJson.getJSONObject(i);
if (!bootyJson.getString("type").equals("ITEM"))
throw new IllegalArgumentException(templateId + ": mobile_booties has unknown type '" + bootyJson.getString("type") + "'");
itemsJson = bootyJson.getJSONArray("items");
for (int j = 0; j + 1 < itemsJson.length(); j += 2)
mobile.mobile_booties.add(new CZoneDrop((int) itemsJson.getLong(j + 1), itemsJson.getFloat(j)));
}
rootFsm = dataJson.getString("mobile_root_fs_mid");
if (!rootFsm.isEmpty())
mobile.mobile_root_fs_mid = CacheParserUtil.readEnum(mbEnums.MobBehaviourType.class, rootFsm, templateId, "mobile_root_fs_mid");
mobile.mobile_has_home_goal = dataJson.getBoolean("mobile_has_home_goal");
if (mobile.mobile_has_home_goal) {
mobile.mobile_home_goal = (int) dataJson.getLong("mobile_home_goal");
mobile.mobile_spawn_home_goal = dataJson.getBoolean("mobile_spawn_home_goal");
}
mobile.mobile_has_target_goal = dataJson.getBoolean("mobile_has_target_goal");
if (mobile.mobile_has_target_goal) {
mobile.mobile_target_goal = (int) dataJson.getLong("mobile_target_goal");
mobile.mobile_spawn_target_goal = dataJson.getBoolean("mobile_spawn_target_goal");
}
mobile.mobile_has_group_tactics = dataJson.getBoolean("mobile_has_group_tactics");
if (mobile.mobile_has_group_tactics)
mobile.mobile_group_tactics = (int) dataJson.getLong("mobile_group_tactics");
mobile.mobile_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class,
dataJson.getJSONArray("mobile_enemy_monster_types"), templateId, "mobile_enemy_monster_types"));
mobile.mobile_not_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class,
dataJson.getJSONArray("mobile_not_enemy_monster_types"), templateId, "mobile_not_enemy_monster_types"));
mobile.mobile_friendly_charters.addAll(CacheParserUtil.readIntList(dataJson.getJSONArray("mobile_friendly_charters")));
mobile.mobile_parley_name = dataJson.getString("mobile_parley_name");
if (mobile.mobile_type == mbEnums.ZoneMobileType.TRAINER) {
mobile.trainer_std_profit_margin = dataJson.getFloat("trainer_std_profit_margin");
mobile.trainer_profit_margin = dataJson.getFloat("trainer_profit_margin");
}
return mobile;
}
private static CZonePointSet parsePointSet(JSONObject pointSetJson, int templateId) {
CZonePointSet pointSet = new CZonePointSet();
JSONArray pointsJson;
JSONObject pointJson;
CZonePointSet.Point point;
pointSet.zone_goal_type = CacheParserUtil.readEnum(mbEnums.PointSetType.class, pointSetJson.getString("zone_goal_type"),
templateId, "zone_goal_type");
pointSet.zone_goal_value = (int) pointSetJson.getLong("zone_goal_value");
pointSet.zone_goal_name = pointSetJson.getString("zone_goal_name");
pointsJson = pointSetJson.getJSONArray("zone_goal_points");
for (int i = 0; i < pointsJson.length(); i++) {
pointJson = pointsJson.getJSONObject(i);
point = new CZonePointSet.Point();
point.zone_goal_point_location = CacheParserUtil.readVector3(pointJson.getJSONArray("zone_goal_point_location"));
point.zone_goal_point_vectors = CacheParserUtil.readIntArray(pointJson.getJSONArray("zone_goal_point_vectors"));
pointSet.zone_goal_points.add(point);
}
return pointSet;
}
// Counts to check against World-Boot-Plan findings §1
private static void logCounts(HashMap<Integer, CZoneTemplate> zoneTemplates) {
int topProps = 0;
int childProps = 0;
int cityProps = 0;
int zoneMobiles = 0;
int zoneTrainers = 0;
int propMobiles = 0;
int propTrainers = 0;
int pointSets = 0;
for (CZoneTemplate zone : zoneTemplates.values()) {
topProps += zone.zone_prop_info.size();
pointSets += zone.zone_point_sets.size();
for (CZoneMobile mobile : zone.zone_mobile_info) {
if (mobile.mobile_type == mbEnums.ZoneMobileType.TRAINER)
zoneTrainers++;
else
zoneMobiles++;
}
for (CZoneProp prop : zone.zone_prop_info) {
childProps += prop.prop_content_props.size();
if (prop.prop_type == mbEnums.ZonePropType.CITY)
cityProps++;
for (CZoneMobile mobile : prop.prop_content_mobiles) {
if (mobile.mobile_type == mbEnums.ZoneMobileType.TRAINER)
propTrainers++;
else
propMobiles++;
}
}
}
Logger.info("Zone props: " + topProps + " top level (" + cityProps + " CITY), " + childProps + " children");
Logger.info("Zone mobiles: " + zoneMobiles + " MOBILE, " + zoneTrainers + " TRAINER; prop mobiles: "
+ propMobiles + " MOBILE, " + propTrainers + " TRAINER; point sets: " + pointSets);
}
}
+24
View File
@@ -0,0 +1,24 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
// One item with a chance, from a CZONE mobile's mobile_equipment or mobile_booties.
// Equipment chances are 0–1 fractions; booty chances are percentages (100.0, 50.0).
public class CZoneDrop {
public final int item_id;
public final float chance;
public CZoneDrop(int item_id, float chance) {
this.item_id = item_id;
this.chance = chance;
}
}
@@ -0,0 +1,59 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.mbEnums;
import java.util.ArrayList;
import java.util.EnumSet;
// A MOBILE or TRAINER entry from a CZONE zone (zone_mobile_info) or from a
// top-level prop (prop_content_mobiles). Field names match the JSON keys.
// Fields that are default in every shipped file are not parsed (Task 1.1 list).
public class CZoneMobile {
public mbEnums.ZoneMobileType mobile_type;
public CZonePlacement mobile_base_zone;
public int mobile_number_in_group;
public int mobile_level;
public int mobile_dialog_override;
public final ArrayList<Integer> mobile_rune_stone_ids = new ArrayList<>();
public final ArrayList<CZoneDrop> mobile_equipment = new ArrayList<>();
public final ArrayList<CZoneDrop> mobile_booties = new ArrayList<>();
public final ArrayList<Integer> mobile_spawn_hps = new ArrayList<>();
// null when the entry has no root FSM
public mbEnums.MobBehaviourType mobile_root_fs_mid;
// Goal values are zone_point_sets zone_goal_value hashes
public boolean mobile_has_home_goal;
public int mobile_home_goal;
public boolean mobile_spawn_home_goal;
public boolean mobile_has_target_goal;
public int mobile_target_goal;
public boolean mobile_spawn_target_goal;
public boolean mobile_has_group_tactics;
public int mobile_group_tactics;
public final EnumSet<mbEnums.MonsterType> mobile_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
public final EnumSet<mbEnums.MonsterType> mobile_not_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
public final ArrayList<Integer> mobile_friendly_charters = new ArrayList<>();
public String mobile_parley_name;
// TRAINER only
public float trainer_std_profit_margin;
public float trainer_profit_margin;
}
@@ -0,0 +1,32 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
// The basic_zone_* block shared by every CZONE prop and mobile
// (client BasicZoneTemplateData). Field names match the JSON keys.
// spawn_location is relative to the parent; y_rot is not the client yaw (W8: yaw = π − y_rot).
public class CZonePlacement {
public Vector3fImmutable basic_zone_spawn_location;
public String basic_zone_name_override;
public float basic_zone_time_to_respawn;
public float basic_zone_spawn_radius;
public float basic_zone_y_rot;
public int basic_zone_template_id;
public int basic_zone_level_number;
public int basic_zone_room_number;
// IMMUNE_TO_STEAL is the only sparse key in the shipped cache
public boolean basic_zone_immune_to_steal;
}
@@ -0,0 +1,36 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import java.util.ArrayList;
// A zone_point_sets entry: a named path that mobile home/target goals point at
// through zone_goal_value. Field names match the JSON keys.
public class CZonePointSet {
public mbEnums.PointSetType zone_goal_type;
public int zone_goal_value;
public String zone_goal_name;
public final ArrayList<Point> zone_goal_points = new ArrayList<>();
public static class Point {
public Vector3fImmutable zone_goal_point_location;
// Six ints; the first is the point's index, the rest are unknown
public int[] zone_goal_point_vectors;
}
}
+39
View File
@@ -0,0 +1,39 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.mbEnums;
import java.util.ArrayList;
// A PROP or CITY entry from a CZONE zone (zone_prop_info) or a child prop
// (prop_content_props). Field names match the JSON keys.
// Children never have children or mobiles in the shipped cache.
public class CZoneProp {
public mbEnums.ZonePropType prop_type;
public CZonePlacement prop_basic_zone;
public final ArrayList<CZoneProp> prop_content_props = new ArrayList<>();
public final ArrayList<CZoneMobile> prop_content_mobiles = new ArrayList<>();
public int prop_registry;
public String prop_registry_name = "";
public boolean prop_has_teleporter;
public int prop_teleporter;
// CITY only. Guild and nation are keys into the map's guild seed (W7).
public int city_prop_gm_guild;
public int city_prop_gm_nation;
public int city_prop_asset_rank;
public int city_prop_asset_funds;
}
@@ -0,0 +1,51 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.mbEnums;
import java.util.ArrayList;
import java.util.HashMap;
// Zone template as defined by the client's cache data (MBDATA/data/cache/CZONE).
// Field names match the JSON keys. Only server-needed fields are parsed (Task 1.1 list);
// textures, water, weather, sound and fields default in every file are skipped.
// A template is reused by many zones; placement lives in the zone tree.
//
// Read only once loaded.
public class CZoneTemplate {
public static HashMap<Integer, CZoneTemplate> _zones = new HashMap<>();
public int template_id;
public mbEnums.ZoneShapeType zone_type;
public String zone_name;
public boolean zone_peace_zone;
public float zone_major_radius;
public float zone_minor_radius;
public float zone_min_blend;
public float zone_max_blend;
public float zone_sea_level;
public mbEnums.SeaLevelType zone_sea_level_type;
public boolean zone_has_water;
// zone_terrain_gen; max_y and image exist only on TARGA terrain
public boolean zone_has_terrain_gen;
public mbEnums.TerrainGenType terrain_type;
public float terrain_max_y;
public int terrain_image;
public final ArrayList<CZoneProp> zone_prop_info = new ArrayList<>();
public final ArrayList<CZoneMobile> zone_mobile_info = new ArrayList<>();
public final ArrayList<CZonePointSet> zone_point_sets = new ArrayList<>();
}
+10
View File
@@ -36,6 +36,8 @@ import engine.parsers.cache.DeedParser;
import engine.parsers.cache.ItemParser;
import engine.parsers.cache.ParityCheck;
import engine.parsers.cache.RuneParser;
import engine.parsers.cache.ZoneParser;
import engine.parsers.objects.CZoneTemplate;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.ItemTemplate;
import engine.parsers.objects.RuneTemplate;
@@ -430,6 +432,14 @@ public class WorldServer {
Logger.info("Indexing Cache Templates");
CObjectIndex.build();
Logger.info("Loading Cache Zone Templates");
CZoneTemplate._zones = ZoneParser.parseZoneFolder();
if (CZoneTemplate._zones == null || CZoneTemplate._zones.isEmpty()) {
Logger.error("ABORT! Cache zone templates failed to load!");
exit(1);
}
Logger.info("Loading Rune Power Requirements");
PowerReq.loadRunePowers();