mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 18:57:32 +00:00
Merge pull request #220 from KAMKEEL/codex/optimize-player-sync-performance-0c9js2
Add debug logging toggle for sync controller
This commit is contained in:
24 files changed
+715
-124
No files matched your search
@@ -3,6 +3,9 @@ package kamkeel.npcs.controllers;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.addon.DBCAddon;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import kamkeel.npcs.network.LargeAbstractPacket;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.enums.EnumSyncAction;
|
||||
import kamkeel.npcs.network.enums.EnumSyncType;
|
||||
@@ -10,10 +13,12 @@ import kamkeel.npcs.network.packets.data.LoginPacket;
|
||||
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;
|
||||
@@ -35,91 +40,169 @@ 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.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SyncController {
|
||||
|
||||
public static boolean DEBUG_SYNC_LOGGING = false;
|
||||
|
||||
private static void debug(String message, Object... args) {
|
||||
if (DEBUG_SYNC_LOGGING) {
|
||||
System.out.println("[SyncController] " + String.format(message, args));
|
||||
}
|
||||
}
|
||||
|
||||
private static final EnumSyncType[] LOGIN_SYNC_TYPES = new EnumSyncType[]{
|
||||
EnumSyncType.FACTION,
|
||||
EnumSyncType.DIALOG_CATEGORY,
|
||||
EnumSyncType.QUEST_CATEGORY,
|
||||
EnumSyncType.WORKBENCH_RECIPES,
|
||||
EnumSyncType.CARPENTRY_RECIPES,
|
||||
EnumSyncType.ANVIL_RECIPES,
|
||||
EnumSyncType.CUSTOM_EFFECTS,
|
||||
EnumSyncType.MAGIC,
|
||||
EnumSyncType.MAGIC_CYCLE
|
||||
};
|
||||
|
||||
private static final EnumMap<EnumSyncType, SyncCacheEntry> CACHE_ENTRIES = new EnumMap<>(EnumSyncType.class);
|
||||
private static final ConcurrentHashMap<UUID, PlayerSyncState> PLAYER_SYNC_STATE = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
registerCache(EnumSyncType.FACTION, SyncController::factionsNBT);
|
||||
registerCache(EnumSyncType.DIALOG_CATEGORY, SyncController::dialogCategoriesNBT);
|
||||
registerCache(EnumSyncType.QUEST_CATEGORY, SyncController::questCategoriesNBT);
|
||||
registerCache(EnumSyncType.WORKBENCH_RECIPES, SyncController::workbenchNBT);
|
||||
registerCache(EnumSyncType.CARPENTRY_RECIPES, SyncController::carpentryNBT);
|
||||
registerCache(EnumSyncType.ANVIL_RECIPES, SyncController::anvilNBT);
|
||||
registerCache(EnumSyncType.CUSTOM_EFFECTS, SyncController::customEffectsNBT);
|
||||
registerCache(EnumSyncType.MAGIC, SyncController::magicsNBT);
|
||||
registerCache(EnumSyncType.MAGIC_CYCLE, SyncController::magicCyclesNBT);
|
||||
}
|
||||
|
||||
public static void syncPlayer(EntityPlayerMP player) {
|
||||
syncPlayer(player, true);
|
||||
}
|
||||
|
||||
// Login Packet
|
||||
PacketHandler.Instance.sendToPlayer(new LoginPacket(), player);
|
||||
private static void syncPlayer(EntityPlayerMP player, boolean includePostPackets) {
|
||||
PlayerSyncState state = PLAYER_SYNC_STATE.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new);
|
||||
|
||||
// 1) Factions
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.FACTION,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
factionsNBT()
|
||||
), player);
|
||||
for (EnumSyncType type : LOGIN_SYNC_TYPES) {
|
||||
SyncCacheEntry entry = CACHE_ENTRIES.get(type);
|
||||
if (entry == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2) Dialog Categories
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.DIALOG_CATEGORY,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
dialogCategoriesNBT()
|
||||
), player);
|
||||
int currentRevision = entry.getRevisionValue();
|
||||
int lastRevision = state.getRevision(type);
|
||||
if (lastRevision == currentRevision) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3) Quest Categories
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.QUEST_CATEGORY,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
questCategoriesNBT()
|
||||
), player);
|
||||
CachedSyncPayload payload = entry.getPayload(type);
|
||||
if (payload == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4) Workbench Recipes
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.WORKBENCH_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
workbenchNBT()
|
||||
), player);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// 5) Carpentry Recipes
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.CARPENTRY_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
carpentryNBT()
|
||||
), player);
|
||||
if (includePostPackets) {
|
||||
sendPostLoginPackets(player);
|
||||
}
|
||||
}
|
||||
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.ANVIL_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
anvilNBT()
|
||||
), player);
|
||||
public static void beginLogin(EntityPlayerMP player) {
|
||||
PLAYER_SYNC_STATE.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new);
|
||||
PacketHandler.Instance.sendToPlayer(
|
||||
new LoginPacket(getServerCacheKey(), getServerRevisionSnapshot()),
|
||||
player
|
||||
);
|
||||
}
|
||||
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.CUSTOM_EFFECTS,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
customEffectsNBT()
|
||||
), player);
|
||||
public static void handleClientRevisionReport(
|
||||
EntityPlayerMP player,
|
||||
String serverKey,
|
||||
String previousServerKey,
|
||||
Map<EnumSyncType, Integer> clientRevisions
|
||||
) {
|
||||
String currentServerKey = getServerCacheKey();
|
||||
PlayerSyncState state = PLAYER_SYNC_STATE.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new);
|
||||
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.MAGIC,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
magicsNBT()
|
||||
), player);
|
||||
if (!currentServerKey.equals(serverKey)) {
|
||||
state.reset();
|
||||
syncPlayer(player);
|
||||
return;
|
||||
}
|
||||
|
||||
PacketHandler.Instance.sendToPlayer(new SyncPacket(
|
||||
EnumSyncType.MAGIC_CYCLE,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
magicCyclesNBT()
|
||||
), player);
|
||||
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);
|
||||
|
||||
// Send party information to the player on login
|
||||
PartyInfoPacket.sendPartyData(player);
|
||||
}
|
||||
|
||||
private static EnumMap<EnumSyncType, Integer> getServerRevisionSnapshot() {
|
||||
EnumMap<EnumSyncType, Integer> snapshot = new EnumMap<>(EnumSyncType.class);
|
||||
for (EnumSyncType type : LOGIN_SYNC_TYPES) {
|
||||
SyncCacheEntry entry = CACHE_ENTRIES.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(EnumSyncType type) {
|
||||
SyncCacheEntry entry = CACHE_ENTRIES.get(type);
|
||||
return entry == null ? -1 : entry.getRevisionValue();
|
||||
}
|
||||
|
||||
public static NBTTagCompound workbenchNBT() {
|
||||
RecipeController controller = RecipeController.Instance;
|
||||
NBTTagList list = new NBTTagList();
|
||||
@@ -257,70 +340,74 @@ public class SyncController {
|
||||
}
|
||||
|
||||
public static void syncRemove(EnumSyncType enumSyncType, int id) {
|
||||
Map<EnumSyncType, Integer> revisions = invalidateCaches(enumSyncType);
|
||||
int revision = revisions.getOrDefault(enumSyncType, getCurrentRevision(enumSyncType));
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
enumSyncType,
|
||||
EnumSyncAction.REMOVE,
|
||||
id,
|
||||
revision,
|
||||
new NBTTagCompound()
|
||||
));
|
||||
updateAllPlayerRevisions(revisions);
|
||||
}
|
||||
|
||||
public static void syncAllDialogs() {
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
EnumSyncType.DIALOG_CATEGORY,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
dialogCategoriesNBT()
|
||||
));
|
||||
CachedSyncPayload payload = rebuildNow(EnumSyncType.DIALOG_CATEGORY);
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.DIALOG_CATEGORY, payload));
|
||||
updateAllPlayerRevisions(EnumSyncType.DIALOG_CATEGORY, payload.getRevision());
|
||||
}
|
||||
|
||||
public static void syncAllQuests() {
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
EnumSyncType.QUEST_CATEGORY,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
questCategoriesNBT()
|
||||
));
|
||||
CachedSyncPayload payload = rebuildNow(EnumSyncType.QUEST_CATEGORY);
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.QUEST_CATEGORY, payload));
|
||||
updateAllPlayerRevisions(EnumSyncType.QUEST_CATEGORY, payload.getRevision());
|
||||
}
|
||||
|
||||
public static void syncAllWorkbenchRecipes() {
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
EnumSyncType.WORKBENCH_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
workbenchNBT()
|
||||
));
|
||||
CachedSyncPayload payload = rebuildNow(EnumSyncType.WORKBENCH_RECIPES);
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.WORKBENCH_RECIPES, payload));
|
||||
updateAllPlayerRevisions(EnumSyncType.WORKBENCH_RECIPES, payload.getRevision());
|
||||
}
|
||||
|
||||
public static void syncAllCarpentryRecipes() {
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
EnumSyncType.CARPENTRY_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
carpentryNBT()
|
||||
));
|
||||
CachedSyncPayload payload = rebuildNow(EnumSyncType.CARPENTRY_RECIPES);
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.CARPENTRY_RECIPES, payload));
|
||||
updateAllPlayerRevisions(EnumSyncType.CARPENTRY_RECIPES, payload.getRevision());
|
||||
}
|
||||
|
||||
public static void syncAllAnvilRecipes() {
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
EnumSyncType.ANVIL_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
anvilNBT()
|
||||
));
|
||||
CachedSyncPayload payload = rebuildNow(EnumSyncType.ANVIL_RECIPES);
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.ANVIL_RECIPES, payload));
|
||||
updateAllPlayerRevisions(EnumSyncType.ANVIL_RECIPES, payload.getRevision());
|
||||
}
|
||||
|
||||
public static void syncAllCustomEffects() {
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
EnumSyncType.CUSTOM_EFFECTS,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
customEffectsNBT()
|
||||
));
|
||||
CachedSyncPayload payload = rebuildNow(EnumSyncType.CUSTOM_EFFECTS);
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(EnumSyncType.CUSTOM_EFFECTS, payload));
|
||||
updateAllPlayerRevisions(EnumSyncType.CUSTOM_EFFECTS, payload.getRevision());
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public static void clientSync(EnumSyncType enumSyncType, NBTTagCompound fullCompound) {
|
||||
public static void clientSync(EnumSyncType enumSyncType, int revision, NBTTagCompound fullCompound) {
|
||||
switch (enumSyncType) {
|
||||
case FACTION: {
|
||||
NBTTagList list = fullCompound.getTagList("Factions", 10);
|
||||
@@ -505,13 +592,15 @@ public class SyncController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ClientCacheHandler.updateClientRevision(enumSyncType, revision);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Related Tasks
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public static void clientUpdate(EnumSyncType enumSyncType, int category_id, NBTTagCompound compound) {
|
||||
public static void clientUpdate(EnumSyncType enumSyncType, int category_id, int revision, NBTTagCompound compound) {
|
||||
switch (enumSyncType) {
|
||||
case FACTION: {
|
||||
Faction faction = new Faction();
|
||||
@@ -596,15 +685,21 @@ public class SyncController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ClientCacheHandler.updateClientRevision(enumSyncType, revision);
|
||||
}
|
||||
|
||||
public static void syncUpdate(EnumSyncType type, int cat, NBTTagCompound compound) {
|
||||
Map<EnumSyncType, Integer> revisions = invalidateCaches(type);
|
||||
int revision = revisions.getOrDefault(type, getCurrentRevision(type));
|
||||
PacketHandler.Instance.sendToAll(new SyncPacket(
|
||||
type,
|
||||
EnumSyncAction.UPDATE,
|
||||
cat,
|
||||
revision,
|
||||
compound
|
||||
));
|
||||
updateAllPlayerRevisions(revisions);
|
||||
}
|
||||
|
||||
|
||||
@@ -633,7 +728,7 @@ public class SyncController {
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public static void clientSyncRemove(EnumSyncType enumSyncType, int id) {
|
||||
public static void clientSyncRemove(EnumSyncType enumSyncType, int id, int revision) {
|
||||
switch (enumSyncType) {
|
||||
case FACTION:
|
||||
FactionController.getInstance().factions.remove(id);
|
||||
@@ -675,6 +770,8 @@ public class SyncController {
|
||||
CustomEffectController.Instance.getCustomEffects().remove(id);
|
||||
break;
|
||||
}
|
||||
|
||||
ClientCacheHandler.updateClientRevision(enumSyncType, revision);
|
||||
}
|
||||
|
||||
public static void syncEffects(EntityPlayerMP playerMP) {
|
||||
@@ -693,4 +790,198 @@ public class SyncController {
|
||||
playerData.setPlayerEffects(compound);
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerCache(EnumSyncType type, Supplier<NBTTagCompound> supplier) {
|
||||
CACHE_ENTRIES.put(type, new SyncCacheEntry(type, supplier));
|
||||
}
|
||||
|
||||
private static CachedSyncPayload rebuildNow(EnumSyncType type) {
|
||||
SyncCacheEntry entry = CACHE_ENTRIES.get(type);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
entry.invalidate();
|
||||
return entry.getPayload(type);
|
||||
}
|
||||
|
||||
public static Map<EnumSyncType, Integer> invalidateCaches(EnumSyncType type) {
|
||||
EnumSet<EnumSyncType> targets = getInvalidationTargets(type);
|
||||
EnumMap<EnumSyncType, Integer> revisions = new EnumMap<>(EnumSyncType.class);
|
||||
for (EnumSyncType target : targets) {
|
||||
SyncCacheEntry entry = CACHE_ENTRIES.get(target);
|
||||
if (entry != null) {
|
||||
int newRevision = entry.invalidate();
|
||||
revisions.put(target, newRevision);
|
||||
}
|
||||
}
|
||||
return revisions;
|
||||
}
|
||||
|
||||
private static EnumSet<EnumSyncType> getInvalidationTargets(EnumSyncType type) {
|
||||
switch (type) {
|
||||
case DIALOG:
|
||||
case DIALOG_CATEGORY:
|
||||
return EnumSet.of(EnumSyncType.DIALOG_CATEGORY);
|
||||
case QUEST:
|
||||
case QUEST_CATEGORY:
|
||||
return EnumSet.of(EnumSyncType.QUEST_CATEGORY);
|
||||
case FACTION:
|
||||
return EnumSet.of(EnumSyncType.FACTION);
|
||||
case MAGIC:
|
||||
return EnumSet.of(EnumSyncType.MAGIC);
|
||||
case MAGIC_CYCLE:
|
||||
return EnumSet.of(EnumSyncType.MAGIC_CYCLE);
|
||||
case WORKBENCH_RECIPES:
|
||||
return EnumSet.of(EnumSyncType.WORKBENCH_RECIPES);
|
||||
case CARPENTRY_RECIPES:
|
||||
return EnumSet.of(EnumSyncType.CARPENTRY_RECIPES);
|
||||
case ANVIL_RECIPES:
|
||||
return EnumSet.of(EnumSyncType.ANVIL_RECIPES);
|
||||
case CUSTOM_EFFECTS:
|
||||
return EnumSet.of(EnumSyncType.CUSTOM_EFFECTS);
|
||||
default:
|
||||
return EnumSet.noneOf(EnumSyncType.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateAllPlayerRevisions(EnumSyncType type, int revision) {
|
||||
if (revision < 0) {
|
||||
return;
|
||||
}
|
||||
for (PlayerSyncState state : PLAYER_SYNC_STATE.values()) {
|
||||
state.updateRevision(type, revision);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateAllPlayerRevisions(Map<EnumSyncType, Integer> revisions) {
|
||||
if (revisions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (PlayerSyncState state : PLAYER_SYNC_STATE.values()) {
|
||||
for (Map.Entry<EnumSyncType, Integer> 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<NBTTagCompound> supplier;
|
||||
private volatile CachedSyncPayload payload;
|
||||
private int revision = 0;
|
||||
private boolean dirty = true;
|
||||
|
||||
private SyncCacheEntry(EnumSyncType type, Supplier<NBTTagCompound> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
private synchronized CachedSyncPayload getPayload(EnumSyncType 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 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 EnumMap<EnumSyncType, Integer> revisions = new EnumMap<>(EnumSyncType.class);
|
||||
|
||||
private PlayerSyncState(UUID ignored) {
|
||||
}
|
||||
|
||||
private synchronized int getRevision(EnumSyncType type) {
|
||||
Integer value = revisions.get(type);
|
||||
return value == null ? -1 : value;
|
||||
}
|
||||
|
||||
private synchronized void updateRevision(EnumSyncType type, int revision) {
|
||||
revisions.put(type, revision);
|
||||
}
|
||||
|
||||
private synchronized void applyHandshake(Map<EnumSyncType, Integer> incoming) {
|
||||
if (incoming == null || incoming.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
revisions.putAll(incoming);
|
||||
}
|
||||
|
||||
private synchronized void reset() {
|
||||
revisions.clear();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public abstract class LargeAbstractPacket extends AbstractPacket {
|
||||
|
||||
private static final Map<UUID, PacketStorage> packetChunks = new ConcurrentHashMap<>();
|
||||
private static final int CHUNK_SIZE = 10000;
|
||||
public static final int CHUNK_SIZE = 10000;
|
||||
|
||||
/**
|
||||
* Create multiple FMLProxyPacket objects, each containing up to CHUNK_SIZE bytes.
|
||||
|
||||
@@ -71,6 +71,7 @@ import kamkeel.npcs.network.packets.player.item.MagicCyclesPacket;
|
||||
import kamkeel.npcs.network.packets.player.profile.ProfileChangePacket;
|
||||
import kamkeel.npcs.network.packets.player.profile.ProfileCreatePacket;
|
||||
import kamkeel.npcs.network.packets.player.profile.ProfileGetInfoPacket;
|
||||
import kamkeel.npcs.network.packets.player.SyncRevisionInfoPacket;
|
||||
import kamkeel.npcs.network.packets.player.profile.ProfileGetPacket;
|
||||
import kamkeel.npcs.network.packets.player.profile.ProfileRemovePacket;
|
||||
import kamkeel.npcs.network.packets.player.profile.ProfileRenamePacket;
|
||||
@@ -575,6 +576,8 @@ public class PacketHandler {
|
||||
|
||||
PLAYER_PACKET.registerPacket(new ScreenSizePacket());
|
||||
|
||||
PLAYER_PACKET.registerPacket(new SyncRevisionInfoPacket());
|
||||
|
||||
// CustomGUI Packets
|
||||
PLAYER_PACKET.registerPacket(new CustomGuiButtonPacket());
|
||||
PLAYER_PACKET.registerPacket(new CustomGuiClosePacket());
|
||||
|
||||
@@ -28,5 +28,8 @@ public enum EnumPlayerPacket {
|
||||
ProfileCreate,
|
||||
ProfileRemove,
|
||||
ProfileRename,
|
||||
ProfileChange, ProfileGet, ProfileGetInfo,
|
||||
ProfileChange,
|
||||
ProfileGet,
|
||||
ProfileGetInfo,
|
||||
SyncRevisionInfo,
|
||||
}
|
||||
@@ -5,20 +5,38 @@ import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.enums.EnumDataPacket;
|
||||
import kamkeel.npcs.network.enums.EnumSyncType;
|
||||
import kamkeel.npcs.network.packets.player.SyncRevisionInfoPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.config.ConfigMain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class LoginPacket extends AbstractPacket {
|
||||
public static final String packetName = "CNPC+|Login";
|
||||
|
||||
private String serverCacheKey = "";
|
||||
private final EnumMap<EnumSyncType, Integer> revisionSnapshot = new EnumMap<>(EnumSyncType.class);
|
||||
|
||||
public LoginPacket() {
|
||||
}
|
||||
|
||||
public LoginPacket(String serverCacheKey, EnumMap<EnumSyncType, Integer> revisionSnapshot) {
|
||||
if (serverCacheKey != null) {
|
||||
this.serverCacheKey = serverCacheKey;
|
||||
}
|
||||
if (revisionSnapshot != null) {
|
||||
this.revisionSnapshot.putAll(revisionSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumDataPacket.LOGIN;
|
||||
@@ -33,6 +51,12 @@ public final class LoginPacket extends AbstractPacket {
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeBoolean(ConfigMain.PartiesEnabled);
|
||||
out.writeBoolean(ConfigMain.ProfilesEnabled);
|
||||
ByteBufUtils.writeString(out, serverCacheKey);
|
||||
out.writeShort(revisionSnapshot.size());
|
||||
for (Map.Entry<EnumSyncType, Integer> entry : revisionSnapshot.entrySet()) {
|
||||
out.writeInt(entry.getKey().ordinal());
|
||||
out.writeInt(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@@ -40,5 +64,22 @@ public final class LoginPacket extends AbstractPacket {
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
ClientCacheHandler.allowParties = in.readBoolean();
|
||||
ClientCacheHandler.allowProfiles = in.readBoolean();
|
||||
|
||||
String serverKey = ByteBufUtils.readString(in);
|
||||
int revisionCount = in.readUnsignedShort();
|
||||
EnumMap<EnumSyncType, Integer> serverRevisions = new EnumMap<>(EnumSyncType.class);
|
||||
for (int i = 0; i < revisionCount; i++) {
|
||||
EnumSyncType type = EnumSyncType.values()[in.readInt()];
|
||||
int revision = in.readInt();
|
||||
serverRevisions.put(type, revision);
|
||||
}
|
||||
|
||||
ClientCacheHandler.setActiveServer(serverKey, serverRevisions);
|
||||
|
||||
PacketClient.sendClient(new SyncRevisionInfoPacket(
|
||||
serverKey,
|
||||
ClientCacheHandler.getLastServerKey(),
|
||||
ClientCacheHandler.getCachedRevisionsForServer(serverKey)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package kamkeel.npcs.network.packets.data.large;
|
||||
|
||||
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -17,6 +18,9 @@ import noppes.npcs.CustomNpcs;
|
||||
import noppes.npcs.LogWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A large sync packet that sends chunked data to the client for a given SyncType
|
||||
@@ -28,6 +32,9 @@ public final class SyncPacket extends LargeAbstractPacket {
|
||||
private EnumSyncAction enumSyncAction;
|
||||
private NBTTagCompound syncData;
|
||||
private int operationID;
|
||||
private int revision = -1;
|
||||
private byte[] cachedPayload;
|
||||
private byte[][] cachedChunks;
|
||||
|
||||
public SyncPacket() {
|
||||
}
|
||||
@@ -36,10 +43,24 @@ public final class SyncPacket extends LargeAbstractPacket {
|
||||
* Constructs a new LargeSyncPacket.
|
||||
*/
|
||||
public SyncPacket(EnumSyncType enumSyncType, EnumSyncAction enumSyncAction, int catId, NBTTagCompound syncData) {
|
||||
this(enumSyncType, enumSyncAction, catId, -1, syncData);
|
||||
}
|
||||
|
||||
public SyncPacket(EnumSyncType enumSyncType, EnumSyncAction enumSyncAction, int catId, int revision, NBTTagCompound syncData) {
|
||||
this.enumSyncType = enumSyncType;
|
||||
this.enumSyncAction = enumSyncAction;
|
||||
this.syncData = syncData;
|
||||
this.operationID = catId;
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public SyncPacket(EnumSyncType enumSyncType, SyncController.CachedSyncPayload payload) {
|
||||
this.enumSyncType = enumSyncType;
|
||||
this.enumSyncAction = EnumSyncAction.RELOAD;
|
||||
this.operationID = -1;
|
||||
this.revision = payload.getRevision();
|
||||
this.cachedPayload = payload.getPayload();
|
||||
this.cachedChunks = payload.getChunks();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,20 +75,55 @@ public final class SyncPacket extends LargeAbstractPacket {
|
||||
|
||||
@Override
|
||||
protected byte[] getData() throws IOException {
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
// 1) Write SyncType
|
||||
buffer.writeInt(enumSyncType.ordinal());
|
||||
// 2) Write SyncAction
|
||||
buffer.writeInt(enumSyncAction.ordinal());
|
||||
// 3) Optional Category ID
|
||||
buffer.writeInt(operationID);
|
||||
// 4) Write the NBTTagCompound
|
||||
ByteBufUtils.writeBigNBT(buffer, syncData);
|
||||
if (cachedPayload != null) {
|
||||
return cachedPayload;
|
||||
}
|
||||
|
||||
// Copy the buffer’s readable bytes into a byte[]
|
||||
byte[] bytes = new byte[buffer.readableBytes()];
|
||||
buffer.readBytes(bytes);
|
||||
return bytes;
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
try {
|
||||
buffer.writeInt(enumSyncType.ordinal());
|
||||
buffer.writeInt(enumSyncAction.ordinal());
|
||||
buffer.writeInt(operationID);
|
||||
buffer.writeInt(revision);
|
||||
ByteBufUtils.writeBigNBT(buffer, syncData);
|
||||
|
||||
byte[] bytes = new byte[buffer.readableBytes()];
|
||||
buffer.readBytes(bytes);
|
||||
return bytes;
|
||||
} finally {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FMLProxyPacket> generatePackets() {
|
||||
if (cachedChunks == null) {
|
||||
return super.generatePackets();
|
||||
}
|
||||
|
||||
List<FMLProxyPacket> packets = new ArrayList<>(cachedChunks.length);
|
||||
PacketChannel packetChannel = getChannel();
|
||||
EnumDataPacket dataPacket = (EnumDataPacket) getType();
|
||||
UUID packetId = UUID.randomUUID();
|
||||
|
||||
int totalSize = cachedPayload.length;
|
||||
int offset = 0;
|
||||
|
||||
for (byte[] chunk : cachedChunks) {
|
||||
ByteBuf chunkBuf = Unpooled.buffer();
|
||||
chunkBuf.writeInt(packetChannel.getChannelType().ordinal());
|
||||
chunkBuf.writeInt(dataPacket.ordinal());
|
||||
chunkBuf.writeLong(packetId.getMostSignificantBits());
|
||||
chunkBuf.writeLong(packetId.getLeastSignificantBits());
|
||||
chunkBuf.writeInt(totalSize);
|
||||
chunkBuf.writeInt(offset);
|
||||
chunkBuf.writeInt(cachedChunks.length);
|
||||
chunkBuf.writeBytes(chunk);
|
||||
packets.add(new FMLProxyPacket(chunkBuf, packetChannel.getChannelName()));
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
return packets;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,28 +135,35 @@ public final class SyncPacket extends LargeAbstractPacket {
|
||||
int syncTypeOrdinal = data.readInt();
|
||||
int syncActionOrdinal = data.readInt();
|
||||
int categoryID = data.readInt();
|
||||
int incomingRevision = data.readInt();
|
||||
|
||||
EnumSyncType type = EnumSyncType.values()[syncTypeOrdinal];
|
||||
EnumSyncAction action = EnumSyncAction.values()[syncActionOrdinal];
|
||||
try {
|
||||
NBTTagCompound tag = ByteBufUtils.readBigNBT(data);
|
||||
// Now do your client-side logic (similar to your old clientSync() or clientSyncUpdate() approach)
|
||||
handleSyncPacketClient(type, action, categoryID, tag);
|
||||
handleSyncPacketClient(type, action, categoryID, incomingRevision, tag);
|
||||
} catch (RuntimeException e) {
|
||||
LogWriter.error(String.format("Attempted to Sync %s but it was too big", type.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSyncPacketClient(EnumSyncType enumSyncType, EnumSyncAction enumSyncAction, int id, NBTTagCompound data) {
|
||||
private void handleSyncPacketClient(
|
||||
EnumSyncType enumSyncType,
|
||||
EnumSyncAction enumSyncAction,
|
||||
int id,
|
||||
int incomingRevision,
|
||||
NBTTagCompound data
|
||||
) {
|
||||
switch (enumSyncAction) {
|
||||
case RELOAD:
|
||||
SyncController.clientSync(enumSyncType, data);
|
||||
SyncController.clientSync(enumSyncType, incomingRevision, data);
|
||||
break;
|
||||
case UPDATE:
|
||||
SyncController.clientUpdate(enumSyncType, id, data);
|
||||
SyncController.clientUpdate(enumSyncType, id, incomingRevision, data);
|
||||
break;
|
||||
case REMOVE:
|
||||
SyncController.clientSyncRemove(enumSyncType, id);
|
||||
SyncController.clientSyncRemove(enumSyncType, id, incomingRevision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package kamkeel.npcs.network.packets.player;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.enums.EnumPlayerPacket;
|
||||
import kamkeel.npcs.network.enums.EnumSyncType;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SyncRevisionInfoPacket extends AbstractPacket {
|
||||
|
||||
public static final String packetName = "Player|SyncRevisionInfo";
|
||||
|
||||
private String serverKey = "";
|
||||
private String previousServerKey = "";
|
||||
private final EnumMap<EnumSyncType, Integer> revisions = new EnumMap<>(EnumSyncType.class);
|
||||
|
||||
public SyncRevisionInfoPacket() {
|
||||
}
|
||||
|
||||
public SyncRevisionInfoPacket(String serverKey, String previousServerKey, EnumMap<EnumSyncType, Integer> revisions) {
|
||||
if (serverKey != null) {
|
||||
this.serverKey = serverKey;
|
||||
}
|
||||
if (previousServerKey != null) {
|
||||
this.previousServerKey = previousServerKey;
|
||||
}
|
||||
if (revisions != null) {
|
||||
this.revisions.putAll(revisions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumPlayerPacket.SyncRevisionInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.PLAYER_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
ByteBufUtils.writeString(out, serverKey);
|
||||
ByteBufUtils.writeString(out, previousServerKey);
|
||||
out.writeShort(revisions.size());
|
||||
for (Map.Entry<EnumSyncType, Integer> entry : revisions.entrySet()) {
|
||||
out.writeInt(entry.getKey().ordinal());
|
||||
out.writeInt(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String incomingServerKey = ByteBufUtils.readString(in);
|
||||
String incomingPreviousKey = ByteBufUtils.readString(in);
|
||||
int revisionCount = in.readUnsignedShort();
|
||||
EnumMap<EnumSyncType, Integer> incomingRevisions = new EnumMap<>(EnumSyncType.class);
|
||||
for (int i = 0; i < revisionCount; i++) {
|
||||
EnumSyncType type = EnumSyncType.values()[in.readInt()];
|
||||
int revision = in.readInt();
|
||||
incomingRevisions.put(type, revision);
|
||||
}
|
||||
|
||||
SyncController.handleClientRevisionReport((EntityPlayerMP) player, incomingServerKey, incomingPreviousKey, incomingRevisions);
|
||||
}
|
||||
}
|
||||
@@ -209,6 +209,7 @@ public class ServerEventsHandler {
|
||||
EnumSyncType.WORKBENCH_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
SyncController.getCurrentRevision(EnumSyncType.WORKBENCH_RECIPES),
|
||||
SyncController.workbenchNBT()
|
||||
), (EntityPlayerMP) player);
|
||||
}
|
||||
@@ -217,6 +218,7 @@ public class ServerEventsHandler {
|
||||
EnumSyncType.CARPENTRY_RECIPES,
|
||||
EnumSyncAction.RELOAD,
|
||||
-1,
|
||||
SyncController.getCurrentRevision(EnumSyncType.CARPENTRY_RECIPES),
|
||||
SyncController.carpentryNBT()
|
||||
), (EntityPlayerMP) player);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class ServerTickHandler {
|
||||
}
|
||||
|
||||
ProfileController.Instance.login(player);
|
||||
SyncController.syncPlayer(player);
|
||||
SyncController.beginLogin(player);
|
||||
SyncController.syncEffects(player);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,12 @@ import noppes.npcs.controllers.data.AnimationData;
|
||||
import noppes.npcs.controllers.data.Party;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import noppes.npcs.controllers.data.SkinOverlay;
|
||||
import kamkeel.npcs.network.enums.EnumSyncType;
|
||||
import noppes.npcs.util.CacheHashMap;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ClientCacheHandler {
|
||||
@@ -27,11 +30,70 @@ public class ClientCacheHandler {
|
||||
public static HashMap<UUID, HashMap<Integer, SkinOverlay>> skinOverlays = new HashMap<>();
|
||||
public static HashMap<UUID, AnimationData> playerAnimations = new HashMap<>();
|
||||
|
||||
private static String activeServerKey = "";
|
||||
private static String lastServerKey = "";
|
||||
private static final Map<String, EnumMap<EnumSyncType, Integer>> clientRevisionCache = new HashMap<>();
|
||||
|
||||
public static Party party;
|
||||
|
||||
public static boolean allowProfiles = true;
|
||||
public static boolean allowParties = true;
|
||||
|
||||
public static void setActiveServer(String serverKey, EnumMap<EnumSyncType, Integer> serverRevisions) {
|
||||
String normalizedKey = serverKey == null ? "" : serverKey;
|
||||
String previousKey = activeServerKey == null ? "" : activeServerKey;
|
||||
lastServerKey = previousKey;
|
||||
|
||||
if (!normalizedKey.equals(activeServerKey)) {
|
||||
clientRevisionCache.clear();
|
||||
}
|
||||
|
||||
activeServerKey = normalizedKey;
|
||||
|
||||
if (activeServerKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnumMap<EnumSyncType, Integer> cached = clientRevisionCache.computeIfAbsent(
|
||||
activeServerKey,
|
||||
ignored -> new EnumMap<>(EnumSyncType.class)
|
||||
);
|
||||
if (serverRevisions != null && !serverRevisions.isEmpty()) {
|
||||
cached.keySet().retainAll(serverRevisions.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getLastServerKey() {
|
||||
return lastServerKey == null ? "" : lastServerKey;
|
||||
}
|
||||
|
||||
public static EnumMap<EnumSyncType, Integer> getCachedRevisionsForServer(String serverKey) {
|
||||
String key = serverKey == null ? "" : serverKey;
|
||||
if (key.isEmpty()) {
|
||||
return new EnumMap<>(EnumSyncType.class);
|
||||
}
|
||||
EnumMap<EnumSyncType, Integer> revisions = clientRevisionCache.get(key);
|
||||
if (revisions == null) {
|
||||
return new EnumMap<>(EnumSyncType.class);
|
||||
}
|
||||
return new EnumMap<>(revisions);
|
||||
}
|
||||
|
||||
public static void updateClientRevision(EnumSyncType type, int revision) {
|
||||
if (revision < 0) {
|
||||
return;
|
||||
}
|
||||
String key = activeServerKey == null ? "" : activeServerKey;
|
||||
if (key.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
EnumMap<EnumSyncType, Integer> revisions = clientRevisionCache.computeIfAbsent(
|
||||
key,
|
||||
ignored -> new EnumMap<>(EnumSyncType.class)
|
||||
);
|
||||
revisions.put(type, revision);
|
||||
}
|
||||
|
||||
|
||||
public static ImageData getImageData(String directory) {
|
||||
synchronized (imageDataCache) {
|
||||
|
||||
@@ -41,6 +41,8 @@ public class GuiMagicCycleMap extends GuiDiagram {
|
||||
invalidateCache();
|
||||
return;
|
||||
}
|
||||
// TODO: Consumes MagicController's full magic registry to resolve associations; visible only through magic editing GUIs
|
||||
// that require the NPC wand and CustomNpcsPermissions.GLOBAL_MAGIC (or op status) to open.
|
||||
MagicController mc = MagicController.getInstance();
|
||||
// Get magics from the cycle's associations if associations are not null.
|
||||
if (cycle.associations != null) {
|
||||
@@ -67,6 +69,8 @@ public class GuiMagicCycleMap extends GuiDiagram {
|
||||
* Constructor using a cycle ID.
|
||||
*/
|
||||
public GuiMagicCycleMap(GuiNPCInterface parent, int x, int y, int width, int height, int cycleId) {
|
||||
// TODO: Looks up cycle data from MagicController; accessed from the same wand/CustomNpcsPermissions.GLOBAL_MAGIC gated
|
||||
// management screens.
|
||||
this(parent, x, y, width, height, MagicController.getInstance().getCycle(cycleId));
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,8 @@ public class GuiScript extends GuiNPCInterface implements IGuiData, GuiYesNoCall
|
||||
initGui();
|
||||
}
|
||||
if (guibutton.id == 106) {
|
||||
// TODO: Uses ScriptController.Instance.dir (shared script repository); this GUI is gated behind the scripter tool
|
||||
// and CustomNpcsPermissions.TOOL_SCRIPTER so only authorized editors touch the controller-backed scripts.
|
||||
NoppesUtil.openFolder(ScriptController.Instance.dir);
|
||||
}
|
||||
if (guibutton.id == 107) {
|
||||
|
||||
@@ -19,6 +19,8 @@ public class SubGuiMagicCycleViewer extends SubGuiInterface {
|
||||
}
|
||||
|
||||
public SubGuiMagicCycleViewer(int cycleID) {
|
||||
// TODO: Fetches MagicController cycle definitions (global magic data); only staff with the wand and
|
||||
// CustomNpcsPermissions.GLOBAL_MAGIC reach this viewer via the magic management GUIs.
|
||||
this(MagicController.getInstance().getCycle(cycleID));
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ public class GuiNPCManageEffects extends GuiNPCInterface2 implements ICustomScro
|
||||
effect.readFromNBT(compound);
|
||||
setSelected(effect.name);
|
||||
if (effect.id != -1) {
|
||||
// TODO: Writes back into the CustomEffectController cache (global effect dataset); only players with the wand and
|
||||
// CustomNpcsPermissions.GLOBAL_EFFECT reach this admin editor.
|
||||
CustomEffectController.getInstance().getCustomEffects().replace(effect.id, effect);
|
||||
}
|
||||
initGui();
|
||||
|
||||
@@ -41,6 +41,8 @@ public class GuiAnvilRecipes extends GuiNPCInterface {
|
||||
setBackground("recipesAnvil.png");
|
||||
this.closeOnEsc = true;
|
||||
|
||||
// TODO: Loads the entire anvil recipe registry from RecipeController; any player can open this reference GUI without
|
||||
// special permissions.
|
||||
recipes.addAll(RecipeController.Instance.anvilRecipes.values());
|
||||
}
|
||||
|
||||
@@ -81,9 +83,12 @@ public class GuiAnvilRecipes extends GuiNPCInterface {
|
||||
|
||||
private List<RecipeAnvil> getSearchList() {
|
||||
if (search.isEmpty()) {
|
||||
// TODO: Returns every anvil recipe from RecipeController for browsing; no CustomNpcsPermissions gate applies.
|
||||
return new ArrayList<RecipeAnvil>(RecipeController.Instance.anvilRecipes.values());
|
||||
}
|
||||
List<RecipeAnvil> list = new ArrayList<RecipeAnvil>();
|
||||
// TODO: Filters across the RecipeController anvil collection; the bench/anvil GUIs are normal gameplay with no
|
||||
// permission checks.
|
||||
for (RecipeAnvil recipe : RecipeController.Instance.anvilRecipes.values()) {
|
||||
ItemStack repairItem = recipe.itemToRepair;
|
||||
if (repairItem == null)
|
||||
|
||||
@@ -39,6 +39,8 @@ public class GuiNpcAnvil extends GuiContainerNPCInterface {
|
||||
@Override
|
||||
protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
|
||||
// Enable the button if there are any anvil recipes.
|
||||
// TODO: Checks the shared RecipeController anvil list (entire dataset) before enabling; this player crafting GUI is
|
||||
// public, so no CustomNpcsPermissions gate applies here.
|
||||
button.enabled = RecipeController.Instance != null && !RecipeController.Instance.getAnvilList().isEmpty();
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
mc.renderEngine.bindTexture(resource);
|
||||
|
||||
@@ -36,6 +36,8 @@ public class GuiNpcCarpentryBench extends GuiContainerNPCInterface {
|
||||
|
||||
@Override
|
||||
protected void drawGuiContainerBackgroundLayer(float f, int i, int j) {
|
||||
// TODO: Reads the global RecipeController carpentry list before enabling; this bench is a public crafting block with no
|
||||
// additional permission checks.
|
||||
button.enabled = RecipeController.Instance != null && !RecipeController.Instance.carpentryRecipes.isEmpty();
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
mc.renderEngine.bindTexture(resource);
|
||||
|
||||
@@ -39,6 +39,8 @@ public class GuiRecipes extends GuiNPCInterface {
|
||||
this.xSize = 256;
|
||||
setBackground("recipes.png");
|
||||
this.closeOnEsc = true;
|
||||
// TODO: Preloads every carpentry recipe from RecipeController (full dataset); this book is open to any player, so no
|
||||
// explicit permission is checked when accessing it.
|
||||
recipes.addAll(RecipeController.Instance.carpentryRecipes.values());
|
||||
}
|
||||
|
||||
@@ -74,9 +76,13 @@ public class GuiRecipes extends GuiNPCInterface {
|
||||
|
||||
private List<IRecipe> getSearchList() {
|
||||
if (search.isEmpty()) {
|
||||
// TODO: Returns the entire carpentry recipe collection from RecipeController; again this is unrestricted player
|
||||
// content with no CustomNpcsPermissions gating.
|
||||
return new ArrayList<IRecipe>(RecipeController.Instance.carpentryRecipes.values());
|
||||
}
|
||||
List<IRecipe> list = new ArrayList<IRecipe>();
|
||||
// TODO: Iterates over the RecipeController carpentry map to filter recipes; this viewing GUI is available to all
|
||||
// players without special permissions.
|
||||
for (IRecipe recipe : RecipeController.Instance.carpentryRecipes.values()) {
|
||||
if (recipe.getRecipeOutput() == null)
|
||||
continue;
|
||||
|
||||
@@ -94,6 +94,8 @@ public class GuiCNPCInventory extends GuiNPCInterface {
|
||||
PlayerData data = PlayerData.get(mc.thePlayer);
|
||||
if (data != null && data.effectData != null) {
|
||||
for (PlayerEffect pe : data.effectData.getEffects().values()) {
|
||||
// TODO: Pulls specific CustomEffect entries from the global controller; this HUD is part of the base
|
||||
// player inventory so it must work without extra permissions (no CustomNpcsPermissions required).
|
||||
CustomEffect effect = CustomEffectController.getInstance().get(pe.id, pe.index);
|
||||
if (effect != null) {
|
||||
effectBar.entries.add(new GuiEffectBar.EffectEntry(effect, pe));
|
||||
|
||||
@@ -255,6 +255,8 @@ public class GuiModernQuestDialog extends GuiNPCInterface implements IGuiClose {
|
||||
}
|
||||
int fac1ID = quest.factionOptions.factionId;
|
||||
if (fac1ID != -1) {
|
||||
// TODO: Looks up faction display data from the global FactionController; this dialog is part of the standard
|
||||
// player quest flow so no special permissions are needed beyond quest access.
|
||||
String fac1Name = FactionController.getInstance().getFaction(fac1ID).getName();
|
||||
String fac1Color = (quest.factionOptions.decreaseFactionPoints) ? "§c-" : "§a+";
|
||||
int fac1Point = quest.factionOptions.factionPoints;
|
||||
@@ -263,6 +265,8 @@ public class GuiModernQuestDialog extends GuiNPCInterface implements IGuiClose {
|
||||
}
|
||||
int fac2ID = quest.factionOptions.faction2Id;
|
||||
if (fac2ID != -1) {
|
||||
// TODO: Retrieves the second faction's metadata from FactionController as above; normal quest participants reach
|
||||
// this without CustomNpcsPermissions requirements.
|
||||
String fac2Name = FactionController.getInstance().getFaction(fac2ID).getName();
|
||||
String fac2Color = (quest.factionOptions.decreaseFaction2Points) ? "§c-" : "§a+";
|
||||
int fac2Point = quest.factionOptions.faction2Points;
|
||||
|
||||
@@ -79,7 +79,9 @@ public class GuiNpcQuestTypeItem extends GuiContainerNPCInterface implements ITe
|
||||
// map.put(i, item.copy());
|
||||
// }
|
||||
// ((QuestItem)quest.questInterface).items = map;
|
||||
// QuestController.saveQuest(quest);
|
||||
// TODO: Saving would route through the global QuestController, which should stay limited to wand users with
|
||||
// CustomNpcsPermissions.GLOBAL_QUEST or operators when OpsOnly is active.
|
||||
// QuestController.saveQuest(quest);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -307,6 +307,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
|
||||
}
|
||||
|
||||
if (guibutton.id == 106) {
|
||||
// TODO: Opens the ScriptController shared directory; this screen is available only to editors with the scripter
|
||||
// tool and CustomNpcsPermissions.TOOL_SCRIPTER.
|
||||
NoppesUtil.openFolder(ScriptController.Instance.dir);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ public class GuiDialogSelection extends SubGuiInterface implements ICustomScroll
|
||||
setBackground("menubg.png");
|
||||
xSize = 366;
|
||||
ySize = 226;
|
||||
// TODO: Pulls the entire DialogController dialog map (global data); players only reach this via wand-required dialog
|
||||
// editors that enforce PacketUtil.verifyItemPacket and CustomNpcsPermissions.GLOBAL_DIALOG/ops when editing.
|
||||
this.selectedDialog = DialogController.Instance.dialogs.get(dialog);
|
||||
if (selectedDialog != null) {
|
||||
selectedCategory = selectedDialog.category;
|
||||
@@ -59,6 +61,8 @@ public class GuiDialogSelection extends SubGuiInterface implements ICustomScroll
|
||||
HashMap<String, DialogCategory> categoryData = new HashMap<String, DialogCategory>();
|
||||
HashMap<String, Dialog> dialogData = new HashMap<String, Dialog>();
|
||||
|
||||
// TODO: Iterates over every dialog category provided by DialogController; reachable through NPC wand workflows that
|
||||
// require dialog-edit permissions (CustomNpcsPermissions.GLOBAL_DIALOG or op status under OpsOnly).
|
||||
for (DialogCategory category : DialogController.Instance.categories.values()) {
|
||||
categoryData.put(category.title, category);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public class GuiQuestSelection extends SubGuiInterface implements ICustomScrollL
|
||||
setBackground("menubg.png");
|
||||
xSize = 366;
|
||||
ySize = 226;
|
||||
// TODO: Requires the full QuestController quest map (global dataset); access comes from wand-driven quest editors
|
||||
// that rely on PacketUtil.verifyItemPacket and ultimately CustomNpcsPermissions.GLOBAL_QUEST / server op checks.
|
||||
this.selectedQuest = QuestController.Instance.quests.get(quest);
|
||||
if (selectedQuest != null) {
|
||||
selectedCategory = selectedQuest.category;
|
||||
@@ -59,6 +61,8 @@ public class GuiQuestSelection extends SubGuiInterface implements ICustomScrollL
|
||||
HashMap<String, QuestCategory> categoryData = new HashMap<String, QuestCategory>();
|
||||
HashMap<String, Quest> questData = new HashMap<String, Quest>();
|
||||
|
||||
// TODO: Needs every quest category from QuestController so the selector can populate; opened from NPC wand flows that
|
||||
// demand the editing player satisfy CustomNpcsPermissions.GLOBAL_QUEST (or be OP when OpsOnly is enabled).
|
||||
for (QuestCategory category : QuestController.Instance.categories.values()) {
|
||||
categoryData.put(category.title, category);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user