Load static trainers from the zone JSON, comparing with the DB until migrated
World Boot Plan 3.2. With MB_STATIC_BUILDINGS=JSON, every TRAINER zone mobile takes a static ID after the cities (npc:<zone>/m<i>, npc:<zone>/<prop>/m<i>). While obj_npc still holds trainers of the same zone and name, the boot only compares and writes maps/<map>.trainer_migration.sql; afterwards the JSON trainers load before the DB vendors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a3480a0dd
commit
08b50eecf0
4 files changed
+434
No files matched your search
@@ -16,6 +16,7 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class dbStaticWorldHandler extends dbHandlerBase {
|
||||
@@ -102,4 +103,26 @@ public class dbStaticWorldHandler extends dbHandlerBase {
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// "<parent>/<name>" of every obj_npc row, to tell whether the 3.2 trainer migration has run; null on error.
|
||||
public HashSet<String> GET_NPC_ZONE_NAMES() {
|
||||
|
||||
HashSet<String> zoneNames = new HashSet<>();
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `object`.`parent`, `obj_npc`.`npc_name` FROM `object` "
|
||||
+ "INNER JOIN `obj_npc` ON `obj_npc`.`UID` = `object`.`UID`;")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
while (rs.next())
|
||||
zoneNames.add(rs.getInt("parent") + "/" + rs.getString("npc_name"));
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return zoneNames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.gameManager;
|
||||
|
||||
import engine.gameManager.StaticBuildingLoader.StaticBuildingEntry;
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.mbEnums.ZoneMobileType;
|
||||
import engine.mbEnums.ZonePropType;
|
||||
import engine.objects.Contract;
|
||||
import engine.objects.Guild;
|
||||
import engine.objects.NPC;
|
||||
import engine.objects.Zone;
|
||||
import engine.parsers.objects.CZoneMobile;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import engine.parsers.objects.ZoneTreeEntry;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static engine.util.StringUtils.wordCount;
|
||||
|
||||
// World Boot Plan 3.2: one static NPC per TRAINER zone mobile. IDs follow the cities, zone by zone in tree order:
|
||||
// the zone's own mobiles, then each top-level prop's content mobiles, in JSON order. The mobile index counts
|
||||
// MOBILE and TRAINER entries alike, so 3.3 can key the mobs without moving these. Never renumber.
|
||||
|
||||
public enum StaticTrainerLoader {
|
||||
|
||||
STATICTRAINERLOADER;
|
||||
|
||||
// Takes the static IDs and resolves contract, building and guild; builds no NPC yet.
|
||||
public static ArrayList<StaticTrainerEntry> collectTrainers(ArrayList<StaticBuildingEntry> topLevelEntries) {
|
||||
|
||||
ArrayList<StaticTrainerEntry> trainerEntries = new ArrayList<>();
|
||||
HashMap<String, StaticBuildingEntry> buildingsByKey = new HashMap<>();
|
||||
HashMap<Zone, Guild> cityGuildsByZone = new HashMap<>();
|
||||
Zone zone;
|
||||
Guild guild;
|
||||
CZoneMobile mobile;
|
||||
CZoneProp prop;
|
||||
String propKey;
|
||||
|
||||
for (StaticBuildingEntry buildingEntry : topLevelEntries) {
|
||||
|
||||
buildingsByKey.put(buildingEntry.key, buildingEntry);
|
||||
|
||||
if (buildingEntry.prop.prop_type != ZonePropType.CITY)
|
||||
continue;
|
||||
|
||||
guild = GuildManager._npcGuildsByKey.get(buildingEntry.prop.city_prop_gm_guild);
|
||||
|
||||
if (guild != null)
|
||||
cityGuildsByZone.putIfAbsent(ZoneManager.staticZonesByTreeUID.get(buildingEntry.zoneTreeUID), guild);
|
||||
}
|
||||
|
||||
for (ZoneTreeEntry treeEntry : ZoneManager.zoneTree) {
|
||||
|
||||
zone = ZoneManager.staticZonesByTreeUID.get(treeEntry.uid);
|
||||
guild = getZoneGuild(zone, cityGuildsByZone);
|
||||
|
||||
for (int mobileIndex = 0; mobileIndex < zone.template.zone_mobile_info.size(); mobileIndex++) {
|
||||
|
||||
mobile = zone.template.zone_mobile_info.get(mobileIndex);
|
||||
|
||||
if (mobile.mobile_type == ZoneMobileType.TRAINER)
|
||||
trainerEntries.add(buildEntry("npc:" + treeEntry.uid + "/m" + mobileIndex, zone, mobile, null, guild));
|
||||
}
|
||||
|
||||
for (int propIndex = 0; propIndex < zone.template.zone_prop_info.size(); propIndex++) {
|
||||
|
||||
prop = zone.template.zone_prop_info.get(propIndex);
|
||||
propKey = "prop:" + treeEntry.uid + "/" + propIndex;
|
||||
|
||||
for (int mobileIndex = 0; mobileIndex < prop.prop_content_mobiles.size(); mobileIndex++) {
|
||||
|
||||
mobile = prop.prop_content_mobiles.get(mobileIndex);
|
||||
|
||||
if (mobile.mobile_type != ZoneMobileType.TRAINER)
|
||||
continue;
|
||||
|
||||
// Twins carry no mobiles on map 108 (Task 3.2 check); a trainer in one would lose its building
|
||||
if (!buildingsByKey.containsKey(propKey))
|
||||
Logger.error("Static trainers: " + propKey + " was dropped as a twin; trainer " + mobile.mobile_base_zone.basic_zone_name_override + " has no building");
|
||||
|
||||
trainerEntries.add(buildEntry("npc:" + treeEntry.uid + "/" + propIndex + "/m" + mobileIndex, zone, mobile,
|
||||
buildingsByKey.get(propKey), guild));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Static trainers: " + trainerEntries.size() + " TRAINER zone mobiles");
|
||||
|
||||
return trainerEntries;
|
||||
}
|
||||
|
||||
private static StaticTrainerEntry buildEntry(String key, Zone zone, CZoneMobile mobile, StaticBuildingEntry buildingEntry, Guild guild) {
|
||||
|
||||
StaticTrainerEntry entry = new StaticTrainerEntry();
|
||||
|
||||
entry.key = key;
|
||||
entry.staticID = DbManager.nextStaticID(GameObjectType.NPC, key);
|
||||
entry.zone = zone;
|
||||
entry.mobile = mobile;
|
||||
entry.buildingEntry = buildingEntry;
|
||||
entry.guild = guild;
|
||||
entry.contract = findContract(mobile);
|
||||
|
||||
if (entry.contract == null)
|
||||
Logger.warn("Static trainers: " + key + " " + mobile.mobile_base_zone.basic_zone_name_override + " has no contract rune "
|
||||
+ mobile.mobile_rune_stone_ids + "; it stays out of the world, as the DB row did");
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// The first rune after the race that is a contract (Greer Osrak's is [2], not [1]; findings §6, 3.1)
|
||||
private static Contract findContract(CZoneMobile mobile) {
|
||||
|
||||
Contract contract;
|
||||
|
||||
for (int runeIndex = 1; runeIndex < mobile.mobile_rune_stone_ids.size(); runeIndex++) {
|
||||
|
||||
contract = DbManager.ContractQueries.GET_CONTRACT(mobile.mobile_rune_stone_ids.get(runeIndex));
|
||||
|
||||
if (contract != null)
|
||||
return contract;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// The guild of the zone's first CITY prop, else the nearest parent zone's
|
||||
private static Guild getZoneGuild(Zone zone, HashMap<Zone, Guild> cityGuildsByZone) {
|
||||
|
||||
for (Zone guildZone = zone; guildZone != null; guildZone = guildZone.parent)
|
||||
if (cityGuildsByZone.containsKey(guildZone))
|
||||
return cityGuildsByZone.get(guildZone);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// DB NPC rows with a JSON trainer's zone and name: more than 0 means the 3.2 migration hasn't run; -1 on error.
|
||||
public static int countDbTrainerRows(ArrayList<StaticTrainerEntry> trainerEntries) {
|
||||
|
||||
HashSet<String> dbZoneNames = DbManager.StaticWorldQueries.GET_NPC_ZONE_NAMES();
|
||||
int dbRows = 0;
|
||||
|
||||
if (dbZoneNames == null)
|
||||
return -1;
|
||||
|
||||
for (StaticTrainerEntry entry : trainerEntries)
|
||||
if (dbZoneNames.contains(entry.zone.getObjectUUID() + "/" + entry.mobile.mobile_base_zone.basic_zone_name_override))
|
||||
dbRows++;
|
||||
|
||||
return dbRows;
|
||||
}
|
||||
|
||||
public static void addToWorld(ArrayList<StaticTrainerEntry> trainerEntries) {
|
||||
|
||||
for (StaticTrainerEntry entry : trainerEntries) {
|
||||
|
||||
entry.npc = NPC.createStaticNPC(entry.staticID, entry.zone, entry.mobile,
|
||||
entry.contract == null ? 0 : entry.contract.getObjectUUID(),
|
||||
entry.buildingEntry == null ? null : entry.buildingEntry.building, entry.guild);
|
||||
DbManager.addToCache(entry.npc);
|
||||
entry.npc.runAfterLoad();
|
||||
}
|
||||
|
||||
Logger.info("Static trainers: " + trainerEntries.size() + " added to the world");
|
||||
}
|
||||
|
||||
public static class StaticTrainerEntry {
|
||||
|
||||
public String key;
|
||||
public int staticID;
|
||||
public Zone zone;
|
||||
public CZoneMobile mobile;
|
||||
public StaticBuildingEntry buildingEntry;
|
||||
public Contract contract;
|
||||
public Guild guild;
|
||||
public NPC npc;
|
||||
|
||||
// The name runAfterLoad gives the NPC
|
||||
public String getNpcName() {
|
||||
|
||||
String name = this.mobile.mobile_base_zone.basic_zone_name_override;
|
||||
|
||||
if (wordCount(name) < 2 && this.contract != null)
|
||||
return name + " the " + this.contract.getName();
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.gameManager;
|
||||
|
||||
import engine.gameManager.StaticTrainerLoader.StaticTrainerEntry;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.objects.AbstractGameObject;
|
||||
import engine.objects.Building;
|
||||
import engine.objects.Guild;
|
||||
import engine.objects.Item;
|
||||
import engine.objects.MobBase;
|
||||
import engine.objects.NPC;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
|
||||
// World Boot Plan 3.2: while obj_npc still holds the static trainers, compares them with the TRAINER zone mobiles
|
||||
// (matched by parent zone and name) and writes maps/<map>.trainer_migration.sql, the DB UID -> static ID mapping
|
||||
// that sql/task32_trainer_switch.sql deletes the rows by. No JSON NPC is built.
|
||||
|
||||
public enum StaticTrainerParity {
|
||||
|
||||
STATICTRAINERPARITY;
|
||||
|
||||
private static final String mapPath = ConfigManager.DEFAULT_DATA_DIR + "maps/";
|
||||
private static final float LOCATION_TOLERANCE = 0.2f;
|
||||
|
||||
// Run after the DB NPCs load.
|
||||
public static void run(int mapID, ArrayList<StaticTrainerEntry> trainerEntries) {
|
||||
|
||||
HashMap<String, ArrayList<StaticTrainerEntry>> entriesByZoneName = new HashMap<>();
|
||||
HashMap<NPC, StaticTrainerEntry> entriesByDbNpc = new HashMap<>();
|
||||
ArrayList<NPC> dbNpcs = new ArrayList<>();
|
||||
ArrayList<String> differences = new ArrayList<>();
|
||||
ArrayList<StaticTrainerEntry> candidates;
|
||||
NPC dbNpc;
|
||||
int dbOnly = 0;
|
||||
int jsonOnly = 0;
|
||||
|
||||
for (StaticTrainerEntry entry : trainerEntries)
|
||||
entriesByZoneName.computeIfAbsent(entry.zone.getObjectUUID() + "/" + entry.getNpcName(), key -> new ArrayList<>()).add(entry);
|
||||
|
||||
for (AbstractGameObject gameObject : DbManager.getMap(GameObjectType.NPC).values()) {
|
||||
|
||||
dbNpc = (NPC) gameObject;
|
||||
|
||||
if (!dbNpc.isStaticWorldObject())
|
||||
dbNpcs.add(dbNpc);
|
||||
}
|
||||
|
||||
dbNpcs.sort((first, second) -> Integer.compare(first.getDBID(), second.getDBID()));
|
||||
|
||||
for (NPC npc : dbNpcs) {
|
||||
|
||||
candidates = entriesByZoneName.get(npc.parentZoneUUID + "/" + npc.getName());
|
||||
|
||||
// The hand-placed vendors match no trainer; they stay in the DB
|
||||
if (candidates == null || candidates.isEmpty()) {
|
||||
dbOnly++;
|
||||
continue;
|
||||
}
|
||||
|
||||
entriesByDbNpc.put(npc, candidates.remove(0));
|
||||
compareTrainer(npc, entriesByDbNpc.get(npc), differences);
|
||||
}
|
||||
|
||||
for (StaticTrainerEntry entry : trainerEntries)
|
||||
if (!entriesByDbNpc.containsValue(entry)) {
|
||||
differences.add("JSON only: " + entry.key + " " + entry.getNpcName());
|
||||
jsonOnly++;
|
||||
}
|
||||
|
||||
Logger.info("Static trainer parity: " + trainerEntries.size() + " JSON trainers, " + entriesByDbNpc.size() + " matched DB NPCs, "
|
||||
+ dbOnly + " DB NPCs not trainers (stay in the DB), " + jsonOnly + " JSON only, " + differences.size() + " differences");
|
||||
|
||||
for (String difference : differences)
|
||||
Logger.info(" " + difference);
|
||||
|
||||
writeMigration(mapID, dbNpcs, entriesByDbNpc);
|
||||
}
|
||||
|
||||
private static void compareTrainer(NPC dbNpc, StaticTrainerEntry entry, ArrayList<String> differences) {
|
||||
|
||||
String label = dbNpc.getDBID() + " " + dbNpc.getName() + " (" + entry.key + ")";
|
||||
Building jsonBuilding = entry.buildingEntry == null ? null : entry.buildingEntry.building;
|
||||
Guild jsonGuild;
|
||||
Vector3fImmutable jsonLoc;
|
||||
|
||||
if (jsonBuilding != null)
|
||||
jsonGuild = jsonBuilding.getGuild();
|
||||
else
|
||||
jsonGuild = entry.guild == null ? Guild.getErrantGuild() : entry.guild;
|
||||
|
||||
compare(differences, label, "level", (int) dbNpc.level, entry.mobile.mobile_level);
|
||||
compare(differences, label, "contract", dbNpc.getContractID(), entry.contract == null ? 0 : entry.contract.getObjectUUID());
|
||||
compare(differences, label, "race", dbNpc.loadID, entry.mobile.mobile_rune_stone_ids.get(0));
|
||||
compare(differences, label, "runes", NPCManager._runeSetMap.get(dbNpc.runeSetID), entry.mobile.mobile_rune_stone_ids);
|
||||
compare(differences, label, "building", dbNpc.getBuilding() == null ? 0 : dbNpc.getBuilding().getObjectUUID(),
|
||||
jsonBuilding == null ? 0 : jsonBuilding.getObjectUUID());
|
||||
compare(differences, label, "guild", dbNpc.getGuild() == null ? "none" : dbNpc.getGuild().getName(), jsonGuild.getName());
|
||||
compare(differences, label, "equipment", describeEquipment(dbNpc.charItemManager.equipped.values()),
|
||||
describeEquipment(MobBase.loadEquipment(entry.mobile.mobile_equipment).values()));
|
||||
|
||||
// Building trainers take their spot from the building slot in both loads
|
||||
if (jsonBuilding == null && dbNpc.getBuilding() == null && dbNpc.getBindLoc() != null) {
|
||||
|
||||
jsonLoc = entry.zone.getLoc().add(entry.mobile.mobile_base_zone.basic_zone_spawn_location);
|
||||
|
||||
if (dbNpc.getBindLoc().distance(jsonLoc) > LOCATION_TOLERANCE)
|
||||
differences.add(label + " location: DB " + dbNpc.getBindLoc() + ", JSON " + jsonLoc);
|
||||
}
|
||||
}
|
||||
|
||||
private static String describeEquipment(Collection<Item> items) {
|
||||
|
||||
TreeSet<String> descriptions = new TreeSet<>();
|
||||
|
||||
for (Item item : items)
|
||||
descriptions.add(item.templateID + "@" + item.drop_chance);
|
||||
|
||||
return descriptions.toString();
|
||||
}
|
||||
|
||||
private static void compare(ArrayList<String> differences, String label, String field, Object dbValue, Object jsonValue) {
|
||||
|
||||
if (!Objects.equals(dbValue, jsonValue))
|
||||
differences.add(label + " " + field + ": DB " + dbValue + ", JSON " + jsonValue);
|
||||
}
|
||||
|
||||
// task32_trainer_map: every matched DB trainer (UID -> static ID and key).
|
||||
private static void writeMigration(int mapID, ArrayList<NPC> dbNpcs, HashMap<NPC, StaticTrainerEntry> entriesByDbNpc) {
|
||||
|
||||
File migrationFile = new File(mapPath + mapID + ".trainer_migration.sql");
|
||||
StringBuilder sql = new StringBuilder();
|
||||
StaticTrainerEntry entry;
|
||||
int written = 0;
|
||||
|
||||
sql.append("-- World Boot Plan 3.2: DB trainer UID -> static ID for map ").append(mapID)
|
||||
.append(", written by a JSON boot while obj_npc still holds the static trainers.\n");
|
||||
sql.append("DROP TABLE IF EXISTS `task32_trainer_map`;\n");
|
||||
sql.append("CREATE TABLE `task32_trainer_map` (`oldUID` BIGINT UNSIGNED NOT NULL, `newID` INT NOT NULL, ")
|
||||
.append("`staticKey` VARCHAR(64) NOT NULL, PRIMARY KEY (`oldUID`));\n");
|
||||
sql.append("INSERT INTO `task32_trainer_map` VALUES\n");
|
||||
|
||||
for (NPC npc : dbNpcs) {
|
||||
|
||||
entry = entriesByDbNpc.get(npc);
|
||||
|
||||
if (entry == null)
|
||||
continue;
|
||||
|
||||
sql.append(written == 0 ? "" : ",\n");
|
||||
sql.append("(").append(npc.getDBID()).append(", ").append(entry.staticID).append(", '").append(entry.key).append("')");
|
||||
written++;
|
||||
}
|
||||
|
||||
sql.append(";\n");
|
||||
|
||||
try {
|
||||
Files.write(migrationFile.toPath(), sql.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
Logger.error("Static trainer migration mapping not written: " + migrationFile.getAbsolutePath() + ": " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info("Static trainer migration mapping: " + written + " rows written to " + migrationFile.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -289,9 +289,11 @@ public class WorldServer {
|
||||
mbEnums.StaticRunegateMode staticRunegateMode;
|
||||
int staticZoneRows;
|
||||
int npcCityRows = 0;
|
||||
int dbTrainerRows = 0;
|
||||
int movedStaticIDs;
|
||||
ArrayList<StaticBuildingLoader.StaticBuildingEntry> staticBuildingEntries = null;
|
||||
ArrayList<StaticCityLoader.StaticCityEntry> staticCityEntries = null;
|
||||
ArrayList<StaticTrainerLoader.StaticTrainerEntry> staticTrainerEntries = null;
|
||||
|
||||
Logger.info("MAGICBANE SERVER GREETING:");
|
||||
Logger.info(ConfigManager.MB_WORLD_GREETING.getValue());
|
||||
@@ -640,6 +642,26 @@ public class WorldServer {
|
||||
|
||||
Logger.info("Loading all Cities");
|
||||
DbManager.CityQueries.GET_ALL_CITIES();
|
||||
|
||||
// Until the 3.2 migration deletes the DB trainers, they load from the DB and the JSON trainers only take
|
||||
// their IDs for the parity check. Static trainers load before the DB vendors, as their UIDs did.
|
||||
|
||||
if (staticBuildingMode == mbEnums.StaticBuildingMode.JSON) {
|
||||
|
||||
staticTrainerEntries = StaticTrainerLoader.collectTrainers(staticBuildingEntries);
|
||||
dbTrainerRows = StaticTrainerLoader.countDbTrainerRows(staticTrainerEntries);
|
||||
|
||||
if (dbTrainerRows < 0) {
|
||||
Logger.error("ABORT! DB trainer rows could not be counted.");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Static trainers: " + dbTrainerRows + " DB NPC rows match a trainer; " + (dbTrainerRows == 0 ? "loading from the zone JSON" : "parity only"));
|
||||
|
||||
if (dbTrainerRows == 0)
|
||||
StaticTrainerLoader.addToWorld(staticTrainerEntries);
|
||||
}
|
||||
|
||||
Logger.info("Loading all NPCs");
|
||||
DbManager.NPCQueries.GET_ALL_NPCS();
|
||||
|
||||
@@ -659,6 +681,11 @@ public class WorldServer {
|
||||
StaticCityParity.run(worldMapID, staticCityEntries);
|
||||
}
|
||||
|
||||
if (dbTrainerRows > 0) {
|
||||
Logger.info("Comparing static trainers with the zone JSON");
|
||||
StaticTrainerParity.run(worldMapID, staticTrainerEntries);
|
||||
}
|
||||
|
||||
movedStaticIDs = StaticManifest.writeManifest(worldMapID);
|
||||
StaticManifest.logOrphanReferences();
|
||||
|
||||
|
||||
Reference in new issue
Block a user