Load static NPC guilds from the guild seed at world server boot
GuildManager.loadStaticGuilds builds the map's NPC guilds from maps/<MB_WORLD_MAPID>.guild through Guild.createStaticGuild, before zones, so they take the first static IDs (-2 down, file order). Each is flagged static, is its own nation (as live and in the DB) and is indexed by its CZONE guild and nation keys. Boot aborts on a missing or bad seed file or a duplicate key. After zones load, every CITY-prop guild key with no seed entry is logged once with its zones; known no-guild keys are only counted. Nothing references the static guilds yet: NPC cities, NPCs and players still use the DB guilds (UID 2-25) until 2.5, so there is no gameplay change. Seed vs the 24 DB NPC guilds (scripts/task16_guild_seed.py): name, crest, leadership type, motto and repledge/teleport limits identical. Charter differs on all 24: DB 562 (BARBARIAN placeholder) -> guilddata value, read as the client guild type index (sb.exe g_GuildTypeNames 0x175B8E0), e.g. Kingscross NOBLE, Bastion CATHEDRAL, Khar Th'Sekt VIRAKT, hamlets MILITARY. Static guilds have no warehouse hash yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4839883530
commit
42a6070d76
3 files changed
+152
-9
No files matched your search
@@ -16,7 +16,18 @@ import engine.net.client.ClientConnection;
|
||||
import engine.net.client.msg.guild.AcceptInviteToGuildMsg;
|
||||
import engine.net.client.msg.guild.GuildInfoMsg;
|
||||
import engine.objects.*;
|
||||
import engine.parsers.map.GuildSeedParser;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import engine.parsers.objects.GuildSeed;
|
||||
import engine.parsers.objects.GuildSeedEntry;
|
||||
import org.joda.time.DateTime;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public enum GuildManager {
|
||||
|
||||
@@ -33,6 +44,95 @@ public enum GuildManager {
|
||||
public static final int CREST_RESERVED = 153; //153: Guild crest is reserved
|
||||
public static final int CREST_COLOR_ERROR = 154; //154: All three crest colors cannot be the same
|
||||
|
||||
// NPC guilds by CZONE city_prop_gm_guild / city_prop_gm_nation key (W7)
|
||||
public static final HashMap<Integer, Guild> _npcGuildsByKey = new HashMap<>();
|
||||
public static final HashMap<Integer, Guild> _npcGuildsByNationKey = new HashMap<>();
|
||||
public static final HashSet<Integer> _noGuildKeys = new HashSet<>();
|
||||
|
||||
// Static NPC guilds from maps/<map>.guild. Loaded before zones, so they take the first static IDs. False aborts boot.
|
||||
|
||||
public static boolean loadStaticGuilds(int mapID) {
|
||||
|
||||
GuildSeed guildSeed;
|
||||
Guild guild;
|
||||
|
||||
guildSeed = GuildSeedParser.parseGuildSeed(mapID);
|
||||
|
||||
if (guildSeed == null)
|
||||
return false;
|
||||
|
||||
for (GuildSeedEntry seedEntry : guildSeed.guilds) {
|
||||
|
||||
guild = Guild.createStaticGuild(seedEntry);
|
||||
DbManager.addToCache(guild);
|
||||
|
||||
for (int guildKey : seedEntry.guildKeys)
|
||||
if (_npcGuildsByKey.put(guildKey, guild) != null || guildSeed.noGuildKeys.contains(guildKey)) {
|
||||
Logger.error("Guild seed: guild key " + String.format("%08X", guildKey) + " is used twice");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_npcGuildsByNationKey.put(seedEntry.nationKey, guild) != null) {
|
||||
Logger.error("Guild seed: nation key " + String.format("%08X", seedEntry.nationKey) + " is used twice");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_noGuildKeys.addAll(guildSeed.noGuildKeys);
|
||||
|
||||
Logger.info("NPC guilds: " + guildSeed.guilds.size() + " static, " + _npcGuildsByKey.size() + " guild keys");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Logs each CITY-prop guild key on the map that has no seed entry; known no-guild keys are only counted.
|
||||
|
||||
public static void logMissingGuildKeys() {
|
||||
|
||||
TreeMap<String, TreeSet<String>> missingKeys = new TreeMap<>();
|
||||
ArrayList<CZoneProp> cityProps = new ArrayList<>();
|
||||
int noGuildProps = 0;
|
||||
|
||||
for (Zone zone : ZoneManager.getAllZones()) {
|
||||
|
||||
if (!zone.isStaticWorldObject() || zone.template == null)
|
||||
continue;
|
||||
|
||||
cityProps.clear();
|
||||
collectCityProps(zone.template.zone_prop_info, cityProps);
|
||||
|
||||
for (CZoneProp cityProp : cityProps) {
|
||||
|
||||
if (_npcGuildsByKey.containsKey(cityProp.city_prop_gm_guild))
|
||||
continue;
|
||||
|
||||
if (_noGuildKeys.contains(cityProp.city_prop_gm_guild)) {
|
||||
noGuildProps++;
|
||||
continue;
|
||||
}
|
||||
|
||||
missingKeys.computeIfAbsent(String.format("%08X", cityProp.city_prop_gm_guild), key -> new TreeSet<>())
|
||||
.add(zone.zoneName);
|
||||
}
|
||||
}
|
||||
|
||||
for (String guildKey : missingKeys.keySet())
|
||||
Logger.warn("CITY prop guild key " + guildKey + " has no guild seed entry; zones: " + missingKeys.get(guildKey));
|
||||
|
||||
Logger.info("CITY props: " + noGuildProps + " with no guild, " + missingKeys.size() + " guild keys missing from the seed");
|
||||
}
|
||||
|
||||
private static void collectCityProps(ArrayList<CZoneProp> props, ArrayList<CZoneProp> cityProps) {
|
||||
|
||||
for (CZoneProp prop : props) {
|
||||
|
||||
if (prop.prop_type == mbEnums.ZonePropType.CITY)
|
||||
cityProps.add(prop);
|
||||
|
||||
collectCityProps(prop.prop_content_props, cityProps);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean joinGuild(PlayerCharacter pc, Guild guild, GuildHistoryType historyType) {
|
||||
return joinGuild(pc, guild, 0, historyType);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import engine.net.client.msg.AllianceChangeMsg;
|
||||
import engine.net.client.msg.ErrorPopupMsg;
|
||||
import engine.net.client.msg.UpdateClientAlliancesMsg;
|
||||
import engine.net.client.msg.guild.GuildInfoMsg;
|
||||
import engine.parsers.objects.GuildSeedEntry;
|
||||
import engine.server.world.WorldServer;
|
||||
import org.joda.time.DateTime;
|
||||
import org.pmw.tinylog.Logger;
|
||||
@@ -35,15 +36,15 @@ public class Guild extends AbstractWorldObject {
|
||||
|
||||
private static Guild errantGuild;
|
||||
private static Guild errantNation;
|
||||
private final String name;
|
||||
private final GuildTag guildTag;
|
||||
public final GuildCharterType charter;
|
||||
private final String leadershipType; // Have to see how this is sent to the client
|
||||
private final int repledgeMin;
|
||||
private final int repledgeMax;
|
||||
private final int repledgeKick;
|
||||
private final int teleportMin;
|
||||
private final int teleportMax;
|
||||
private String name;
|
||||
private GuildTag guildTag;
|
||||
public GuildCharterType charter;
|
||||
private String leadershipType; // Have to see how this is sent to the client
|
||||
private int repledgeMin;
|
||||
private int repledgeMax;
|
||||
private int repledgeKick;
|
||||
private int teleportMin;
|
||||
private int teleportMax;
|
||||
private final ConcurrentHashMap<Integer, Condemned> guildCondemned = new ConcurrentHashMap<>();
|
||||
public boolean wooWasModified;
|
||||
public HashMap<Integer, GuildAlliances> guildAlliances = new HashMap<>();
|
||||
@@ -67,6 +68,13 @@ public class Guild extends AbstractWorldObject {
|
||||
private String hash;
|
||||
private boolean ownerIsNPC;
|
||||
|
||||
/**
|
||||
* No-arg Constructor
|
||||
*/
|
||||
private Guild() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* No Id Constructor
|
||||
*/
|
||||
@@ -163,6 +171,32 @@ public class Guild extends AbstractWorldObject {
|
||||
this.hash = rs.getString("hash");
|
||||
}
|
||||
|
||||
// Every NPC guild is its own nation, as live. No hash: it only feeds warehouse records, which are not static-safe yet.
|
||||
public static Guild createStaticGuild(GuildSeedEntry seedEntry) {
|
||||
|
||||
Guild guild = new Guild();
|
||||
|
||||
guild.setObjectUUID(DbManager.nextStaticID());
|
||||
guild.setStaticWorldObject(true);
|
||||
guild.name = seedEntry.name;
|
||||
guild.charter = seedEntry.charter;
|
||||
guild.leadershipType = seedEntry.leadershipType;
|
||||
guild.guildTag = new GuildTag(seedEntry.crest[0], seedEntry.crest[1], seedEntry.crest[2],
|
||||
seedEntry.crest[4], seedEntry.crest[3]);
|
||||
guild.motto = seedEntry.motto;
|
||||
guild.repledgeMin = seedEntry.repledgeMin;
|
||||
guild.repledgeMax = seedEntry.repledgeMax;
|
||||
guild.repledgeKick = seedEntry.repledgeKick;
|
||||
guild.teleportMin = seedEntry.teleportMin;
|
||||
guild.teleportMax = seedEntry.teleportMax;
|
||||
guild.banishList = new ArrayList<>();
|
||||
guild.subGuildList = new ArrayList<>();
|
||||
guild.nation = guild;
|
||||
guild.guildState = GuildManager.calcuateGuildState(guild);
|
||||
|
||||
return guild;
|
||||
}
|
||||
|
||||
public static AbstractCharacter GetGL(Guild guild) {
|
||||
if (guild == null)
|
||||
return null;
|
||||
|
||||
@@ -541,6 +541,13 @@ public class WorldServer {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
Logger.info("Loading NPC guilds");
|
||||
|
||||
if (!GuildManager.loadStaticGuilds(worldMapID)) {
|
||||
Logger.error("ABORT! Guild seed failed to load!");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading all Zones");
|
||||
|
||||
if (!ZoneManager.loadZones(worldMapID)) {
|
||||
@@ -548,6 +555,8 @@ public class WorldServer {
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
GuildManager.logMissingGuildKeys();
|
||||
|
||||
Logger.info("Loading all Buildings");
|
||||
BuildingManager._npcBuildingOwners = DbManager.BuildingQueries.GET_NPC_BUILDING_OWNERS();
|
||||
DbManager.BuildingQueries.GET_ALL_BUILDINGS();
|
||||
|
||||
Reference in new issue
Block a user