diff --git a/src/main/java/foxz/command/CmdDialog.java b/src/main/java/foxz/command/CmdDialog.java index 2bb2d1da3..74ec807fc 100644 --- a/src/main/java/foxz/command/CmdDialog.java +++ b/src/main/java/foxz/command/CmdDialog.java @@ -6,6 +6,7 @@ import foxz.commandhelper.annotations.SubCommand; import foxz.commandhelper.permissions.OpOnly; import foxz.commandhelper.permissions.ParamCheck; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.network.enums.SyncType; import net.minecraft.command.CommandBase; import net.minecraft.entity.player.EntityPlayer; import noppes.npcs.NoppesUtilServer; @@ -90,7 +91,7 @@ public class CmdDialog extends ChMcLogger { ) public boolean reload(String args[]) { new DialogController().load(); - SyncController.syncAllDialogs(); + SyncController.syncAll(SyncType.DIALOG_CATEGORY); return true; } diff --git a/src/main/java/foxz/command/CmdQuest.java b/src/main/java/foxz/command/CmdQuest.java index 5f7a7f1f4..95e4b75f0 100644 --- a/src/main/java/foxz/command/CmdQuest.java +++ b/src/main/java/foxz/command/CmdQuest.java @@ -6,6 +6,7 @@ import foxz.commandhelper.annotations.SubCommand; import foxz.commandhelper.permissions.OpOnly; import foxz.commandhelper.permissions.ParamCheck; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.network.enums.SyncType; import kamkeel.npcs.network.packets.data.AchievementPacket; import kamkeel.npcs.network.packets.data.ChatAlertPacket; import net.minecraft.entity.player.EntityPlayerMP; @@ -170,7 +171,7 @@ public class CmdQuest extends ChMcLogger { ) public boolean reload(String args[]) { new QuestController().load(); - SyncController.syncAllQuests(); + SyncController.syncAll(SyncType.QUEST_CATEGORY); return true; } } diff --git a/src/main/java/kamkeel/npcs/command/DialogCommand.java b/src/main/java/kamkeel/npcs/command/DialogCommand.java index 2392859a4..d9df94c13 100644 --- a/src/main/java/kamkeel/npcs/command/DialogCommand.java +++ b/src/main/java/kamkeel/npcs/command/DialogCommand.java @@ -1,6 +1,7 @@ package kamkeel.npcs.command; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.network.enums.SyncType; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; @@ -93,7 +94,7 @@ public class DialogCommand extends CommandKamkeelBase { ) public void reload(ICommandSender sender, String args[]) { new DialogController().load(); - SyncController.syncAllDialogs(); + SyncController.syncAll(SyncType.DIALOG_CATEGORY); sendResult(sender, "Dialogs Reloaded"); } diff --git a/src/main/java/kamkeel/npcs/command/QuestCommand.java b/src/main/java/kamkeel/npcs/command/QuestCommand.java index 5ccbe5319..7cfd6ebe4 100644 --- a/src/main/java/kamkeel/npcs/command/QuestCommand.java +++ b/src/main/java/kamkeel/npcs/command/QuestCommand.java @@ -2,6 +2,7 @@ package kamkeel.npcs.command; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.network.enums.SyncType; import kamkeel.npcs.network.packets.data.AchievementPacket; import kamkeel.npcs.network.packets.data.ChatAlertPacket; import net.minecraft.command.CommandException; @@ -393,7 +394,7 @@ public class QuestCommand extends CommandKamkeelBase { ) public void reload(ICommandSender sender, String args[]) { new QuestController().load(); - SyncController.syncAllQuests(); + SyncController.syncAll(SyncType.QUEST_CATEGORY); sendResult(sender, "Quests Reloaded"); } } diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index c47c395a6..c352119dd 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -32,6 +32,7 @@ import kamkeel.npcs.controllers.data.ability.type.AbilitySlam; import kamkeel.npcs.controllers.data.ability.type.AbilityTeleport; import kamkeel.npcs.controllers.data.ability.type.AbilityTrap; import kamkeel.npcs.controllers.data.ability.type.AbilityVortex; +import kamkeel.npcs.network.enums.SyncType; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDisc; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDome; @@ -393,7 +394,7 @@ public class AbilityController implements IAbilityHandler { customAbilitiesById.put(uuid, ability); customAbilityRevision++; LogWriter.script("Saved custom ability: " + name + " [" + uuid + "]"); - SyncController.syncAllCustomAbilities(); + SyncController.syncAll(SyncType.CUSTOM_ABILITY); return true; } catch (Exception e) { LogWriter.error("Error saving custom ability: " + name, e); @@ -478,7 +479,7 @@ public class AbilityController implements IAbilityHandler { customAbilityRevision++; LogWriter.script("Deleted custom ability: " + name); - SyncController.syncAllCustomAbilities(); + SyncController.syncAll(SyncType.CUSTOM_ABILITY); return true; } @@ -765,7 +766,7 @@ public class AbilityController implements IAbilityHandler { chainedAbilitiesById.put(uuid, chain); chainedAbilityRevision++; LogWriter.script("Saved chained ability: " + name + " [" + uuid + "]"); - SyncController.syncAllChainedAbilities(); + SyncController.syncAll(SyncType.CHAINED_ABILITY); return true; } catch (Exception e) { LogWriter.error("Error saving chained ability: " + name, e); @@ -845,7 +846,7 @@ public class AbilityController implements IAbilityHandler { chainedAbilityRevision++; LogWriter.script("Deleted chained ability: " + name); - SyncController.syncAllChainedAbilities(); + SyncController.syncAll(SyncType.CHAINED_ABILITY); return true; } diff --git a/src/main/java/kamkeel/npcs/controllers/SyncController.java b/src/main/java/kamkeel/npcs/controllers/SyncController.java index ca2171461..7bf1231ea 100644 --- a/src/main/java/kamkeel/npcs/controllers/SyncController.java +++ b/src/main/java/kamkeel/npcs/controllers/SyncController.java @@ -5,51 +5,26 @@ import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import kamkeel.npcs.addon.DBCAddon; -import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.controllers.sync.SyncRegistry; +import kamkeel.npcs.controllers.sync.handlers.*; import kamkeel.npcs.network.LargeAbstractPacket; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSyncAction; import kamkeel.npcs.network.enums.SyncType; import kamkeel.npcs.network.packets.data.LoginPacket; import kamkeel.npcs.network.packets.data.ProfileSharedQuestPacket; -import kamkeel.npcs.network.packets.data.ability.AbilityCooldownSyncPacket; -import kamkeel.npcs.network.packets.data.ability.AbilityHotbarSyncPacket; -import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket; -import kamkeel.npcs.network.packets.data.large.SyncEffectPacket; import kamkeel.npcs.network.packets.data.large.SyncPacket; import kamkeel.npcs.network.packets.request.party.PartyInfoPacket; import kamkeel.npcs.util.ByteBufUtils; -import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagList; import net.minecraft.server.MinecraftServer; import noppes.npcs.client.ClientCacheHandler; -import noppes.npcs.controllers.CustomEffectController; -import noppes.npcs.controllers.DialogController; -import noppes.npcs.controllers.FactionController; -import noppes.npcs.controllers.MagicController; -import noppes.npcs.controllers.QuestController; -import noppes.npcs.controllers.RecipeController; -import noppes.npcs.controllers.data.CustomEffect; -import noppes.npcs.controllers.data.Dialog; -import noppes.npcs.controllers.data.DialogCategory; -import noppes.npcs.controllers.data.EffectKey; -import noppes.npcs.controllers.data.Faction; -import noppes.npcs.controllers.data.Magic; -import noppes.npcs.controllers.data.MagicCycle; import noppes.npcs.controllers.data.PlayerData; -import noppes.npcs.controllers.data.PlayerEffect; -import noppes.npcs.controllers.data.Quest; -import noppes.npcs.controllers.data.QuestCategory; -import noppes.npcs.controllers.data.RecipeAnvil; -import noppes.npcs.controllers.data.RecipeCarpentry; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -57,6 +32,31 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +/** + * Central sync backend for CNPC+. + * + *

This controller manages two distinct sync lanes:

+ * + * + *

This class retains ownership of:

+ * + */ public class SyncController { public static boolean DEBUG_SYNC_LOGGING = false; @@ -71,45 +71,65 @@ public class SyncController { } } - private static final SyncType[] LOGIN_SYNC_TYPES = new SyncType[]{ - SyncType.FACTION, - SyncType.DIALOG_CATEGORY, - SyncType.QUEST_CATEGORY, - SyncType.WORKBENCH_RECIPES, - SyncType.CARPENTRY_RECIPES, - SyncType.ANVIL_RECIPES, - SyncType.CUSTOM_EFFECTS, - SyncType.MAGIC, - SyncType.MAGIC_CYCLE, - SyncType.CUSTOM_ABILITY, - SyncType.CHAINED_ABILITY - }; + // ═══════════════════════════════════════════════════════════════════════ + // Lifecycle — handler registration and cache initialization + // ═══════════════════════════════════════════════════════════════════════ + /** + * Registers all core CNPC+ sync handlers in deterministic order. + * Called on server start. The registration order determines login + * sync ordering via {@link SyncRegistry#getLoginTypes()}. + * + *

Handler registration order matches the original hard-coded + * LOGIN_SYNC_TYPES array for behavioral parity.

+ */ public static void load() { cacheEntries.clear(); playerSyncState.clear(); + SyncRegistry.clear(); - registerCache(SyncType.FACTION, SyncController::factionsNBT); - registerCache(SyncType.DIALOG_CATEGORY, SyncController::dialogCategoriesNBT); - registerCache(SyncType.QUEST_CATEGORY, SyncController::questCategoriesNBT); - registerCache(SyncType.WORKBENCH_RECIPES, SyncController::workbenchNBT); - registerCache(SyncType.CARPENTRY_RECIPES, SyncController::carpentryNBT); - registerCache(SyncType.ANVIL_RECIPES, SyncController::anvilNBT); - registerCache(SyncType.CUSTOM_EFFECTS, SyncController::customEffectsNBT); - registerCache(SyncType.MAGIC, SyncController::magicsNBT); - registerCache(SyncType.MAGIC_CYCLE, SyncController::magicCyclesNBT); - registerCache(SyncType.CUSTOM_ABILITY, SyncController::customAbilitiesNBT); - registerCache(SyncType.CHAINED_ABILITY, SyncController::chainedAbilitiesNBT); + // Register all core handlers in deterministic order + // Order matches original LOGIN_SYNC_TYPES for login sync ordering + SyncRegistry.register(SyncType.FACTION, new FactionSyncHandler()); + SyncRegistry.register(SyncType.DIALOG, new DialogSyncHandler()); + SyncRegistry.register(SyncType.DIALOG_CATEGORY, new DialogCategorySyncHandler()); + SyncRegistry.register(SyncType.QUEST, new QuestSyncHandler()); + SyncRegistry.register(SyncType.QUEST_CATEGORY, new QuestCategorySyncHandler()); + SyncRegistry.register(SyncType.PLAYERDATA, new PlayerDataSyncHandler()); + SyncRegistry.register(SyncType.WORKBENCH_RECIPES, new WorkbenchRecipeSyncHandler()); + SyncRegistry.register(SyncType.CARPENTRY_RECIPES, new CarpentryRecipeSyncHandler()); + SyncRegistry.register(SyncType.ANVIL_RECIPES, new AnvilRecipeSyncHandler()); + SyncRegistry.register(SyncType.CUSTOM_EFFECTS, new CustomEffectSyncHandler()); + SyncRegistry.register(SyncType.MAGIC, new MagicSyncHandler()); + SyncRegistry.register(SyncType.MAGIC_CYCLE, new MagicCycleSyncHandler()); + SyncRegistry.register(SyncType.CUSTOM_ABILITY, new CustomAbilitySyncHandler()); + SyncRegistry.register(SyncType.CHAINED_ABILITY, new ChainedAbilitySyncHandler()); + + // Register caches for all cached (login-sync) types + // The cache supplier delegates to the handler's serializeAll() + for (SyncType type : SyncRegistry.getLoginTypes()) { + SyncHandler handler = SyncRegistry.getHandler(type); + if (handler != null) + registerCache(type, handler::serializeAll); + } + + SyncRegistry.validateRegistrations(); + SyncRegistry.freeze(); } + // ═══════════════════════════════════════════════════════════════════════ + // Login sync and revision handshake + // ═══════════════════════════════════════════════════════════════════════ + public static void syncPlayer(EntityPlayerMP player) { - SyncController.syncPlayerInternal(player, true); + SyncController.syncPlayer(player, true); } - private static void syncPlayerInternal(EntityPlayerMP player, boolean includePostPackets) { + private static void syncPlayer(EntityPlayerMP player, boolean includePostPackets) { PlayerSyncState state = playerSyncState.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new); - for (SyncType type : LOGIN_SYNC_TYPES) { + // Registry-driven login iteration: iterate only cached types + for (SyncType type : SyncRegistry.getLoginTypes()) { SyncCacheEntry entry = cacheEntries.get(type); if (entry == null) { continue; @@ -117,14 +137,12 @@ public class SyncController { int currentRevision = entry.getRevisionValue(); int lastRevision = state.getRevision(type); - if (lastRevision == currentRevision) { + if (lastRevision == currentRevision) continue; - } - + CachedSyncPayload payload = entry.getPayload(type); - if (payload == null) { + if (payload == null) continue; - } if (lastRevision != payload.getRevision()) { debug("Sending %s data to %s", type, player.getCommandSenderName()); @@ -184,7 +202,7 @@ public class SyncController { private static void sendPostLoginPackets(EntityPlayerMP player) { DBCAddon.instance.syncPlayer(player); - syncPlayerData(player, false); + PlayerDataSyncHandler.getInstance().syncPlayerData(player, false); PartyInfoPacket.sendPartyData(player); ProfileSharedQuestPacket.sendToPlayer(player); @@ -198,12 +216,13 @@ public class SyncController { } private static Map getServerRevisionSnapshot() { + // Registry-driven: iterate login types from registry Map snapshot = new LinkedHashMap<>(); - for (SyncType type : LOGIN_SYNC_TYPES) { + for (SyncType type : SyncRegistry.getLoginTypes()) { SyncCacheEntry entry = cacheEntries.get(type); - if (entry != null) { + if (entry != null) snapshot.put(type, entry.getRevisionValue()); - } + } return snapshot; } @@ -228,634 +247,75 @@ public class SyncController { return entry == null ? -1 : entry.getRevisionValue(); } - public static NBTTagCompound workbenchNBT() { - RecipeController controller = RecipeController.Instance; - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (RecipeCarpentry recipe : controller.globalRecipes.values()) { - list.appendTag(recipe.writeNBT(false)); - } - compound.setTag("recipes", list); - return compound; - } + // ═══════════════════════════════════════════════════════════════════════ + // Generic sync dispatch — registry-driven, no family-specific logic + // ═══════════════════════════════════════════════════════════════════════ - public static NBTTagCompound carpentryNBT() { - RecipeController controller = RecipeController.Instance; - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (RecipeCarpentry recipe : controller.carpentryRecipes.values()) { - list.appendTag(recipe.writeNBT(false)); - } - compound.setTag("recipes", list); - return compound; - } - - public static NBTTagCompound anvilNBT() { - RecipeController controller = RecipeController.Instance; - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (RecipeAnvil recipe : controller.anvilRecipes.values()) { - list.appendTag(recipe.writeNBT(false)); - } - compound.setTag("recipes", list); - return compound; - } - - public static NBTTagCompound factionsNBT() { - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (Faction faction : FactionController.getInstance().factions.values()) { - NBTTagCompound factioNBT = new NBTTagCompound(); - faction.writeNBT(factioNBT); - list.appendTag(factioNBT); - } - compound.setTag("Factions", list); - return compound; - } - - public static NBTTagCompound dialogCategoriesNBT() { - NBTTagCompound compound = new NBTTagCompound(); - NBTTagList categoryList = new NBTTagList(); - for (DialogCategory category : DialogController.Instance.categories.values()) { - NBTTagCompound questCompound = new NBTTagCompound(); - NBTTagList dialogList = new NBTTagList(); - for (int dialogID : category.dialogs.keySet()) { - Dialog quest = category.dialogs.get(dialogID); - dialogList.appendTag(quest.writeToNBT(new NBTTagCompound())); - } - questCompound.setTag("Data", dialogList); - questCompound.setTag("CatNBT", category.writeSmallNBT(new NBTTagCompound())); - categoryList.appendTag(questCompound); - } - compound.setTag("DialogCategories", categoryList); - return compound; - } - - public static NBTTagCompound questCategoriesNBT() { - NBTTagCompound compound = new NBTTagCompound(); - NBTTagList categoryList = new NBTTagList(); - for (QuestCategory category : QuestController.Instance.categories.values()) { - NBTTagCompound questCompound = new NBTTagCompound(); - ; - NBTTagList questList = new NBTTagList(); - for (int questID : category.quests.keySet()) { - Quest quest = category.quests.get(questID); - questList.appendTag(quest.writeToNBT(new NBTTagCompound())); - } - questCompound.setTag("Data", questList); - questCompound.setTag("CatNBT", category.writeSmallNBT(new NBTTagCompound())); - categoryList.appendTag(questCompound); - } - compound.setTag("QuestCategories", categoryList); - return compound; - } - - public static NBTTagCompound customEffectsNBT() { - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (CustomEffect effect : CustomEffectController.getInstance().getCustomEffects().values()) { - list.appendTag(effect.writeToNBT(false)); - } - compound.setTag("Data", list); - return compound; - } - - public static NBTTagCompound magicsNBT() { - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (Magic magic : MagicController.getInstance().magics.values()) { - NBTTagCompound magicCompound = new NBTTagCompound(); - magic.writeNBT(magicCompound); - list.appendTag(magicCompound); - } - compound.setTag("Data", list); - return compound; - } - - public static NBTTagCompound magicCyclesNBT() { - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (MagicCycle cycle : MagicController.getInstance().cycles.values()) { - NBTTagCompound cycleCompound = new NBTTagCompound(); - cycle.writeNBT(cycleCompound); - list.appendTag(cycleCompound); - } - compound.setTag("Data", list); - return compound; - } - - public static NBTTagCompound customAbilitiesNBT() { - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (Map.Entry entry : AbilityController.Instance.getCustomAbilities().entrySet()) { - NBTTagCompound abilityNBT = entry.getValue().writeNBT(false); - abilityNBT.setString("CustomAbilityId", entry.getKey()); - list.appendTag(abilityNBT); - } - compound.setTag("Data", list); - return compound; - } - - public static void syncAllCustomAbilities() { - CachedSyncPayload payload = rebuildNow(SyncType.CUSTOM_ABILITY); + /** + * Generic full-broadcast helper. Rebuilds the cache for the given type + * and sends to all connected players. + */ + public static void syncAll(SyncType type) { + CachedSyncPayload payload = rebuildNow(type); if (payload == null) { return; } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CUSTOM_ABILITY, payload)); - updateAllPlayerRevisions(SyncType.CUSTOM_ABILITY, payload.getRevision()); + PacketHandler.Instance.sendToAll(new SyncPacket(type, payload)); + updateAllPlayerRevisions(type, payload.getRevision()); } - public static NBTTagCompound chainedAbilitiesNBT() { - NBTTagList list = new NBTTagList(); - NBTTagCompound compound = new NBTTagCompound(); - for (Map.Entry entry : AbilityController.Instance.getChainedAbilities().entrySet()) { - NBTTagCompound chainNBT = entry.getValue().writeNBT(false); - chainNBT.setString("ChainedAbilityId", entry.getKey()); - list.appendTag(chainNBT); - } - compound.setTag("Data", list); - return compound; - } - - public static void syncAllChainedAbilities() { - CachedSyncPayload payload = rebuildNow(SyncType.CHAINED_ABILITY); - if (payload == null) { - return; - } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CHAINED_ABILITY, payload)); - updateAllPlayerRevisions(SyncType.CHAINED_ABILITY, payload.getRevision()); - } - - public static void syncPlayerData(EntityPlayerMP player, boolean update) { - PlayerData data = PlayerData.get(player); - if (data != null) { - if (update) { - PacketHandler.Instance.sendToPlayer(new SyncPacket( - SyncType.PLAYERDATA, - EnumSyncAction.UPDATE, - -1, - data.getSyncNBT()) - , player); - } else { - PacketHandler.Instance.sendToPlayer(new SyncPacket( - SyncType.PLAYERDATA, - EnumSyncAction.RELOAD, - -1, - data.getSyncNBTFull()), player); - } - } - } + // ═══════════════════════════════════════════════════════════════════════ + // UPDATE and REMOVE dispatch — generic, registry-driven + // ═══════════════════════════════════════════════════════════════════════ public static void syncRemove(SyncType syncType, int id) { Map revisions = invalidateCaches(syncType); int revision = revisions.getOrDefault(syncType, getCurrentRevision(syncType)); - PacketHandler.Instance.sendToAll(new SyncPacket( - syncType, - EnumSyncAction.REMOVE, - id, - revision, - new NBTTagCompound() - )); + PacketHandler.Instance.sendToAll(new SyncPacket(syncType, EnumSyncAction.REMOVE, id, revision, new NBTTagCompound())); updateAllPlayerRevisions(revisions); } - public static void syncAllDialogs() { - CachedSyncPayload payload = rebuildNow(SyncType.DIALOG_CATEGORY); - if (payload == null) { - return; - } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.DIALOG_CATEGORY, payload)); - updateAllPlayerRevisions(SyncType.DIALOG_CATEGORY, payload.getRevision()); - } - - public static void syncAllQuests() { - CachedSyncPayload payload = rebuildNow(SyncType.QUEST_CATEGORY); - if (payload == null) { - return; - } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.QUEST_CATEGORY, payload)); - updateAllPlayerRevisions(SyncType.QUEST_CATEGORY, payload.getRevision()); - } - - public static void syncAllWorkbenchRecipes() { - CachedSyncPayload payload = rebuildNow(SyncType.WORKBENCH_RECIPES); - if (payload == null) { - return; - } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.WORKBENCH_RECIPES, payload)); - updateAllPlayerRevisions(SyncType.WORKBENCH_RECIPES, payload.getRevision()); - } - - public static void syncAllCarpentryRecipes() { - CachedSyncPayload payload = rebuildNow(SyncType.CARPENTRY_RECIPES); - if (payload == null) { - return; - } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CARPENTRY_RECIPES, payload)); - updateAllPlayerRevisions(SyncType.CARPENTRY_RECIPES, payload.getRevision()); - } - - public static void syncAllAnvilRecipes() { - CachedSyncPayload payload = rebuildNow(SyncType.ANVIL_RECIPES); - if (payload == null) { - return; - } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.ANVIL_RECIPES, payload)); - updateAllPlayerRevisions(SyncType.ANVIL_RECIPES, payload.getRevision()); - } - - public static void syncAllCustomEffects() { - CachedSyncPayload payload = rebuildNow(SyncType.CUSTOM_EFFECTS); - if (payload == null) { - return; - } - PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CUSTOM_EFFECTS, payload)); - updateAllPlayerRevisions(SyncType.CUSTOM_EFFECTS, payload.getRevision()); - } - - @SideOnly(Side.CLIENT) - public static void clientSync(SyncType syncType, int revision, NBTTagCompound fullCompound) { - if (syncType == SyncType.FACTION) { - NBTTagList list = fullCompound.getTagList("Factions", 10); - for (int i = 0; i < list.tagCount(); i++) { - Faction faction = new Faction(); - faction.readNBT(list.getCompoundTagAt(i)); - FactionController.getInstance().factionsSync.put(faction.id, faction); - } - FactionController.getInstance().factions = FactionController.getInstance().factionsSync; - FactionController.getInstance().factionsSync = new HashMap(); - } else if (syncType == SyncType.DIALOG_CATEGORY) { - if (!fullCompound.hasNoTags()) { - NBTTagList categories = fullCompound.getTagList("DialogCategories", 10); - for (int j = 0; j < fullCompound.getTagList("DialogCategories", 10).tagCount(); j++) { - NBTTagCompound categoryCompound = categories.getCompoundTagAt(j); - if (categoryCompound.hasNoTags()) - continue; - - DialogCategory category = new DialogCategory(); - category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); - NBTTagList dialogList = categoryCompound.getTagList("Data", 10); - if (DialogController.Instance.categoriesSync.containsKey(category.id)) { - category = DialogController.Instance.categoriesSync.get(category.id); - category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); - } - for (int i = 0; i < dialogList.tagCount(); i++) { - Dialog dialog = new Dialog(); - dialog.readNBT(dialogList.getCompoundTagAt(i)); - dialog.category = category; - category.dialogs.put(dialog.id, dialog); - } - DialogController.Instance.categoriesSync.put(category.id, category); - } - } - - HashMap dialogs = new HashMap(); - for (DialogCategory category : DialogController.Instance.categoriesSync.values()) { - for (Dialog dialog : category.dialogs.values()) { - dialogs.put(dialog.id, dialog); - } - } - - DialogController.Instance.categories = DialogController.Instance.categoriesSync; - DialogController.Instance.dialogs = dialogs; - DialogController.Instance.categoriesSync = new HashMap(); - } else if (syncType == SyncType.QUEST_CATEGORY) { - if (!fullCompound.hasNoTags()) { - NBTTagList categories = fullCompound.getTagList("QuestCategories", 10); - for (int j = 0; j < fullCompound.getTagList("QuestCategories", 10).tagCount(); j++) { - NBTTagCompound categoryCompound = categories.getCompoundTagAt(j); - if (categoryCompound.hasNoTags()) - continue; - - QuestCategory category = new QuestCategory(); - category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); - NBTTagList questList = categoryCompound.getTagList("Data", 10); - if (QuestController.Instance.categoriesSync.containsKey(category.id)) { - category = QuestController.Instance.categoriesSync.get(category.id); - category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); - } - for (int i = 0; i < questList.tagCount(); i++) { - Quest quest = new Quest(); - quest.readNBT(questList.getCompoundTagAt(i)); - quest.category = category; - category.quests.put(quest.id, quest); - } - QuestController.Instance.categoriesSync.put(category.id, category); - } - } - - HashMap quests = new HashMap(); - for (QuestCategory category : QuestController.Instance.categoriesSync.values()) { - for (Quest quest : category.quests.values()) { - quests.put(quest.id, quest); - } - } - - QuestController.Instance.categories = QuestController.Instance.categoriesSync; - QuestController.Instance.quests = quests; - QuestController.Instance.categoriesSync = new HashMap(); - } else if (syncType == SyncType.PLAYERDATA) { - ClientCacheHandler.playerData.setSyncNBTFull(fullCompound); - } else if (syncType == SyncType.MAGIC) { - NBTTagList list = fullCompound.getTagList("Data", 10); - MagicController mc = MagicController.getInstance(); - mc.magicSync.clear(); - for (int i = 0; i < list.tagCount(); i++) { - Magic magic = new Magic(); - magic.readNBT(list.getCompoundTagAt(i)); - mc.magicSync.put(magic.id, magic); - } - - mc.magics.clear(); - mc.magics.putAll(mc.magicSync); - mc.magicSync.clear(); - } else if (syncType == SyncType.MAGIC_CYCLE) { - NBTTagList list = fullCompound.getTagList("Data", 10); - MagicController mc = MagicController.getInstance(); - mc.cyclesSync.clear(); - for (int i = 0; i < list.tagCount(); i++) { - MagicCycle cycle = new MagicCycle(); - cycle.readNBT(list.getCompoundTagAt(i)); - mc.cyclesSync.put(cycle.id, cycle); - } - - mc.cycles.clear(); - mc.cycles.putAll(mc.cyclesSync); - mc.cyclesSync.clear(); - } else if (syncType == SyncType.WORKBENCH_RECIPES) { - NBTTagList list = fullCompound.getTagList("recipes", 10); - if (list == null) - return; - - for (int i = 0; i < list.tagCount(); i++) { - NBTTagCompound recipeCompound = list.getCompoundTagAt(i); - RecipeCarpentry recipe = RecipeCarpentry.create(recipeCompound); - recipe.readNBT(recipeCompound); - RecipeController.syncRecipes.put(recipe.id, recipe); - } - - RecipeController.reloadGlobalRecipes(RecipeController.syncRecipes); - RecipeController.syncRecipes = new HashMap(); - } else if (syncType == SyncType.CARPENTRY_RECIPES) { - NBTTagList list = fullCompound.getTagList("recipes", 10); - if (list == null) - return; - - for (int i = 0; i < list.tagCount(); i++) { - NBTTagCompound recipeCompound = list.getCompoundTagAt(i); - RecipeCarpentry recipe = RecipeCarpentry.create(recipeCompound); - recipe.readNBT(recipeCompound); - RecipeController.syncRecipes.put(recipe.id, recipe); - } - - RecipeController.Instance.carpentryRecipes = RecipeController.syncRecipes; - RecipeController.syncRecipes = new HashMap(); - } else if (syncType == SyncType.ANVIL_RECIPES) { - NBTTagList list = fullCompound.getTagList("recipes", 10); - if (list == null) - return; - - for (int i = 0; i < list.tagCount(); i++) { - RecipeAnvil recipe = new RecipeAnvil(); - recipe.readNBT(list.getCompoundTagAt(i)); - RecipeController.syncAnvilRecipes.put(recipe.id, recipe); - } - - RecipeController.Instance.anvilRecipes = RecipeController.syncAnvilRecipes; - RecipeController.syncAnvilRecipes = new HashMap(); - } else if (syncType == SyncType.CUSTOM_EFFECTS) { - NBTTagList list = fullCompound.getTagList("Data", 10); - CustomEffectController ce = CustomEffectController.getInstance(); - ce.customEffectsSync.clear(); - for (int i = 0; i < list.tagCount(); i++) { - CustomEffect effect = new CustomEffect(); - effect.readFromNBT(list.getCompoundTagAt(i)); - ClientCacheHandler.getImageData(effect.icon); - ce.customEffectsSync.put(effect.id, effect); - } - - ce.getCustomEffects().clear(); - ce.getCustomEffects().putAll(ce.customEffectsSync); - ce.customEffectsSync.clear(); - } else if (syncType == SyncType.CUSTOM_ABILITY) { - NBTTagList list = fullCompound.getTagList("Data", 10); - LinkedHashMap sync = new LinkedHashMap<>(); - for (int i = 0; i < list.tagCount(); i++) { - NBTTagCompound nbt = list.getCompoundTagAt(i); - String name = nbt.getString("CustomAbilityId"); - Ability ability = AbilityController.Instance.fromNBT(nbt); - if (ability != null) { - ability.setName(name); - sync.put(name, ability); - } - } - AbilityController.Instance.setCustomAbilities(sync); - } else if (syncType == SyncType.CHAINED_ABILITY) { - NBTTagList list = fullCompound.getTagList("Data", 10); - LinkedHashMap sync = new LinkedHashMap<>(); - for (int i = 0; i < list.tagCount(); i++) { - NBTTagCompound nbt = list.getCompoundTagAt(i); - String id = nbt.getString("ChainedAbilityId"); - ChainedAbility chain = new ChainedAbility(); - chain.readNBT(nbt); - chain.setName(id); - sync.put(id, chain); - } - AbilityController.Instance.setChainedAbilities(sync); - } - - ClientCacheHandler.updateClientRevision(syncType, revision); - } - - @SideOnly(Side.CLIENT) - public static void clientUpdate(SyncType syncType, int category_id, int revision, NBTTagCompound compound) { - if (syncType == SyncType.FACTION) { - Faction faction = new Faction(); - faction.readNBT(compound); - FactionController.getInstance().factions.put(faction.id, faction); - } else if (syncType == SyncType.DIALOG) { - DialogCategory category = DialogController.Instance.categories.get(category_id); - Dialog dialog = new Dialog(); - dialog.category = category; - dialog.readNBT(compound); - DialogController.Instance.dialogs.put(dialog.id, dialog); - category.dialogs.put(dialog.id, dialog); - } else if (syncType == SyncType.QUEST) { - QuestCategory category = QuestController.Instance.categories.get(category_id); - Quest quest = new Quest(); - quest.category = category; - quest.readNBT(compound); - QuestController.Instance.quests.put(quest.id, quest); - category.quests.put(quest.id, quest); - } else if (syncType == SyncType.QUEST_CATEGORY) { - QuestCategory category = new QuestCategory(); - category.readSmallNBT(compound.getCompoundTag("CatNBT")); - NBTTagList list = compound.getTagList("Data", 10); - if (QuestController.Instance.categoriesSync.containsKey(category.id)) { - category = QuestController.Instance.categoriesSync.get(category.id); - category.readSmallNBT(compound.getCompoundTag("CatNBT")); - } - for (int i = 0; i < list.tagCount(); i++) { - Quest quest = new Quest(); - quest.readNBT(list.getCompoundTagAt(i)); - quest.category = category; - category.quests.put(quest.id, quest); - } - QuestController.Instance.categories.put(category.id, category); - } else if (syncType == SyncType.DIALOG_CATEGORY) { - DialogCategory category = new DialogCategory(); - category.readSmallNBT(compound.getCompoundTag("CatNBT")); - NBTTagList list = compound.getTagList("Data", 10); - if (DialogController.Instance.categoriesSync.containsKey(category.id)) { - category = DialogController.Instance.categoriesSync.get(category.id); - category.readSmallNBT(compound.getCompoundTag("CatNBT")); - } - for (int i = 0; i < list.tagCount(); i++) { - Dialog dialog = new Dialog(); - dialog.readNBT(list.getCompoundTagAt(i)); - dialog.category = category; - category.dialogs.put(dialog.id, dialog); - } - DialogController.Instance.categories.put(category.id, category); - } else if (syncType == SyncType.MAGIC_CYCLE) { - MagicCycle cycle = new MagicCycle(); - cycle.readNBT(compound); - MagicController.getInstance().cycles.put(cycle.id, cycle); - } else if (syncType == SyncType.MAGIC) { - Magic magic = new Magic(); - magic.readNBT(compound); - MagicController.getInstance().magics.put(magic.id, magic); - } else if (syncType == SyncType.PLAYERDATA) { - ClientCacheHandler.playerData.setSyncNBT(compound); - } else if (syncType == SyncType.CUSTOM_EFFECTS) { - CustomEffect effect = new CustomEffect(); - effect.readFromNBT(compound); - ClientCacheHandler.getImageData(effect.icon); - - CustomEffectController.Instance.getCustomEffects().put(effect.id, effect); - } - - ClientCacheHandler.updateClientRevision(syncType, revision); - } - public static void syncUpdate(SyncType type, int cat, NBTTagCompound compound) { Map revisions = invalidateCaches(type); int revision = revisions.getOrDefault(type, getCurrentRevision(type)); - PacketHandler.Instance.sendToAll(new SyncPacket( - type, - EnumSyncAction.UPDATE, - cat, - revision, - compound - )); + PacketHandler.Instance.sendToAll(new SyncPacket(type, EnumSyncAction.UPDATE, cat, revision, compound)); updateAllPlayerRevisions(revisions); } + // ═══════════════════════════════════════════════════════════════════════ + // Client-side dispatch — registry-driven handler delegation + // ═══════════════════════════════════════════════════════════════════════ - public static NBTTagCompound updateQuestCat(QuestCategory questCategory) { - NBTTagCompound questCompound = new NBTTagCompound(); - NBTTagList questList = new NBTTagList(); - for (int questID : questCategory.quests.keySet()) { - Quest quest = questCategory.quests.get(questID); - questList.appendTag(quest.writeToNBT(new NBTTagCompound())); - } - questCompound.setTag("Data", questList); - questCompound.setTag("CatNBT", questCategory.writeSmallNBT(new NBTTagCompound())); - return questCompound; + @SideOnly(Side.CLIENT) + public static void clientSync(SyncType syncType, int revision, NBTTagCompound fullCompound) { + SyncHandler handler = SyncRegistry.getHandler(syncType); + if (handler != null) + handler.clientHandleReload(fullCompound); + + ClientCacheHandler.updateClientRevision(syncType, revision); } - public static NBTTagCompound updateDialogCat(DialogCategory dialogCategory) { - NBTTagCompound dialogCompound = new NBTTagCompound(); - NBTTagList dialogList = new NBTTagList(); - for (int questID : dialogCategory.dialogs.keySet()) { - Dialog dialog = dialogCategory.dialogs.get(questID); - dialogList.appendTag(dialog.writeToNBT(new NBTTagCompound())); - } - dialogCompound.setTag("Data", dialogList); - dialogCompound.setTag("CatNBT", dialogCategory.writeSmallNBT(new NBTTagCompound())); - return dialogCompound; + @SideOnly(Side.CLIENT) + public static void clientUpdate(SyncType syncType, int category_id, int revision, NBTTagCompound compound) { + SyncHandler handler = SyncRegistry.getHandler(syncType); + if (handler != null) + handler.clientHandleUpdate(compound, category_id); + + ClientCacheHandler.updateClientRevision(syncType, revision); } @SideOnly(Side.CLIENT) public static void clientSyncRemove(SyncType syncType, int id, int revision) { - if (syncType == SyncType.FACTION) { - FactionController.getInstance().factions.remove(id); - } else if (syncType == SyncType.DIALOG) { - Dialog dialog = DialogController.Instance.dialogs.remove(id); - if (dialog != null) { - dialog.category.dialogs.remove(id); - } - } else if (syncType == SyncType.DIALOG_CATEGORY) { - DialogCategory dialogCategory = DialogController.Instance.categories.remove(id); - if (dialogCategory != null) { - DialogController.Instance.dialogs.keySet().removeAll(dialogCategory.dialogs.keySet()); - } - } else if (syncType == SyncType.QUEST) { - Quest quest = QuestController.Instance.quests.remove(id); - if (quest != null) { - quest.category.quests.remove(id); - } - } else if (syncType == SyncType.QUEST_CATEGORY) { - QuestCategory questCategory = QuestController.Instance.categories.remove(id); - if (questCategory != null) { - QuestController.Instance.quests.keySet().removeAll(questCategory.quests.keySet()); - } - } else if (syncType == SyncType.MAGIC) { - for (MagicCycle cycle : MagicController.getInstance().cycles.values()) { - cycle.associations.remove(id); - } - MagicController.getInstance().cycles.remove(id); - } else if (syncType == SyncType.MAGIC_CYCLE) { - MagicController.getInstance().cycles.remove(id); - } else if (syncType == SyncType.CUSTOM_EFFECTS) { - CustomEffectController.Instance.getCustomEffects().remove(id); - } - + SyncHandler handler = SyncRegistry.getHandler(syncType); + if (handler != null) + handler.clientHandleRemove(id); + ClientCacheHandler.updateClientRevision(syncType, revision); } - public static void syncEffects(EntityPlayerMP playerMP) { - ConcurrentHashMap playerEffects = CustomEffectController.getInstance().getPlayerEffects(playerMP); - PlayerData playerData = PlayerData.get(playerMP); - playerData.effectData.setEffects(playerEffects); - - NBTTagCompound compound = playerData.getPlayerEffects(); - PacketHandler.Instance.sendToPlayer(new SyncEffectPacket(compound), playerMP); - } - - /** - * Full sync of all player ability state to the client: unlocked abilities, - * selection, toggles, hotbar layout, and cooldowns. - * Used for entity reconstruction and other full-refresh scenarios. - */ - public static void syncAbilities(EntityPlayerMP playerMP) { - PlayerData playerData = PlayerData.get(playerMP); - if (playerData == null) return; - - PlayerAbilitySyncPacket.sendToPlayer(playerMP); - AbilityHotbarSyncPacket.sendToPlayer(playerMP); - AbilityCooldownSyncPacket.sendToPlayer(playerMP); - } - - /** - * Lightweight sync of cooldown state only (global + per-ability). - * Called periodically (every 10 ticks) to keep the client's cooldown - * display accurate. Cooldowns are transient and not part of NBT sync. - */ - public static void syncAbilityCooldowns(EntityPlayerMP playerMP) { - PlayerData playerData = PlayerData.get(playerMP); - if (playerData == null) return; - - AbilityCooldownSyncPacket.sendToPlayer(playerMP); - } - - @SideOnly(Side.CLIENT) - public static void clientSyncEffects(NBTTagCompound compound) { - PlayerData playerData = PlayerData.get(Minecraft.getMinecraft().thePlayer); - if (playerData != null) { - playerData.setPlayerEffects(compound); - } - } + // ═══════════════════════════════════════════════════════════════════════ + // Cache and revision internals + // ═══════════════════════════════════════════════════════════════════════ private static void registerCache(SyncType type, Supplier supplier) { cacheEntries.put(type, new SyncCacheEntry(supplier)); @@ -863,15 +323,19 @@ public class SyncController { private static CachedSyncPayload rebuildNow(SyncType type) { SyncCacheEntry entry = cacheEntries.get(type); - if (entry == null) { + if (entry == null) return null; - } + entry.invalidate(); return entry.getPayload(type); } + /** + * Registry-driven invalidation: delegates to handler-owned + * invalidation targets via {@link SyncRegistry#getInvalidationTargets}. + */ public static Map invalidateCaches(SyncType type) { - Set targets = getInvalidationTargets(type); + Set targets = SyncRegistry.getInvalidationTargets(type); Map revisions = new LinkedHashMap<>(); for (SyncType target : targets) { SyncCacheEntry entry = cacheEntries.get(target); @@ -883,55 +347,6 @@ public class SyncController { return revisions; } - private static Set getInvalidationTargets(SyncType type) { - if (type == SyncType.DIALOG || type == SyncType.DIALOG_CATEGORY) { - Set set = new HashSet<>(); - set.add(SyncType.DIALOG_CATEGORY); - return set; - } else if (type == SyncType.QUEST || type == SyncType.QUEST_CATEGORY) { - Set set = new HashSet<>(); - set.add(SyncType.QUEST_CATEGORY); - return set; - } else if (type == SyncType.FACTION) { - Set set = new HashSet<>(); - set.add(SyncType.FACTION); - return set; - } else if (type == SyncType.MAGIC) { - Set set = new HashSet<>(); - set.add(SyncType.MAGIC); - return set; - } else if (type == SyncType.MAGIC_CYCLE) { - Set set = new HashSet<>(); - set.add(SyncType.MAGIC_CYCLE); - return set; - } else if (type == SyncType.WORKBENCH_RECIPES) { - Set set = new HashSet<>(); - set.add(SyncType.WORKBENCH_RECIPES); - return set; - } else if (type == SyncType.CARPENTRY_RECIPES) { - Set set = new HashSet<>(); - set.add(SyncType.CARPENTRY_RECIPES); - return set; - } else if (type == SyncType.ANVIL_RECIPES) { - Set set = new HashSet<>(); - set.add(SyncType.ANVIL_RECIPES); - return set; - } else if (type == SyncType.CUSTOM_EFFECTS) { - Set set = new HashSet<>(); - set.add(SyncType.CUSTOM_EFFECTS); - return set; - } else if (type == SyncType.CUSTOM_ABILITY) { - Set set = new HashSet<>(); - set.add(SyncType.CUSTOM_ABILITY); - return set; - } else if (type == SyncType.CHAINED_ABILITY) { - Set set = new HashSet<>(); - set.add(SyncType.CHAINED_ABILITY); - return set; - } - return Collections.emptySet(); - } - private static void updateAllPlayerRevisions(SyncType type, int revision) { if (revision < 0) { return; @@ -952,6 +367,10 @@ public class SyncController { } } + // ═══════════════════════════════════════════════════════════════════════ + // Inner classes — cache payload and player revision state + // ═══════════════════════════════════════════════════════════════════════ + public static final class CachedSyncPayload { private final int revision; private final byte[] payload; diff --git a/src/main/java/kamkeel/npcs/controllers/SyncControllerOld.java b/src/main/java/kamkeel/npcs/controllers/SyncControllerOld.java new file mode 100644 index 000000000..987059c80 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/SyncControllerOld.java @@ -0,0 +1,1058 @@ +package kamkeel.npcs.controllers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import kamkeel.npcs.addon.DBCAddon; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.network.LargeAbstractPacket; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.enums.EnumSyncAction; +import kamkeel.npcs.network.enums.SyncType; +import kamkeel.npcs.network.packets.data.LoginPacket; +import kamkeel.npcs.network.packets.data.ProfileSharedQuestPacket; +import kamkeel.npcs.network.packets.data.ability.AbilityCooldownSyncPacket; +import kamkeel.npcs.network.packets.data.ability.AbilityHotbarSyncPacket; +import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket; +import kamkeel.npcs.network.packets.data.large.SyncEffectPacket; +import kamkeel.npcs.network.packets.data.large.SyncPacket; +import kamkeel.npcs.network.packets.request.party.PartyInfoPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.server.MinecraftServer; +import noppes.npcs.client.ClientCacheHandler; +import noppes.npcs.controllers.*; +import noppes.npcs.controllers.data.*; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +public class SyncControllerOld { + + public static boolean DEBUG_SYNC_LOGGING = false; + + private static final Map cacheEntries = new LinkedHashMap<>(); + private static final ConcurrentHashMap playerSyncState = new ConcurrentHashMap<>(); + + + private static void debug(String message, Object... args) { + if (DEBUG_SYNC_LOGGING) { + System.out.println("[SyncController] " + String.format(message, args)); + } + } + + private static final SyncType[] LOGIN_SYNC_TYPES = new SyncType[]{ + SyncType.FACTION, + SyncType.DIALOG_CATEGORY, + SyncType.QUEST_CATEGORY, + SyncType.WORKBENCH_RECIPES, + SyncType.CARPENTRY_RECIPES, + SyncType.ANVIL_RECIPES, + SyncType.CUSTOM_EFFECTS, + SyncType.MAGIC, + SyncType.MAGIC_CYCLE, + SyncType.CUSTOM_ABILITY, + SyncType.CHAINED_ABILITY + }; + + public static void load() { + cacheEntries.clear(); + playerSyncState.clear(); + + registerCache(SyncType.FACTION, SyncControllerOld::factionsNBT); + registerCache(SyncType.DIALOG_CATEGORY, SyncControllerOld::dialogCategoriesNBT); + registerCache(SyncType.QUEST_CATEGORY, SyncControllerOld::questCategoriesNBT); + registerCache(SyncType.WORKBENCH_RECIPES, SyncControllerOld::workbenchNBT); + registerCache(SyncType.CARPENTRY_RECIPES, SyncControllerOld::carpentryNBT); + registerCache(SyncType.ANVIL_RECIPES, SyncControllerOld::anvilNBT); + registerCache(SyncType.CUSTOM_EFFECTS, SyncControllerOld::customEffectsNBT); + registerCache(SyncType.MAGIC, SyncControllerOld::magicsNBT); + registerCache(SyncType.MAGIC_CYCLE, SyncControllerOld::magicCyclesNBT); + registerCache(SyncType.CUSTOM_ABILITY, SyncControllerOld::customAbilitiesNBT); + registerCache(SyncType.CHAINED_ABILITY, SyncControllerOld::chainedAbilitiesNBT); + } + + public static void syncPlayer(EntityPlayerMP player) { + SyncControllerOld.syncPlayerInternal(player, true); + } + + private static void syncPlayerInternal(EntityPlayerMP player, boolean includePostPackets) { + PlayerSyncState state = playerSyncState.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new); + + for (SyncType type : LOGIN_SYNC_TYPES) { + SyncCacheEntry entry = cacheEntries.get(type); + if (entry == null) { + continue; + } + + int currentRevision = entry.getRevisionValue(); + int lastRevision = state.getRevision(type); + if (lastRevision == currentRevision) { + continue; + } + + CachedSyncPayload payload = entry.getPayload(type); + if (payload == null) { + continue; + } + + if (lastRevision != payload.getRevision()) { + debug("Sending %s data to %s", type, player.getCommandSenderName()); + PacketHandler.Instance.sendToPlayer(new SyncPacket(type, payload), player); + state.updateRevision(type, payload.getRevision()); + } + } + + if (includePostPackets) { + sendPostLoginPackets(player); + } + } + + public static void beginLogin(EntityPlayerMP player) { + playerSyncState.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new); + PacketHandler.Instance.sendToPlayer( + new LoginPacket(getServerCacheKey(), getServerRevisionSnapshot()), + player + ); + } + + public static void handleClientRevisionReport( + EntityPlayerMP player, + String serverKey, + String previousServerKey, + Map clientRevisions + ) { + String currentServerKey = getServerCacheKey(); + PlayerSyncState state = playerSyncState.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new); + + if (!currentServerKey.equals(serverKey)) { + state.reset(); + syncPlayer(player); + return; + } + + if (!currentServerKey.isEmpty()) { + if (previousServerKey == null || !currentServerKey.equals(previousServerKey)) { + state.reset(); + syncPlayer(player); + return; + } + } + + if (clientRevisions == null || clientRevisions.isEmpty()) { + debug( + "Client %s reported no cached revisions; forcing full sync", + player.getCommandSenderName() + ); + state.reset(); + } else { + state.applyHandshake(clientRevisions); + } + + syncPlayer(player); + } + + private static void sendPostLoginPackets(EntityPlayerMP player) { + DBCAddon.instance.syncPlayer(player); + syncPlayerData(player, false); + PartyInfoPacket.sendPartyData(player); + ProfileSharedQuestPacket.sendToPlayer(player); + + // Sync skin overlays after full handshake to ensure the client is ready. + // Overlays are not part of getSyncNBTFull(), and packets sent during + // PlayerLoggedInEvent can arrive before the client entity exists. + PlayerData data = PlayerData.get(player); + if (data != null) { + data.skinOverlays.updateClient(); + } + } + + private static Map getServerRevisionSnapshot() { + Map snapshot = new LinkedHashMap<>(); + for (SyncType type : LOGIN_SYNC_TYPES) { + SyncCacheEntry entry = cacheEntries.get(type); + if (entry != null) { + snapshot.put(type, entry.getRevisionValue()); + } + } + return snapshot; + } + + private static final String SERVER_IDENTITY_KEY = UUID.randomUUID().toString(); + + private static String getServerCacheKey() { + MinecraftServer server = MinecraftServer.getServer(); + if (server == null) { + return ""; + } + + if (!server.isDedicatedServer()) { + return ""; + } + + return SERVER_IDENTITY_KEY; + } + + public static int getCurrentRevision(SyncType type) { + SyncCacheEntry entry = cacheEntries.get(type); + return entry == null ? -1 : entry.getRevisionValue(); + } + + public static NBTTagCompound workbenchNBT() { + RecipeController controller = RecipeController.Instance; + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (RecipeCarpentry recipe : controller.globalRecipes.values()) { + list.appendTag(recipe.writeNBT(false)); + } + compound.setTag("recipes", list); + return compound; + } + + public static NBTTagCompound carpentryNBT() { + RecipeController controller = RecipeController.Instance; + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (RecipeCarpentry recipe : controller.carpentryRecipes.values()) { + list.appendTag(recipe.writeNBT(false)); + } + compound.setTag("recipes", list); + return compound; + } + + public static NBTTagCompound anvilNBT() { + RecipeController controller = RecipeController.Instance; + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (RecipeAnvil recipe : controller.anvilRecipes.values()) { + list.appendTag(recipe.writeNBT(false)); + } + compound.setTag("recipes", list); + return compound; + } + + public static NBTTagCompound factionsNBT() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Faction faction : FactionController.getInstance().factions.values()) { + NBTTagCompound factioNBT = new NBTTagCompound(); + faction.writeNBT(factioNBT); + list.appendTag(factioNBT); + } + compound.setTag("Factions", list); + return compound; + } + + public static NBTTagCompound dialogCategoriesNBT() { + NBTTagCompound compound = new NBTTagCompound(); + NBTTagList categoryList = new NBTTagList(); + for (DialogCategory category : DialogController.Instance.categories.values()) { + NBTTagCompound questCompound = new NBTTagCompound(); + NBTTagList dialogList = new NBTTagList(); + for (int dialogID : category.dialogs.keySet()) { + Dialog quest = category.dialogs.get(dialogID); + dialogList.appendTag(quest.writeToNBT(new NBTTagCompound())); + } + questCompound.setTag("Data", dialogList); + questCompound.setTag("CatNBT", category.writeSmallNBT(new NBTTagCompound())); + categoryList.appendTag(questCompound); + } + compound.setTag("DialogCategories", categoryList); + return compound; + } + + public static NBTTagCompound questCategoriesNBT() { + NBTTagCompound compound = new NBTTagCompound(); + NBTTagList categoryList = new NBTTagList(); + for (QuestCategory category : QuestController.Instance.categories.values()) { + NBTTagCompound questCompound = new NBTTagCompound(); + ; + NBTTagList questList = new NBTTagList(); + for (int questID : category.quests.keySet()) { + Quest quest = category.quests.get(questID); + questList.appendTag(quest.writeToNBT(new NBTTagCompound())); + } + questCompound.setTag("Data", questList); + questCompound.setTag("CatNBT", category.writeSmallNBT(new NBTTagCompound())); + categoryList.appendTag(questCompound); + } + compound.setTag("QuestCategories", categoryList); + return compound; + } + + public static NBTTagCompound customEffectsNBT() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (CustomEffect effect : CustomEffectController.getInstance().getCustomEffects().values()) { + list.appendTag(effect.writeToNBT(false)); + } + compound.setTag("Data", list); + return compound; + } + + public static NBTTagCompound magicsNBT() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Magic magic : MagicController.getInstance().magics.values()) { + NBTTagCompound magicCompound = new NBTTagCompound(); + magic.writeNBT(magicCompound); + list.appendTag(magicCompound); + } + compound.setTag("Data", list); + return compound; + } + + public static NBTTagCompound magicCyclesNBT() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (MagicCycle cycle : MagicController.getInstance().cycles.values()) { + NBTTagCompound cycleCompound = new NBTTagCompound(); + cycle.writeNBT(cycleCompound); + list.appendTag(cycleCompound); + } + compound.setTag("Data", list); + return compound; + } + + public static NBTTagCompound customAbilitiesNBT() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Map.Entry entry : AbilityController.Instance.getCustomAbilities().entrySet()) { + NBTTagCompound abilityNBT = entry.getValue().writeNBT(false); + abilityNBT.setString("CustomAbilityId", entry.getKey()); + list.appendTag(abilityNBT); + } + compound.setTag("Data", list); + return compound; + } + + public static void syncAllCustomAbilities() { + CachedSyncPayload payload = rebuildNow(SyncType.CUSTOM_ABILITY); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CUSTOM_ABILITY, payload)); + updateAllPlayerRevisions(SyncType.CUSTOM_ABILITY, payload.getRevision()); + } + + public static NBTTagCompound chainedAbilitiesNBT() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Map.Entry entry : AbilityController.Instance.getChainedAbilities().entrySet()) { + NBTTagCompound chainNBT = entry.getValue().writeNBT(false); + chainNBT.setString("ChainedAbilityId", entry.getKey()); + list.appendTag(chainNBT); + } + compound.setTag("Data", list); + return compound; + } + + public static void syncAllChainedAbilities() { + CachedSyncPayload payload = rebuildNow(SyncType.CHAINED_ABILITY); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CHAINED_ABILITY, payload)); + updateAllPlayerRevisions(SyncType.CHAINED_ABILITY, payload.getRevision()); + } + + public static void syncPlayerData(EntityPlayerMP player, boolean update) { + PlayerData data = PlayerData.get(player); + if (data != null) { + if (update) { + PacketHandler.Instance.sendToPlayer(new SyncPacket( + SyncType.PLAYERDATA, + EnumSyncAction.UPDATE, + -1, + data.getSyncNBT()) + , player); + } else { + PacketHandler.Instance.sendToPlayer(new SyncPacket( + SyncType.PLAYERDATA, + EnumSyncAction.RELOAD, + -1, + data.getSyncNBTFull()), player); + } + } + } + + public static void syncRemove(SyncType syncType, int id) { + Map revisions = invalidateCaches(syncType); + int revision = revisions.getOrDefault(syncType, getCurrentRevision(syncType)); + PacketHandler.Instance.sendToAll(new SyncPacket( + syncType, + EnumSyncAction.REMOVE, + id, + revision, + new NBTTagCompound() + )); + updateAllPlayerRevisions(revisions); + } + + public static void syncAllDialogs() { + CachedSyncPayload payload = rebuildNow(SyncType.DIALOG_CATEGORY); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.DIALOG_CATEGORY, payload)); + updateAllPlayerRevisions(SyncType.DIALOG_CATEGORY, payload.getRevision()); + } + + public static void syncAllQuests() { + CachedSyncPayload payload = rebuildNow(SyncType.QUEST_CATEGORY); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.QUEST_CATEGORY, payload)); + updateAllPlayerRevisions(SyncType.QUEST_CATEGORY, payload.getRevision()); + } + + public static void syncAllWorkbenchRecipes() { + CachedSyncPayload payload = rebuildNow(SyncType.WORKBENCH_RECIPES); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.WORKBENCH_RECIPES, payload)); + updateAllPlayerRevisions(SyncType.WORKBENCH_RECIPES, payload.getRevision()); + } + + public static void syncAllCarpentryRecipes() { + CachedSyncPayload payload = rebuildNow(SyncType.CARPENTRY_RECIPES); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CARPENTRY_RECIPES, payload)); + updateAllPlayerRevisions(SyncType.CARPENTRY_RECIPES, payload.getRevision()); + } + + public static void syncAllAnvilRecipes() { + CachedSyncPayload payload = rebuildNow(SyncType.ANVIL_RECIPES); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.ANVIL_RECIPES, payload)); + updateAllPlayerRevisions(SyncType.ANVIL_RECIPES, payload.getRevision()); + } + + public static void syncAllCustomEffects() { + CachedSyncPayload payload = rebuildNow(SyncType.CUSTOM_EFFECTS); + if (payload == null) { + return; + } + PacketHandler.Instance.sendToAll(new SyncPacket(SyncType.CUSTOM_EFFECTS, payload)); + updateAllPlayerRevisions(SyncType.CUSTOM_EFFECTS, payload.getRevision()); + } + + @SideOnly(Side.CLIENT) + public static void clientSync(SyncType syncType, int revision, NBTTagCompound fullCompound) { + if (syncType == SyncType.FACTION) { + NBTTagList list = fullCompound.getTagList("Factions", 10); + for (int i = 0; i < list.tagCount(); i++) { + Faction faction = new Faction(); + faction.readNBT(list.getCompoundTagAt(i)); + FactionController.getInstance().factionsSync.put(faction.id, faction); + } + FactionController.getInstance().factions = FactionController.getInstance().factionsSync; + FactionController.getInstance().factionsSync = new HashMap(); + } else if (syncType == SyncType.DIALOG_CATEGORY) { + if (!fullCompound.hasNoTags()) { + NBTTagList categories = fullCompound.getTagList("DialogCategories", 10); + for (int j = 0; j < fullCompound.getTagList("DialogCategories", 10).tagCount(); j++) { + NBTTagCompound categoryCompound = categories.getCompoundTagAt(j); + if (categoryCompound.hasNoTags()) + continue; + + DialogCategory category = new DialogCategory(); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + NBTTagList dialogList = categoryCompound.getTagList("Data", 10); + if (DialogController.Instance.categoriesSync.containsKey(category.id)) { + category = DialogController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < dialogList.tagCount(); i++) { + Dialog dialog = new Dialog(); + dialog.readNBT(dialogList.getCompoundTagAt(i)); + dialog.category = category; + category.dialogs.put(dialog.id, dialog); + } + DialogController.Instance.categoriesSync.put(category.id, category); + } + } + + HashMap dialogs = new HashMap(); + for (DialogCategory category : DialogController.Instance.categoriesSync.values()) { + for (Dialog dialog : category.dialogs.values()) { + dialogs.put(dialog.id, dialog); + } + } + + DialogController.Instance.categories = DialogController.Instance.categoriesSync; + DialogController.Instance.dialogs = dialogs; + DialogController.Instance.categoriesSync = new HashMap(); + } else if (syncType == SyncType.QUEST_CATEGORY) { + if (!fullCompound.hasNoTags()) { + NBTTagList categories = fullCompound.getTagList("QuestCategories", 10); + for (int j = 0; j < fullCompound.getTagList("QuestCategories", 10).tagCount(); j++) { + NBTTagCompound categoryCompound = categories.getCompoundTagAt(j); + if (categoryCompound.hasNoTags()) + continue; + + QuestCategory category = new QuestCategory(); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + NBTTagList questList = categoryCompound.getTagList("Data", 10); + if (QuestController.Instance.categoriesSync.containsKey(category.id)) { + category = QuestController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < questList.tagCount(); i++) { + Quest quest = new Quest(); + quest.readNBT(questList.getCompoundTagAt(i)); + quest.category = category; + category.quests.put(quest.id, quest); + } + QuestController.Instance.categoriesSync.put(category.id, category); + } + } + + HashMap quests = new HashMap(); + for (QuestCategory category : QuestController.Instance.categoriesSync.values()) { + for (Quest quest : category.quests.values()) { + quests.put(quest.id, quest); + } + } + + QuestController.Instance.categories = QuestController.Instance.categoriesSync; + QuestController.Instance.quests = quests; + QuestController.Instance.categoriesSync = new HashMap(); + } else if (syncType == SyncType.PLAYERDATA) { + ClientCacheHandler.playerData.setSyncNBTFull(fullCompound); + } else if (syncType == SyncType.MAGIC) { + NBTTagList list = fullCompound.getTagList("Data", 10); + MagicController mc = MagicController.getInstance(); + mc.magicSync.clear(); + for (int i = 0; i < list.tagCount(); i++) { + Magic magic = new Magic(); + magic.readNBT(list.getCompoundTagAt(i)); + mc.magicSync.put(magic.id, magic); + } + + mc.magics.clear(); + mc.magics.putAll(mc.magicSync); + mc.magicSync.clear(); + } else if (syncType == SyncType.MAGIC_CYCLE) { + NBTTagList list = fullCompound.getTagList("Data", 10); + MagicController mc = MagicController.getInstance(); + mc.cyclesSync.clear(); + for (int i = 0; i < list.tagCount(); i++) { + MagicCycle cycle = new MagicCycle(); + cycle.readNBT(list.getCompoundTagAt(i)); + mc.cyclesSync.put(cycle.id, cycle); + } + + mc.cycles.clear(); + mc.cycles.putAll(mc.cyclesSync); + mc.cyclesSync.clear(); + } else if (syncType == SyncType.WORKBENCH_RECIPES) { + NBTTagList list = fullCompound.getTagList("recipes", 10); + if (list == null) + return; + + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound recipeCompound = list.getCompoundTagAt(i); + RecipeCarpentry recipe = RecipeCarpentry.create(recipeCompound); + recipe.readNBT(recipeCompound); + RecipeController.syncRecipes.put(recipe.id, recipe); + } + + RecipeController.reloadGlobalRecipes(RecipeController.syncRecipes); + RecipeController.syncRecipes = new HashMap(); + } else if (syncType == SyncType.CARPENTRY_RECIPES) { + NBTTagList list = fullCompound.getTagList("recipes", 10); + if (list == null) + return; + + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound recipeCompound = list.getCompoundTagAt(i); + RecipeCarpentry recipe = RecipeCarpentry.create(recipeCompound); + recipe.readNBT(recipeCompound); + RecipeController.syncRecipes.put(recipe.id, recipe); + } + + RecipeController.Instance.carpentryRecipes = RecipeController.syncRecipes; + RecipeController.syncRecipes = new HashMap(); + } else if (syncType == SyncType.ANVIL_RECIPES) { + NBTTagList list = fullCompound.getTagList("recipes", 10); + if (list == null) + return; + + for (int i = 0; i < list.tagCount(); i++) { + RecipeAnvil recipe = new RecipeAnvil(); + recipe.readNBT(list.getCompoundTagAt(i)); + RecipeController.syncAnvilRecipes.put(recipe.id, recipe); + } + + RecipeController.Instance.anvilRecipes = RecipeController.syncAnvilRecipes; + RecipeController.syncAnvilRecipes = new HashMap(); + } else if (syncType == SyncType.CUSTOM_EFFECTS) { + NBTTagList list = fullCompound.getTagList("Data", 10); + CustomEffectController ce = CustomEffectController.getInstance(); + ce.customEffectsSync.clear(); + for (int i = 0; i < list.tagCount(); i++) { + CustomEffect effect = new CustomEffect(); + effect.readFromNBT(list.getCompoundTagAt(i)); + ClientCacheHandler.getImageData(effect.icon); + ce.customEffectsSync.put(effect.id, effect); + } + + ce.getCustomEffects().clear(); + ce.getCustomEffects().putAll(ce.customEffectsSync); + ce.customEffectsSync.clear(); + } else if (syncType == SyncType.CUSTOM_ABILITY) { + NBTTagList list = fullCompound.getTagList("Data", 10); + LinkedHashMap sync = new LinkedHashMap<>(); + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound nbt = list.getCompoundTagAt(i); + String name = nbt.getString("CustomAbilityId"); + Ability ability = AbilityController.Instance.fromNBT(nbt); + if (ability != null) { + ability.setName(name); + sync.put(name, ability); + } + } + AbilityController.Instance.setCustomAbilities(sync); + } else if (syncType == SyncType.CHAINED_ABILITY) { + NBTTagList list = fullCompound.getTagList("Data", 10); + LinkedHashMap sync = new LinkedHashMap<>(); + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound nbt = list.getCompoundTagAt(i); + String id = nbt.getString("ChainedAbilityId"); + ChainedAbility chain = new ChainedAbility(); + chain.readNBT(nbt); + chain.setName(id); + sync.put(id, chain); + } + AbilityController.Instance.setChainedAbilities(sync); + } + + ClientCacheHandler.updateClientRevision(syncType, revision); + } + + @SideOnly(Side.CLIENT) + public static void clientUpdate(SyncType syncType, int category_id, int revision, NBTTagCompound compound) { + if (syncType == SyncType.FACTION) { + Faction faction = new Faction(); + faction.readNBT(compound); + FactionController.getInstance().factions.put(faction.id, faction); + } else if (syncType == SyncType.DIALOG) { + DialogCategory category = DialogController.Instance.categories.get(category_id); + Dialog dialog = new Dialog(); + dialog.category = category; + dialog.readNBT(compound); + DialogController.Instance.dialogs.put(dialog.id, dialog); + category.dialogs.put(dialog.id, dialog); + } else if (syncType == SyncType.QUEST) { + QuestCategory category = QuestController.Instance.categories.get(category_id); + Quest quest = new Quest(); + quest.category = category; + quest.readNBT(compound); + QuestController.Instance.quests.put(quest.id, quest); + category.quests.put(quest.id, quest); + } else if (syncType == SyncType.QUEST_CATEGORY) { + QuestCategory category = new QuestCategory(); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + NBTTagList list = compound.getTagList("Data", 10); + if (QuestController.Instance.categoriesSync.containsKey(category.id)) { + category = QuestController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < list.tagCount(); i++) { + Quest quest = new Quest(); + quest.readNBT(list.getCompoundTagAt(i)); + quest.category = category; + category.quests.put(quest.id, quest); + } + QuestController.Instance.categories.put(category.id, category); + } else if (syncType == SyncType.DIALOG_CATEGORY) { + DialogCategory category = new DialogCategory(); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + NBTTagList list = compound.getTagList("Data", 10); + if (DialogController.Instance.categoriesSync.containsKey(category.id)) { + category = DialogController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < list.tagCount(); i++) { + Dialog dialog = new Dialog(); + dialog.readNBT(list.getCompoundTagAt(i)); + dialog.category = category; + category.dialogs.put(dialog.id, dialog); + } + DialogController.Instance.categories.put(category.id, category); + } else if (syncType == SyncType.MAGIC_CYCLE) { + MagicCycle cycle = new MagicCycle(); + cycle.readNBT(compound); + MagicController.getInstance().cycles.put(cycle.id, cycle); + } else if (syncType == SyncType.MAGIC) { + Magic magic = new Magic(); + magic.readNBT(compound); + MagicController.getInstance().magics.put(magic.id, magic); + } else if (syncType == SyncType.PLAYERDATA) { + ClientCacheHandler.playerData.setSyncNBT(compound); + } else if (syncType == SyncType.CUSTOM_EFFECTS) { + CustomEffect effect = new CustomEffect(); + effect.readFromNBT(compound); + ClientCacheHandler.getImageData(effect.icon); + + CustomEffectController.Instance.getCustomEffects().put(effect.id, effect); + } + + ClientCacheHandler.updateClientRevision(syncType, revision); + } + + public static void syncUpdate(SyncType type, int cat, NBTTagCompound compound) { + Map revisions = invalidateCaches(type); + int revision = revisions.getOrDefault(type, getCurrentRevision(type)); + PacketHandler.Instance.sendToAll(new SyncPacket( + type, + EnumSyncAction.UPDATE, + cat, + revision, + compound + )); + updateAllPlayerRevisions(revisions); + } + + + public static NBTTagCompound updateQuestCat(QuestCategory questCategory) { + NBTTagCompound questCompound = new NBTTagCompound(); + NBTTagList questList = new NBTTagList(); + for (int questID : questCategory.quests.keySet()) { + Quest quest = questCategory.quests.get(questID); + questList.appendTag(quest.writeToNBT(new NBTTagCompound())); + } + questCompound.setTag("Data", questList); + questCompound.setTag("CatNBT", questCategory.writeSmallNBT(new NBTTagCompound())); + return questCompound; + } + + public static NBTTagCompound updateDialogCat(DialogCategory dialogCategory) { + NBTTagCompound dialogCompound = new NBTTagCompound(); + NBTTagList dialogList = new NBTTagList(); + for (int questID : dialogCategory.dialogs.keySet()) { + Dialog dialog = dialogCategory.dialogs.get(questID); + dialogList.appendTag(dialog.writeToNBT(new NBTTagCompound())); + } + dialogCompound.setTag("Data", dialogList); + dialogCompound.setTag("CatNBT", dialogCategory.writeSmallNBT(new NBTTagCompound())); + return dialogCompound; + } + + @SideOnly(Side.CLIENT) + public static void clientSyncRemove(SyncType syncType, int id, int revision) { + if (syncType == SyncType.FACTION) { + FactionController.getInstance().factions.remove(id); + } else if (syncType == SyncType.DIALOG) { + Dialog dialog = DialogController.Instance.dialogs.remove(id); + if (dialog != null) { + dialog.category.dialogs.remove(id); + } + } else if (syncType == SyncType.DIALOG_CATEGORY) { + DialogCategory dialogCategory = DialogController.Instance.categories.remove(id); + if (dialogCategory != null) { + DialogController.Instance.dialogs.keySet().removeAll(dialogCategory.dialogs.keySet()); + } + } else if (syncType == SyncType.QUEST) { + Quest quest = QuestController.Instance.quests.remove(id); + if (quest != null) { + quest.category.quests.remove(id); + } + } else if (syncType == SyncType.QUEST_CATEGORY) { + QuestCategory questCategory = QuestController.Instance.categories.remove(id); + if (questCategory != null) { + QuestController.Instance.quests.keySet().removeAll(questCategory.quests.keySet()); + } + } else if (syncType == SyncType.MAGIC) { + for (MagicCycle cycle : MagicController.getInstance().cycles.values()) { + cycle.associations.remove(id); + } + MagicController.getInstance().cycles.remove(id); + } else if (syncType == SyncType.MAGIC_CYCLE) { + MagicController.getInstance().cycles.remove(id); + } else if (syncType == SyncType.CUSTOM_EFFECTS) { + CustomEffectController.Instance.getCustomEffects().remove(id); + } + + ClientCacheHandler.updateClientRevision(syncType, revision); + } + + public static void syncEffects(EntityPlayerMP playerMP) { + ConcurrentHashMap playerEffects = CustomEffectController.getInstance().getPlayerEffects(playerMP); + PlayerData playerData = PlayerData.get(playerMP); + playerData.effectData.setEffects(playerEffects); + + NBTTagCompound compound = playerData.getPlayerEffects(); + PacketHandler.Instance.sendToPlayer(new SyncEffectPacket(compound), playerMP); + } + + /** + * Full sync of all player ability state to the client: unlocked abilities, + * selection, toggles, hotbar layout, and cooldowns. + * Used for entity reconstruction and other full-refresh scenarios. + */ + public static void syncAbilities(EntityPlayerMP playerMP) { + PlayerData playerData = PlayerData.get(playerMP); + if (playerData == null) return; + + PlayerAbilitySyncPacket.sendToPlayer(playerMP); + AbilityHotbarSyncPacket.sendToPlayer(playerMP); + AbilityCooldownSyncPacket.sendToPlayer(playerMP); + } + + /** + * Lightweight sync of cooldown state only (global + per-ability). + * Called periodically (every 10 ticks) to keep the client's cooldown + * display accurate. Cooldowns are transient and not part of NBT sync. + */ + public static void syncAbilityCooldowns(EntityPlayerMP playerMP) { + PlayerData playerData = PlayerData.get(playerMP); + if (playerData == null) return; + + AbilityCooldownSyncPacket.sendToPlayer(playerMP); + } + + @SideOnly(Side.CLIENT) + public static void clientSyncEffects(NBTTagCompound compound) { + PlayerData playerData = PlayerData.get(Minecraft.getMinecraft().thePlayer); + if (playerData != null) { + playerData.setPlayerEffects(compound); + } + } + + private static void registerCache(SyncType type, Supplier supplier) { + cacheEntries.put(type, new SyncCacheEntry(supplier)); + } + + private static CachedSyncPayload rebuildNow(SyncType type) { + SyncCacheEntry entry = cacheEntries.get(type); + if (entry == null) { + return null; + } + entry.invalidate(); + return entry.getPayload(type); + } + + public static Map invalidateCaches(SyncType type) { + Set targets = getInvalidationTargets(type); + Map revisions = new LinkedHashMap<>(); + for (SyncType target : targets) { + SyncCacheEntry entry = cacheEntries.get(target); + if (entry != null) { + int newRevision = entry.invalidate(); + revisions.put(target, newRevision); + } + } + return revisions; + } + + private static Set getInvalidationTargets(SyncType type) { + if (type == SyncType.DIALOG || type == SyncType.DIALOG_CATEGORY) { + Set set = new HashSet<>(); + set.add(SyncType.DIALOG_CATEGORY); + return set; + } else if (type == SyncType.QUEST || type == SyncType.QUEST_CATEGORY) { + Set set = new HashSet<>(); + set.add(SyncType.QUEST_CATEGORY); + return set; + } else if (type == SyncType.FACTION) { + Set set = new HashSet<>(); + set.add(SyncType.FACTION); + return set; + } else if (type == SyncType.MAGIC) { + Set set = new HashSet<>(); + set.add(SyncType.MAGIC); + return set; + } else if (type == SyncType.MAGIC_CYCLE) { + Set set = new HashSet<>(); + set.add(SyncType.MAGIC_CYCLE); + return set; + } else if (type == SyncType.WORKBENCH_RECIPES) { + Set set = new HashSet<>(); + set.add(SyncType.WORKBENCH_RECIPES); + return set; + } else if (type == SyncType.CARPENTRY_RECIPES) { + Set set = new HashSet<>(); + set.add(SyncType.CARPENTRY_RECIPES); + return set; + } else if (type == SyncType.ANVIL_RECIPES) { + Set set = new HashSet<>(); + set.add(SyncType.ANVIL_RECIPES); + return set; + } else if (type == SyncType.CUSTOM_EFFECTS) { + Set set = new HashSet<>(); + set.add(SyncType.CUSTOM_EFFECTS); + return set; + } else if (type == SyncType.CUSTOM_ABILITY) { + Set set = new HashSet<>(); + set.add(SyncType.CUSTOM_ABILITY); + return set; + } else if (type == SyncType.CHAINED_ABILITY) { + Set set = new HashSet<>(); + set.add(SyncType.CHAINED_ABILITY); + return set; + } + return Collections.emptySet(); + } + + private static void updateAllPlayerRevisions(SyncType type, int revision) { + if (revision < 0) { + return; + } + for (PlayerSyncState state : playerSyncState.values()) { + state.updateRevision(type, revision); + } + } + + private static void updateAllPlayerRevisions(Map revisions) { + if (revisions.isEmpty()) { + return; + } + for (PlayerSyncState state : playerSyncState.values()) { + for (Map.Entry entry : revisions.entrySet()) { + state.updateRevision(entry.getKey(), entry.getValue()); + } + } + } + + public static final class CachedSyncPayload { + private final int revision; + private final byte[] payload; + private final byte[][] chunks; + + private CachedSyncPayload(int revision, byte[] payload, byte[][] chunks) { + this.revision = revision; + this.payload = payload; + this.chunks = chunks; + } + + public int getRevision() { + return revision; + } + + public byte[] getPayload() { + return payload; + } + + public byte[][] getChunks() { + return chunks; + } + } + + private static final class SyncCacheEntry { + private final Supplier supplier; + private volatile CachedSyncPayload payload; + private int revision = 0; + private boolean dirty = true; + + private SyncCacheEntry(Supplier supplier) { + this.supplier = supplier; + } + + private synchronized CachedSyncPayload getPayload(SyncType requestedType) { + if (!dirty && payload != null) { + return payload; + } + + int payloadRevision = revision; + NBTTagCompound data = supplier.get(); + ByteBuf buffer = Unpooled.buffer(); + byte[] bytes; + try { + buffer.writeInt(requestedType.ordinal()); + buffer.writeInt(EnumSyncAction.RELOAD.ordinal()); + buffer.writeInt(-1); + buffer.writeInt(payloadRevision); + ByteBufUtils.writeBigNBT(buffer, data); + + bytes = new byte[buffer.readableBytes()]; + buffer.readBytes(bytes); + } catch (IOException e) { + throw new RuntimeException("Failed to serialize sync payload for " + requestedType, e); + } finally { + buffer.release(); + } + + byte[][] chunks = splitIntoChunks(bytes); + payload = new CachedSyncPayload(payloadRevision, bytes, chunks); + dirty = false; + return payload; + } + + private synchronized int invalidate() { + dirty = true; + payload = null; + revision++; + return revision; + } + + private synchronized int getRevisionValue() { + return revision; + } + + private synchronized void reset() { + dirty = true; + payload = null; + revision = 0; + } + + private static byte[][] splitIntoChunks(byte[] payload) { + int totalSize = payload.length; + int chunkCount = (totalSize + LargeAbstractPacket.CHUNK_SIZE - 1) / LargeAbstractPacket.CHUNK_SIZE; + if (chunkCount <= 0) { + chunkCount = 1; + } + byte[][] chunks = new byte[chunkCount][]; + for (int i = 0; i < chunkCount; i++) { + int offset = i * LargeAbstractPacket.CHUNK_SIZE; + int length = Math.min(LargeAbstractPacket.CHUNK_SIZE, totalSize - offset); + byte[] chunk = new byte[length]; + System.arraycopy(payload, offset, chunk, 0, length); + chunks[i] = chunk; + } + return chunks; + } + } + + private static final class PlayerSyncState { + private final Map revisions = new HashMap<>(); + + private PlayerSyncState(UUID ignored) { + } + + private synchronized int getRevision(SyncType type) { + Integer value = revisions.get(type); + return value == null ? -1 : value; + } + + private synchronized void updateRevision(SyncType type, int revision) { + revisions.put(type, revision); + } + + private synchronized void applyHandshake(Map incoming) { + if (incoming == null || incoming.isEmpty()) { + return; + } + revisions.putAll(incoming); + } + + private synchronized void reset() { + revisions.clear(); + } + + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/profile/CNPCData.java b/src/main/java/kamkeel/npcs/controllers/data/profile/CNPCData.java index f7d6ded84..7d77317a0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/profile/CNPCData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/profile/CNPCData.java @@ -1,6 +1,6 @@ package kamkeel.npcs.controllers.data.profile; -import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.PlayerDataSyncHandler; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; @@ -30,7 +30,7 @@ public class CNPCData implements IProfileData { public void save(EntityPlayer player) { PlayerData customNPCData = PlayerData.get(player); customNPCData.save(); - SyncController.syncPlayerData((EntityPlayerMP) player, false); + PlayerDataSyncHandler.getInstance().syncPlayerData((EntityPlayerMP) player, false); } @Override diff --git a/src/main/java/kamkeel/npcs/controllers/sync/SyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/SyncHandler.java new file mode 100644 index 000000000..5cfe5e7a6 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/SyncHandler.java @@ -0,0 +1,135 @@ +package kamkeel.npcs.controllers.sync; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; + +import java.util.HashSet; +import java.util.Set; + +/** + * Contract for a synchronized data type. Implementors handle both + * server-side serialization and client-side deserialization for their + * specific data type. + * + *

Each handler is registered with {@link SyncRegistry} and covers + * all three sync actions: RELOAD (full), UPDATE (single entity), + * and REMOVE (delete entity).

+ * + *

Handlers that don't support UPDATE or REMOVE should use the + * default no-op implementations.

+ */ +public interface SyncHandler { + + // ========== SERVER-SIDE ========== + + /** + * Serialize ALL objects of this type into an NBTTagCompound. + * This is the supplier for the cache system. + * + *

Called lazily when the cache is dirty and a player needs data. + * The returned compound is cached and chunked for transmission.

+ * + * @return the serialized compound, or null if this type has no independent cache + */ + NBTTagCompound serializeAll(); + + + // ========== CLIENT-SIDE ========== + + /** + * Handle a RELOAD action on the client. Deserialize the full + * dataset and replace the client-side data. + * + *

Implementations should: + *

    + *
  1. Deserialize from the compound into a sync buffer
  2. + *
  3. Atomically swap the sync buffer to the primary map
  4. + *
  5. Reset the sync buffer for the next reload
  6. + *
+ * + * @param fullCompound the full serialized dataset from {@link #serializeAll()} + */ + @SideOnly(Side.CLIENT) + void clientHandleReload(NBTTagCompound fullCompound); + + /** + * Handle an UPDATE action on the client. Deserialize a single + * entity and insert/replace it in the client-side data. + * + *

Default: no-op. Types that don't support individual updates + * use {@code syncAll()} for full reload instead.

+ * + * @param compound the serialized single entity + * @param categoryId the parent category ID (-1 if not applicable) + */ + @SideOnly(Side.CLIENT) + default void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + // No-op: type does not support individual updates + } + + /** + * Removes a single object from client-side data. + * Full object NBTTagCompound sent finer query control + * Int-keyed handlers should override {@link #clientHandleRemove(int)} instead. + */ + @SideOnly(Side.CLIENT) + default void clientHandleRemove(NBTTagCompound compound) { + } + + /** + * Removes a single object with the given ID from client-side data. + * + * @param id the ID of the object to remove + */ + @SideOnly(Side.CLIENT) + default void clientHandleRemove(int id) { + } + + /** + * Whether this handler supports the UPDATE action. + * Used for validation and self-documentation. + */ + default boolean supportsUpdate() { + return false; + } + + /** + * Whether this handler supports the REMOVE action. + * Used for validation and self-documentation. + */ + default boolean supportsRemove() { + return false; + } + + /** + * Whether this type should be included in login sync. + * Types that return true will be sent to players on login + * and checked for revision deltas on reconnect. + * + *

Default: true. Override to false for types like PLAYERDATA + * that are synced through separate mechanisms, or child types + * (DIALOG, QUEST) that share their parent's cache.

+ */ + default boolean isCachedType() { + return true; + } + + /** + * Get the set of cache types that must be invalidated when + * this type changes. Used for cascade invalidation. + * + *

Most types return just themselves. Parent-child types + * (e.g. DIALOG maps to DIALOG_CATEGORY) point children + * to the parent's cache.

+ * + * @param self the SyncType this handler is registered under + * @return the set of types to invalidate + */ + default Set getInvalidationTargets(SyncType self) { + Set set = new HashSet<>(); + set.add(self); + return set; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/SyncRegistry.java b/src/main/java/kamkeel/npcs/controllers/sync/SyncRegistry.java new file mode 100644 index 000000000..a13568797 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/SyncRegistry.java @@ -0,0 +1,98 @@ +package kamkeel.npcs.controllers.sync; + +import kamkeel.npcs.network.enums.SyncType; +import noppes.npcs.LogWriter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class SyncRegistry { + + private static final LinkedHashMap handlers = new LinkedHashMap<>(); + + private static SyncType[] loginTypesCache = null; + private static boolean frozen = false; + + private SyncRegistry() { + } + + public static void register(SyncType type, SyncHandler handler) { + if (type == null) { + throw new IllegalArgumentException("SyncType must not be null"); + } + if (handler == null) { + throw new IllegalArgumentException("SyncHandler must not be null for " + type); + } + if (frozen) { + throw new IllegalStateException( + "SyncRegistry is frozen; cannot register handler for " + type); + } + if (handlers.containsKey(type)) { + throw new IllegalStateException( + "SyncHandler already registered for " + type); + } + handlers.put(type, handler); + loginTypesCache = null; + } + + public static SyncHandler getHandler(SyncType type) { + return handlers.get(type); + } + + public static SyncType[] getLoginTypes() { + if (loginTypesCache == null) { + List list = new ArrayList<>(); + for (Map.Entry e : handlers.entrySet()) { + if (e.getValue().isCachedType()) { + list.add(e.getKey()); + } + } + loginTypesCache = list.toArray(new SyncType[0]); + } + return loginTypesCache; + } + + public static Set getInvalidationTargets(SyncType type) { + SyncHandler handler = handlers.get(type); + if (handler == null) { + return Collections.emptySet(); + } + return handler.getInvalidationTargets(type); + } + + public static void clear() { + handlers.clear(); + loginTypesCache = null; + frozen = false; + } + + public static void freeze() { + frozen = true; + } + + public static boolean isFrozen() { + return frozen; + } + + public static void validateRegistrations() { + for (SyncType type : SyncType.values()) { + if (!handlers.containsKey(type)) { + LogWriter.info("[SyncRegistry] No SyncHandler registered for " + type); + } + } + } + + public static boolean isRegistered(SyncType type) { + return handlers.containsKey(type); + } + + public static Collection getRegisteredTypes() { + return Collections.unmodifiableCollection(handlers.keySet()); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/AnvilRecipeSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/AnvilRecipeSyncHandler.java new file mode 100644 index 000000000..caaad94d9 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/AnvilRecipeSyncHandler.java @@ -0,0 +1,56 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.RecipeController; +import noppes.npcs.controllers.data.RecipeAnvil; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#ANVIL_RECIPES}. + * RELOAD-only family — does not support individual UPDATE or REMOVE. + */ +public class AnvilRecipeSyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + RecipeController controller = RecipeController.Instance; + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (RecipeAnvil recipe : controller.anvilRecipes.values()) { + list.appendTag(recipe.writeNBT(false)); + } + compound.setTag("recipes", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("recipes", 10); + if (list == null) + return; + + for (int i = 0; i < list.tagCount(); i++) { + RecipeAnvil recipe = new RecipeAnvil(); + recipe.readNBT(list.getCompoundTagAt(i)); + RecipeController.syncAnvilRecipes.put(recipe.id, recipe); + } + + RecipeController.Instance.anvilRecipes = RecipeController.syncAnvilRecipes; + RecipeController.syncAnvilRecipes = new HashMap<>(); + } + + // RELOAD-only: supportsUpdate() and supportsRemove() remain false (default) +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/CarpentryRecipeSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/CarpentryRecipeSyncHandler.java new file mode 100644 index 000000000..5213dc227 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/CarpentryRecipeSyncHandler.java @@ -0,0 +1,63 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.RecipeController; +import noppes.npcs.controllers.data.RecipeCarpentry; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#CARPENTRY_RECIPES}. + * RELOAD-only family — does not support individual UPDATE or REMOVE. + */ +public class CarpentryRecipeSyncHandler implements SyncHandler { + + private static final CarpentryRecipeSyncHandler INSTANCE = new CarpentryRecipeSyncHandler(); + + public static CarpentryRecipeSyncHandler getInstance() { + return INSTANCE; + } + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + RecipeController controller = RecipeController.Instance; + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (RecipeCarpentry recipe : controller.carpentryRecipes.values()) { + list.appendTag(recipe.writeNBT(false)); + } + compound.setTag("recipes", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("recipes", 10); + if (list == null) + return; + + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound recipeCompound = list.getCompoundTagAt(i); + RecipeCarpentry recipe = RecipeCarpentry.create(recipeCompound); + recipe.readNBT(recipeCompound); + RecipeController.syncRecipes.put(recipe.id, recipe); + } + + RecipeController.Instance.carpentryRecipes = RecipeController.syncRecipes; + RecipeController.syncRecipes = new HashMap<>(); + } + + // RELOAD-only: supportsUpdate() and supportsRemove() remain false (default) +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/ChainedAbilitySyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/ChainedAbilitySyncHandler.java new file mode 100644 index 000000000..59600770d --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/ChainedAbilitySyncHandler.java @@ -0,0 +1,58 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#CHAINED_ABILITY}. + * String-keyed family using ordered {@link LinkedHashMap} to preserve + * registration/reload order. Supports RELOAD only for global sync. + */ +public class ChainedAbilitySyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Map.Entry entry : AbilityController.Instance.getChainedAbilities().entrySet()) { + NBTTagCompound chainNBT = entry.getValue().writeNBT(false); + chainNBT.setString("ChainedAbilityId", entry.getKey()); + list.appendTag(chainNBT); + } + compound.setTag("Data", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("Data", 10); + LinkedHashMap sync = new LinkedHashMap<>(); + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound nbt = list.getCompoundTagAt(i); + String id = nbt.getString("ChainedAbilityId"); + ChainedAbility chain = new ChainedAbility(); + chain.readNBT(nbt); + chain.setName(id); + sync.put(id, chain); + } + AbilityController.Instance.setChainedAbilities(sync); + } + + // RELOAD-only for global sync: supportsUpdate() and supportsRemove() remain false (default) +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/CustomAbilitySyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/CustomAbilitySyncHandler.java new file mode 100644 index 000000000..16a808797 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/CustomAbilitySyncHandler.java @@ -0,0 +1,59 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#CUSTOM_ABILITY}. + * String-keyed family using ordered {@link LinkedHashMap} to preserve + * registration/reload order. Supports RELOAD only for global sync. + */ +public class CustomAbilitySyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Map.Entry entry : AbilityController.Instance.getCustomAbilities().entrySet()) { + NBTTagCompound abilityNBT = entry.getValue().writeNBT(false); + abilityNBT.setString("CustomAbilityId", entry.getKey()); + list.appendTag(abilityNBT); + } + compound.setTag("Data", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("Data", 10); + LinkedHashMap sync = new LinkedHashMap<>(); + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound nbt = list.getCompoundTagAt(i); + String name = nbt.getString("CustomAbilityId"); + Ability ability = AbilityController.Instance.fromNBT(nbt); + if (ability != null) { + ability.setName(name); + sync.put(name, ability); + } + } + AbilityController.Instance.setCustomAbilities(sync); + } + + // RELOAD-only for global sync: supportsUpdate() and supportsRemove() remain false (default) +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/CustomEffectSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/CustomEffectSyncHandler.java new file mode 100644 index 000000000..ed1d8012e --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/CustomEffectSyncHandler.java @@ -0,0 +1,82 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.client.ClientCacheHandler; +import noppes.npcs.controllers.CustomEffectController; +import noppes.npcs.controllers.data.CustomEffect; + +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#CUSTOM_EFFECTS}. + * Simple int-keyed map family with RELOAD, UPDATE, and REMOVE support. + * Client-side RELOAD precaches effect icons via {@link ClientCacheHandler#getImageData}. + */ +public class CustomEffectSyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (CustomEffect effect : CustomEffectController.getInstance().getCustomEffects().values()) { + list.appendTag(effect.writeToNBT(false)); + } + compound.setTag("Data", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("Data", 10); + CustomEffectController ce = CustomEffectController.getInstance(); + ce.customEffectsSync.clear(); + for (int i = 0; i < list.tagCount(); i++) { + CustomEffect effect = new CustomEffect(); + effect.readFromNBT(list.getCompoundTagAt(i)); + ClientCacheHandler.getImageData(effect.icon); + ce.customEffectsSync.put(effect.id, effect); + } + + ce.getCustomEffects().clear(); + ce.getCustomEffects().putAll(ce.customEffectsSync); + ce.customEffectsSync.clear(); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + CustomEffect effect = new CustomEffect(); + effect.readFromNBT(compound); + ClientCacheHandler.getImageData(effect.icon); + + CustomEffectController.Instance.getCustomEffects().put(effect.id, effect); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + CustomEffectController.Instance.getCustomEffects().remove(id); + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } + +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/DialogCategorySyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/DialogCategorySyncHandler.java new file mode 100644 index 000000000..45d8dcce8 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/DialogCategorySyncHandler.java @@ -0,0 +1,140 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.DialogController; +import noppes.npcs.controllers.data.Dialog; +import noppes.npcs.controllers.data.DialogCategory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#DIALOG_CATEGORY}. + * Parent-child category family. DIALOG children share this cache. + * Invalidation from DIALOG or DIALOG_CATEGORY both target this cache. + */ +public class DialogCategorySyncHandler implements SyncHandler { + + /** + * Serialize a single dialog category for UPDATE packets. + */ + public static NBTTagCompound serializeCategory(DialogCategory dialogCategory) { + NBTTagCompound dialogCompound = new NBTTagCompound(); + NBTTagList dialogList = new NBTTagList(); + for (int questID : dialogCategory.dialogs.keySet()) { + Dialog dialog = dialogCategory.dialogs.get(questID); + dialogList.appendTag(dialog.writeToNBT(new NBTTagCompound())); + } + dialogCompound.setTag("Data", dialogList); + dialogCompound.setTag("CatNBT", dialogCategory.writeSmallNBT(new NBTTagCompound())); + return dialogCompound; + } + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagCompound compound = new NBTTagCompound(); + NBTTagList categoryList = new NBTTagList(); + for (DialogCategory category : DialogController.Instance.categories.values()) { + NBTTagCompound questCompound = new NBTTagCompound(); + NBTTagList dialogList = new NBTTagList(); + for (int dialogID : category.dialogs.keySet()) { + Dialog quest = category.dialogs.get(dialogID); + dialogList.appendTag(quest.writeToNBT(new NBTTagCompound())); + } + questCompound.setTag("Data", dialogList); + questCompound.setTag("CatNBT", category.writeSmallNBT(new NBTTagCompound())); + categoryList.appendTag(questCompound); + } + compound.setTag("DialogCategories", categoryList); + return compound; + } + + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + if (!fullCompound.hasNoTags()) { + NBTTagList categories = fullCompound.getTagList("DialogCategories", 10); + for (int j = 0; j < fullCompound.getTagList("DialogCategories", 10).tagCount(); j++) { + NBTTagCompound categoryCompound = categories.getCompoundTagAt(j); + if (categoryCompound.hasNoTags()) + continue; + + DialogCategory category = new DialogCategory(); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + NBTTagList dialogList = categoryCompound.getTagList("Data", 10); + if (DialogController.Instance.categoriesSync.containsKey(category.id)) { + category = DialogController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < dialogList.tagCount(); i++) { + Dialog dialog = new Dialog(); + dialog.readNBT(dialogList.getCompoundTagAt(i)); + dialog.category = category; + category.dialogs.put(dialog.id, dialog); + } + DialogController.Instance.categoriesSync.put(category.id, category); + } + } + + HashMap dialogs = new HashMap<>(); + for (DialogCategory category : DialogController.Instance.categoriesSync.values()) { + for (Dialog dialog : category.dialogs.values()) { + dialogs.put(dialog.id, dialog); + } + } + + DialogController.Instance.categories = DialogController.Instance.categoriesSync; + DialogController.Instance.dialogs = dialogs; + DialogController.Instance.categoriesSync = new HashMap<>(); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + DialogCategory category = new DialogCategory(); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + NBTTagList list = compound.getTagList("Data", 10); + if (DialogController.Instance.categoriesSync.containsKey(category.id)) { + category = DialogController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < list.tagCount(); i++) { + Dialog dialog = new Dialog(); + dialog.readNBT(list.getCompoundTagAt(i)); + dialog.category = category; + category.dialogs.put(dialog.id, dialog); + } + DialogController.Instance.categories.put(category.id, category); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + DialogCategory dialogCategory = DialogController.Instance.categories.remove(id); + if (dialogCategory != null) { + DialogController.Instance.dialogs.keySet().removeAll(dialogCategory.dialogs.keySet()); + } + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } + +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/DialogSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/DialogSyncHandler.java new file mode 100644 index 000000000..df0b82259 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/DialogSyncHandler.java @@ -0,0 +1,89 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.controllers.DialogController; +import noppes.npcs.controllers.data.Dialog; +import noppes.npcs.controllers.data.DialogCategory; + +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#DIALOG}. + * Child family — does not have its own cache. UPDATE and REMOVE only. + * Invalidation targets the parent {@link SyncType#DIALOG_CATEGORY} cache. + */ +public class DialogSyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + /** + * DIALOG does not have its own independent cache; it shares + * the DIALOG_CATEGORY parent cache. + */ + @Override + public NBTTagCompound serializeAll() { + return null; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + // DIALOG does not handle standalone RELOAD; it's part of DIALOG_CATEGORY + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + DialogCategory category = DialogController.Instance.categories.get(categoryId); + if (category == null) { + return; // Defensive: category may have been removed + } + Dialog dialog = new Dialog(); + dialog.category = category; + dialog.readNBT(compound); + DialogController.Instance.dialogs.put(dialog.id, dialog); + category.dialogs.put(dialog.id, dialog); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + Dialog dialog = DialogController.Instance.dialogs.remove(id); + if (dialog != null) { + dialog.category.dialogs.remove(id); + } + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } + + /** + * Not a cached type — DIALOG shares the DIALOG_CATEGORY cache. + */ + @Override + public boolean isCachedType() { + return false; + } + + @Override + public Set getInvalidationTargets(SyncType self) { + // Child invalidates the parent cache + Set set = new HashSet<>(); + set.add(SyncType.DIALOG_CATEGORY); + return set; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/FactionSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/FactionSyncHandler.java new file mode 100644 index 000000000..2639567db --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/FactionSyncHandler.java @@ -0,0 +1,76 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.FactionController; +import noppes.npcs.controllers.data.Faction; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#FACTION}. + * Simple int-keyed map family with RELOAD, UPDATE, and REMOVE support. + */ +public class FactionSyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Faction faction : FactionController.getInstance().factions.values()) { + NBTTagCompound factionNBT = new NBTTagCompound(); + faction.writeNBT(factionNBT); + list.appendTag(factionNBT); + } + compound.setTag("Factions", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("Factions", 10); + for (int i = 0; i < list.tagCount(); i++) { + Faction faction = new Faction(); + faction.readNBT(list.getCompoundTagAt(i)); + FactionController.getInstance().factionsSync.put(faction.id, faction); + } + FactionController.getInstance().factions = FactionController.getInstance().factionsSync; + FactionController.getInstance().factionsSync = new HashMap(); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + Faction faction = new Faction(); + faction.readNBT(compound); + FactionController.getInstance().factions.put(faction.id, faction); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + FactionController.getInstance().factions.remove(id); + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } + +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/MagicCycleSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/MagicCycleSyncHandler.java new file mode 100644 index 000000000..37cbb3091 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/MagicCycleSyncHandler.java @@ -0,0 +1,79 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.MagicController; +import noppes.npcs.controllers.data.MagicCycle; + +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#MAGIC_CYCLE}. + * Simple int-keyed map family with RELOAD, UPDATE, and REMOVE support. + */ +public class MagicCycleSyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (MagicCycle cycle : MagicController.getInstance().cycles.values()) { + NBTTagCompound cycleCompound = new NBTTagCompound(); + cycle.writeNBT(cycleCompound); + list.appendTag(cycleCompound); + } + compound.setTag("Data", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("Data", 10); + MagicController mc = MagicController.getInstance(); + mc.cyclesSync.clear(); + for (int i = 0; i < list.tagCount(); i++) { + MagicCycle cycle = new MagicCycle(); + cycle.readNBT(list.getCompoundTagAt(i)); + mc.cyclesSync.put(cycle.id, cycle); + } + + mc.cycles.clear(); + mc.cycles.putAll(mc.cyclesSync); + mc.cyclesSync.clear(); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + MagicCycle cycle = new MagicCycle(); + cycle.readNBT(compound); + MagicController.getInstance().cycles.put(cycle.id, cycle); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + MagicController.getInstance().cycles.remove(id); + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } + +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/MagicSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/MagicSyncHandler.java new file mode 100644 index 000000000..86d965cc4 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/MagicSyncHandler.java @@ -0,0 +1,95 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.MagicController; +import noppes.npcs.controllers.data.Magic; +import noppes.npcs.controllers.data.MagicCycle; + +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#MAGIC}. + * Simple int-keyed map family. + * REMOVE dissociates cycle associations before removing the magic entry. + * + *

Note: The original code had a bug where MAGIC REMOVE incorrectly + * removed from {@code cycles} instead of {@code magics}. This handler + * fixes that by properly removing from {@code magics} after dissociating.

+ */ +public class MagicSyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (Magic magic : MagicController.getInstance().magics.values()) { + NBTTagCompound magicCompound = new NBTTagCompound(); + magic.writeNBT(magicCompound); + list.appendTag(magicCompound); + } + compound.setTag("Data", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("Data", 10); + MagicController mc = MagicController.getInstance(); + mc.magicSync.clear(); + for (int i = 0; i < list.tagCount(); i++) { + Magic magic = new Magic(); + magic.readNBT(list.getCompoundTagAt(i)); + mc.magicSync.put(magic.id, magic); + } + + mc.magics.clear(); + mc.magics.putAll(mc.magicSync); + mc.magicSync.clear(); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + Magic magic = new Magic(); + magic.readNBT(compound); + MagicController.getInstance().magics.put(magic.id, magic); + } + + /** + * Removes a magic entry from the client. First dissociates cycle + * references, then removes from the magics map. + *

Bug fix: The original code incorrectly removed from {@code cycles} + * instead of {@code magics} after dissociating cycle associations.

+ */ + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + // First remove this magic's association from all cycles + for (MagicCycle cycle : MagicController.getInstance().cycles.values()) { + cycle.associations.remove(id); + } + // Then remove the magic itself (fixed: was cycles.remove(id)) + MagicController.getInstance().magics.remove(id); + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerAbilitySyncHelper.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerAbilitySyncHelper.java new file mode 100644 index 000000000..258b25edb --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerAbilitySyncHelper.java @@ -0,0 +1,47 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import kamkeel.npcs.network.packets.data.ability.AbilityCooldownSyncPacket; +import kamkeel.npcs.network.packets.data.ability.AbilityHotbarSyncPacket; +import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket; +import net.minecraft.entity.player.EntityPlayerMP; +import noppes.npcs.controllers.data.PlayerData; + +/** + * Direct per-player sync helper for player ability state. + * This is NOT a cached global sync family — it operates outside the + * registry-driven reload/revision system. + * + *

Abilities are synced per-player on demand (e.g. ability unlock, + * entity reconstruction, periodic cooldown ticks).

+ */ +public final class PlayerAbilitySyncHelper { + + private PlayerAbilitySyncHelper() { + } + + /** + * Full sync of all player ability state to the client: unlocked abilities, + * selection, toggles, hotbar layout, and cooldowns. + * Used for entity reconstruction and other full-refresh scenarios. + */ + public static void syncAbilities(EntityPlayerMP playerMP) { + PlayerData playerData = PlayerData.get(playerMP); + if (playerData == null) return; + + PlayerAbilitySyncPacket.sendToPlayer(playerMP); + AbilityHotbarSyncPacket.sendToPlayer(playerMP); + AbilityCooldownSyncPacket.sendToPlayer(playerMP); + } + + /** + * Lightweight sync of cooldown state only (global + per-ability). + * Called periodically (every 10 ticks) to keep the client's cooldown + * display accurate. Cooldowns are transient and not part of NBT sync. + */ + public static void syncAbilityCooldowns(EntityPlayerMP playerMP) { + PlayerData playerData = PlayerData.get(playerMP); + if (playerData == null) return; + + AbilityCooldownSyncPacket.sendToPlayer(playerMP); + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerDataSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerDataSyncHandler.java new file mode 100644 index 000000000..2842b5cee --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerDataSyncHandler.java @@ -0,0 +1,75 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.enums.EnumSyncAction; +import kamkeel.npcs.network.packets.data.large.SyncPacket; +import net.minecraft.entity.player.EntityPlayerMP; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.client.ClientCacheHandler; +import noppes.npcs.controllers.data.PlayerData; + +/** + * Sync handler for {@link SyncType#PLAYERDATA}. + * Special non-cached per-player path. Not included in login cache iteration. + * RELOAD and UPDATE are sent as direct per-player packets. + */ +public class PlayerDataSyncHandler implements SyncHandler { + + private static final PlayerDataSyncHandler INSTANCE = new PlayerDataSyncHandler(); + + public static PlayerDataSyncHandler getInstance() { + return INSTANCE; + } + + public void syncPlayerData(EntityPlayerMP player, boolean update) { + PlayerData data = PlayerData.get(player); + if (data != null) { + if (update) { + PacketHandler.Instance.sendToPlayer(new SyncPacket(SyncType.PLAYERDATA, EnumSyncAction.UPDATE, -1, data.getSyncNBT()), player); + } else { + PacketHandler.Instance.sendToPlayer(new SyncPacket(SyncType.PLAYERDATA, EnumSyncAction.RELOAD, -1, data.getSyncNBTFull()), player); + } + } + } + + // ========== SERVER-SIDE ========== + + /** + * PLAYERDATA is not a global cached type — it is per-player. + */ + @Override + public NBTTagCompound serializeAll() { + return null; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + ClientCacheHandler.playerData.setSyncNBTFull(fullCompound); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + ClientCacheHandler.playerData.setSyncNBT(compound); + } + + @Override + public boolean supportsUpdate() { + return true; + } + + /** + * Not a cached type — PLAYERDATA uses direct per-player sync. + */ + @Override + public boolean isCachedType() { + return false; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerEffectSyncHelper.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerEffectSyncHelper.java new file mode 100644 index 000000000..52244de44 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/PlayerEffectSyncHelper.java @@ -0,0 +1,54 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.packets.data.large.SyncEffectPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.controllers.CustomEffectController; +import noppes.npcs.controllers.data.EffectKey; +import noppes.npcs.controllers.data.PlayerData; +import noppes.npcs.controllers.data.PlayerEffect; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Direct per-player sync helper for player effects. + * This is NOT a cached global sync family — it operates outside the + * registry-driven reload/revision system. + * + *

Effects are synced per-player on demand (e.g. effect application, + * periodic ticks). The data mutates {@code playerData.effectData} + * before sending the packet.

+ */ +public final class PlayerEffectSyncHelper { + + private PlayerEffectSyncHelper() { + } + + /** + * Server-side: sync the player's active effects to their client. + * Mutates {@code playerData.effectData} before sending. + */ + public static void syncEffects(EntityPlayerMP playerMP) { + ConcurrentHashMap playerEffects = CustomEffectController.getInstance().getPlayerEffects(playerMP); + PlayerData playerData = PlayerData.get(playerMP); + playerData.effectData.setEffects(playerEffects); + + NBTTagCompound compound = playerData.getPlayerEffects(); + PacketHandler.Instance.sendToPlayer(new SyncEffectPacket(compound), playerMP); + } + + /** + * Client-side: apply incoming effect data to the local player. + */ + @SideOnly(Side.CLIENT) + public static void clientSyncEffects(NBTTagCompound compound) { + PlayerData playerData = PlayerData.get(Minecraft.getMinecraft().thePlayer); + if (playerData != null) { + playerData.setPlayerEffects(compound); + } + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/QuestCategorySyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/QuestCategorySyncHandler.java new file mode 100644 index 000000000..91d1fc893 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/QuestCategorySyncHandler.java @@ -0,0 +1,141 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.QuestController; +import noppes.npcs.controllers.data.Quest; +import noppes.npcs.controllers.data.QuestCategory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#QUEST_CATEGORY}. + * Parent-child category family. QUEST children share this cache. + * Invalidation from QUEST or QUEST_CATEGORY both target this cache. + */ +public class QuestCategorySyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + NBTTagCompound compound = new NBTTagCompound(); + NBTTagList categoryList = new NBTTagList(); + for (QuestCategory category : QuestController.Instance.categories.values()) { + NBTTagCompound questCompound = new NBTTagCompound(); + ; + NBTTagList questList = new NBTTagList(); + for (int questID : category.quests.keySet()) { + Quest quest = category.quests.get(questID); + questList.appendTag(quest.writeToNBT(new NBTTagCompound())); + } + questCompound.setTag("Data", questList); + questCompound.setTag("CatNBT", category.writeSmallNBT(new NBTTagCompound())); + categoryList.appendTag(questCompound); + } + compound.setTag("QuestCategories", categoryList); + return compound; + } + + /** + * Serialize a single quest category for UPDATE packets. + */ + public static NBTTagCompound serializeCategory(QuestCategory questCategory) { + NBTTagCompound questCompound = new NBTTagCompound(); + NBTTagList questList = new NBTTagList(); + for (int questID : questCategory.quests.keySet()) { + Quest quest = questCategory.quests.get(questID); + questList.appendTag(quest.writeToNBT(new NBTTagCompound())); + } + questCompound.setTag("Data", questList); + questCompound.setTag("CatNBT", questCategory.writeSmallNBT(new NBTTagCompound())); + return questCompound; + } + + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + if (!fullCompound.hasNoTags()) { + NBTTagList categories = fullCompound.getTagList("QuestCategories", 10); + for (int j = 0; j < fullCompound.getTagList("QuestCategories", 10).tagCount(); j++) { + NBTTagCompound categoryCompound = categories.getCompoundTagAt(j); + if (categoryCompound.hasNoTags()) + continue; + + QuestCategory category = new QuestCategory(); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + NBTTagList questList = categoryCompound.getTagList("Data", 10); + if (QuestController.Instance.categoriesSync.containsKey(category.id)) { + category = QuestController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(categoryCompound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < questList.tagCount(); i++) { + Quest quest = new Quest(); + quest.readNBT(questList.getCompoundTagAt(i)); + quest.category = category; + category.quests.put(quest.id, quest); + } + QuestController.Instance.categoriesSync.put(category.id, category); + } + } + + HashMap quests = new HashMap<>(); + for (QuestCategory category : QuestController.Instance.categoriesSync.values()) { + for (Quest quest : category.quests.values()) { + quests.put(quest.id, quest); + } + } + + QuestController.Instance.categories = QuestController.Instance.categoriesSync; + QuestController.Instance.quests = quests; + QuestController.Instance.categoriesSync = new HashMap<>(); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + QuestCategory category = new QuestCategory(); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + NBTTagList list = compound.getTagList("Data", 10); + if (QuestController.Instance.categoriesSync.containsKey(category.id)) { + category = QuestController.Instance.categoriesSync.get(category.id); + category.readSmallNBT(compound.getCompoundTag("CatNBT")); + } + for (int i = 0; i < list.tagCount(); i++) { + Quest quest = new Quest(); + quest.readNBT(list.getCompoundTagAt(i)); + quest.category = category; + category.quests.put(quest.id, quest); + } + QuestController.Instance.categories.put(category.id, category); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + QuestCategory questCategory = QuestController.Instance.categories.remove(id); + if (questCategory != null) { + QuestController.Instance.quests.keySet().removeAll(questCategory.quests.keySet()); + } + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } + +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/QuestSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/QuestSyncHandler.java new file mode 100644 index 000000000..172bf5535 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/QuestSyncHandler.java @@ -0,0 +1,90 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.controllers.QuestController; +import noppes.npcs.controllers.data.Quest; +import noppes.npcs.controllers.data.QuestCategory; + +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#QUEST}. + * Child family — does not have its own cache. UPDATE and REMOVE only. + * Invalidation targets the parent {@link SyncType#QUEST_CATEGORY} cache. + */ +public class QuestSyncHandler implements SyncHandler { + + // ========== SERVER-SIDE ========== + + /** + * QUEST does not have its own independent cache; it shares + * the QUEST_CATEGORY parent cache. + */ + @Override + public NBTTagCompound serializeAll() { + return null; + } + + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + // QUEST does not handle standalone RELOAD; it's part of QUEST_CATEGORY + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleUpdate(NBTTagCompound compound, int categoryId) { + QuestCategory category = QuestController.Instance.categories.get(categoryId); + if (category == null) + return; // Defensive: category may have been removed + + Quest quest = new Quest(); + quest.category = category; + quest.readNBT(compound); + QuestController.Instance.quests.put(quest.id, quest); + category.quests.put(quest.id, quest); + } + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleRemove(int id) { + Quest quest = QuestController.Instance.quests.remove(id); + if (quest != null) + quest.category.quests.remove(id); + + } + + @Override + public boolean supportsUpdate() { + return true; + } + + @Override + public boolean supportsRemove() { + return true; + } + + /** + * Not a cached type — QUEST shares the QUEST_CATEGORY cache. + */ + @Override + public boolean isCachedType() { + return false; + } + + @Override + public Set getInvalidationTargets(SyncType self) { + // Child invalidates the parent cache + Set set = new HashSet<>(); + set.add(SyncType.QUEST_CATEGORY); + return set; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/sync/handlers/WorkbenchRecipeSyncHandler.java b/src/main/java/kamkeel/npcs/controllers/sync/handlers/WorkbenchRecipeSyncHandler.java new file mode 100644 index 000000000..02d52f25a --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/sync/handlers/WorkbenchRecipeSyncHandler.java @@ -0,0 +1,70 @@ +package kamkeel.npcs.controllers.sync.handlers; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.sync.SyncHandler; +import kamkeel.npcs.network.enums.SyncType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.RecipeController; +import noppes.npcs.controllers.data.RecipeCarpentry; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Sync handler for {@link SyncType#WORKBENCH_RECIPES}. + * RELOAD-only family — does not support individual UPDATE or REMOVE. + * Client-side RELOAD routes through {@link RecipeController#reloadGlobalRecipes}. + */ +public class WorkbenchRecipeSyncHandler implements SyncHandler { + + private static final WorkbenchRecipeSyncHandler INSTANCE = new WorkbenchRecipeSyncHandler(); + + public static WorkbenchRecipeSyncHandler getInstance() { + return INSTANCE; + } + // ========== SERVER-SIDE ========== + + @Override + public NBTTagCompound serializeAll() { + RecipeController controller = RecipeController.Instance; + NBTTagList list = new NBTTagList(); + NBTTagCompound compound = new NBTTagCompound(); + for (RecipeCarpentry recipe : controller.globalRecipes.values()) { + list.appendTag(recipe.writeNBT(false)); + } + compound.setTag("recipes", list); + return compound; + } + + // ========== CLIENT-SIDE ========== + + @SideOnly(Side.CLIENT) + @Override + public void clientHandleReload(NBTTagCompound fullCompound) { + NBTTagList list = fullCompound.getTagList("recipes", 10); + if (list == null) + return; + + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound recipeCompound = list.getCompoundTagAt(i); + RecipeCarpentry recipe = RecipeCarpentry.create(recipeCompound); + recipe.readNBT(recipeCompound); + RecipeController.syncRecipes.put(recipe.id, recipe); + } + + RecipeController.reloadGlobalRecipes(RecipeController.syncRecipes); + RecipeController.syncRecipes = new HashMap<>(); + } + + @Override + public Set getInvalidationTargets(SyncType self) { + Set set = new HashSet<>(); + set.add(SyncType.WORKBENCH_RECIPES); + return set; + } + + // RELOAD-only: supportsUpdate() and supportsRemove() remain false (default) +} diff --git a/src/main/java/kamkeel/npcs/network/packets/data/large/SyncEffectPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/large/SyncEffectPacket.java index 66d7a5756..026ec44bf 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/large/SyncEffectPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/large/SyncEffectPacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.PlayerEffectSyncHelper; import kamkeel.npcs.network.LargeAbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; @@ -54,7 +54,7 @@ public final class SyncEffectPacket extends LargeAbstractPacket { return; try { NBTTagCompound tag = ByteBufUtils.readBigNBT(data); - SyncController.clientSyncEffects(tag); + PlayerEffectSyncHelper.clientSyncEffects(tag); } catch (RuntimeException e) { LogWriter.error(String.format("Attempted to Sync Effects but it was too big")); } diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index 3133fb43c..8c1ef02c9 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -9,7 +9,9 @@ import kamkeel.npcs.addon.DBCAddon; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.data.attribute.tracker.PlayerAttributeTracker; -import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.PlayerAbilitySyncHelper; +import kamkeel.npcs.controllers.sync.handlers.PlayerDataSyncHandler; +import kamkeel.npcs.controllers.sync.handlers.PlayerEffectSyncHelper; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.type.AbilityCounter; import kamkeel.npcs.controllers.data.ability.type.AbilityDefend; @@ -141,7 +143,7 @@ public class ScriptPlayerEventHandler { } if (playerData.updateClient) { - SyncController.syncPlayerData((EntityPlayerMP) player, true); + PlayerDataSyncHandler.getInstance().syncPlayerData((EntityPlayerMP) player, true); playerData.updateClient = false; } @@ -151,8 +153,8 @@ public class ScriptPlayerEventHandler { if (player.ticksExisted % 10 == 0) { EntityPlayerMP mp = (EntityPlayerMP) player; - SyncController.syncEffects(mp); - SyncController.syncAbilityCooldowns(mp); + PlayerEffectSyncHelper.syncEffects(mp); + PlayerAbilitySyncHelper.syncAbilityCooldowns(mp); PlayerAbilitySyncPacket.sendToPlayer(mp); playerData.abilityData.ensureLockStateClear(); } diff --git a/src/main/java/noppes/npcs/ServerEventsHandler.java b/src/main/java/noppes/npcs/ServerEventsHandler.java index a8c5f2b4a..1ad41c657 100644 --- a/src/main/java/noppes/npcs/ServerEventsHandler.java +++ b/src/main/java/noppes/npcs/ServerEventsHandler.java @@ -4,6 +4,8 @@ import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.CarpentryRecipeSyncHandler; +import kamkeel.npcs.controllers.sync.handlers.WorkbenchRecipeSyncHandler; import kamkeel.npcs.controllers.data.energycharge.EnergyChargeTracker; import kamkeel.npcs.entity.EntityEnergyBarrier; import kamkeel.npcs.entity.EntityEnergyDome; @@ -259,7 +261,7 @@ public class ServerEventsHandler { EnumSyncAction.RELOAD, -1, SyncController.getCurrentRevision(SyncType.WORKBENCH_RECIPES), - SyncController.workbenchNBT() + WorkbenchRecipeSyncHandler.getInstance().serializeAll() ), (EntityPlayerMP) player); } if (block == CustomItems.carpentyBench && event.action == Action.RIGHT_CLICK_BLOCK && !player.worldObj.isRemote) { @@ -268,7 +270,7 @@ public class ServerEventsHandler { EnumSyncAction.RELOAD, -1, SyncController.getCurrentRevision(SyncType.CARPENTRY_RECIPES), - SyncController.carpentryNBT() + CarpentryRecipeSyncHandler.getInstance().serializeAll() ), (EntityPlayerMP) player); } if ((block == CustomItems.banner || block == CustomItems.wallBanner || block == CustomItems.sign) && event.action == Action.RIGHT_CLICK_BLOCK) { diff --git a/src/main/java/noppes/npcs/ServerTickHandler.java b/src/main/java/noppes/npcs/ServerTickHandler.java index 12a0f29ce..52bebe1ef 100644 --- a/src/main/java/noppes/npcs/ServerTickHandler.java +++ b/src/main/java/noppes/npcs/ServerTickHandler.java @@ -6,6 +6,8 @@ import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.PlayerAbilitySyncHelper; +import kamkeel.npcs.controllers.sync.handlers.PlayerEffectSyncHelper; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.world.WorldServer; import noppes.npcs.controllers.AuctionController; @@ -45,13 +47,12 @@ public class ServerTickHandler { playerData.onLogin(); } - ProfileController.Instance.login(player); SyncController.beginLogin(player); - SyncController.syncEffects(player); + PlayerEffectSyncHelper.syncEffects(player); ScriptController.Instance.syncClientScripts(player); // Sync clean ability state to the new client (after beginLogin initializes sync state) - SyncController.syncAbilities(player); + PlayerAbilitySyncHelper.syncAbilities(player); // Send auction notifications on login if (AuctionController.Instance != null) { diff --git a/src/main/java/noppes/npcs/controllers/DialogController.java b/src/main/java/noppes/npcs/controllers/DialogController.java index 0e2df3d84..df652ac89 100644 --- a/src/main/java/noppes/npcs/controllers/DialogController.java +++ b/src/main/java/noppes/npcs/controllers/DialogController.java @@ -1,6 +1,7 @@ package noppes.npcs.controllers; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.DialogCategorySyncHandler; import kamkeel.npcs.network.enums.SyncType; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; @@ -239,7 +240,7 @@ public class DialogController implements IDialogHandler { dir.mkdirs(); } categories.put(category.id, category); - SyncController.syncUpdate(SyncType.DIALOG_CATEGORY, -1, SyncController.updateDialogCat(category)); + SyncController.syncUpdate(SyncType.DIALOG_CATEGORY, -1, DialogCategorySyncHandler.serializeCategory(category)); } public void removeCategory(int category) { diff --git a/src/main/java/noppes/npcs/controllers/QuestController.java b/src/main/java/noppes/npcs/controllers/QuestController.java index 59136b2aa..69bbe88d0 100644 --- a/src/main/java/noppes/npcs/controllers/QuestController.java +++ b/src/main/java/noppes/npcs/controllers/QuestController.java @@ -1,6 +1,7 @@ package noppes.npcs.controllers; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.QuestCategorySyncHandler; import kamkeel.npcs.network.enums.SyncType; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; @@ -188,7 +189,7 @@ public class QuestController implements IQuestHandler { dir.mkdirs(); } categories.put(category.id, category); - SyncController.syncUpdate(SyncType.QUEST_CATEGORY, -1, SyncController.updateQuestCat(category)); + SyncController.syncUpdate(SyncType.QUEST_CATEGORY, -1, QuestCategorySyncHandler.serializeCategory(category)); } private boolean containsCategoryName(String name) { diff --git a/src/main/java/noppes/npcs/controllers/RecipeController.java b/src/main/java/noppes/npcs/controllers/RecipeController.java index 7fccdec93..621726927 100644 --- a/src/main/java/noppes/npcs/controllers/RecipeController.java +++ b/src/main/java/noppes/npcs/controllers/RecipeController.java @@ -1,6 +1,7 @@ package noppes.npcs.controllers; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.network.enums.SyncType; import net.minecraft.inventory.InventoryCrafting; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.CraftingManager; @@ -282,12 +283,12 @@ public class RecipeController implements IRecipeHandler { return null; RecipeCarpentry globalRecipe = globalRecipes.remove(recipe.id); if (globalRecipe != null) - SyncController.syncAllWorkbenchRecipes(); + SyncController.syncAll(SyncType.WORKBENCH_RECIPES); RecipeCarpentry carpentry = carpentryRecipes.remove(recipe.id); if (carpentry != null) { carpentryScripts.remove(recipe.id); - SyncController.syncAllCarpentryRecipes(); + SyncController.syncAll(SyncType.CARPENTRY_RECIPES); } saveCategories(); @@ -302,7 +303,7 @@ public class RecipeController implements IRecipeHandler { RecipeAnvil anvilRecipe = anvilRecipes.remove(recipe.id); if (anvilRecipe != null) - SyncController.syncAllAnvilRecipes(); + SyncController.syncAll(SyncType.ANVIL_RECIPES); saveCategories(); return recipe; diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 916f2949a..8b444cbf2 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -1,7 +1,7 @@ package noppes.npcs.controllers.data; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.controllers.sync.handlers.PlayerAbilitySyncHelper; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; @@ -496,23 +496,23 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA /** * Full sync of all ability state to the client. - * Delegates to {@link SyncController#syncAbilities(EntityPlayerMP)}. + * Delegates to {@link PlayerAbilitySyncHelper#syncAbilities(EntityPlayerMP)}. */ public void syncToClient() { EntityPlayer player = playerData.player; if (player instanceof EntityPlayerMP) { - SyncController.syncAbilities((EntityPlayerMP) player); + PlayerAbilitySyncHelper.syncAbilities((EntityPlayerMP) player); } } /** * Lightweight sync of cooldown state only to the client. - * Delegates to {@link SyncController#syncAbilityCooldowns(EntityPlayerMP)}. + * Delegates to {@link PlayerAbilitySyncHelper#syncAbilityCooldowns(EntityPlayerMP)}. */ public void syncCooldownToClient() { EntityPlayer player = playerData.player; if (player instanceof EntityPlayerMP) { - SyncController.syncAbilityCooldowns((EntityPlayerMP) player); + PlayerAbilitySyncHelper.syncAbilityCooldowns((EntityPlayerMP) player); } }