
Dynamic Cover help
xKostmand opened this issue · 5 comments
Cross-mod Integration
No response
Minecraft Version
1.20.1 Forge
Feature Description
hello, i would like some help with coding.
i am trying to build a mod that adds a dynamic cover for the gt machines
basically, the dynamic cover is a container that can store multiple other covers inside it and all of them work concurrently.
i am just building the mod for fun and for training, my inspiration was this mod for 1.12 Nomifactory: https://github.com/Zorbatron/ZBGT adding dual covers in the game and i thought, it could be extended
here is my problem:
I can put multiple covers inside the Dynamic Cover GUI but they dont work concurrently, only the first one i put inside works and the others remain idle. The core problem is that the GregTech CEu API for CoverBehavior does not seem to directly support the kind of concurrent ticking, NBT handling, and redstone interaction that would be required for the dynamic cover to function as intended. The ICoverable interface delegates some lifecycle methods, but not the crucial ones for continuous operation.
Any help is much loved, if there is a better place to ask this, please tell me. Also, i can share my code if it helps. I know this is a feature request but i thought there is no better place but to ask the GregTech devs
check out the gregtech discord https://discord.gg/ZV5r5jcz,
we mostly talk in the #modern-dev-talks channel
btw i tried to already do that but got stuck on trying to make the syncdata system work with it, if you want you can check out my code on the tar/multicover branch
thanks for replying. i got stuck in the following. i could either get the inner cover gui to load or they would work concurrently. for some reason when i fixed one of them the other would break unfortunately. I just wanted dual covers on 1.20 and i thought maybe i could extend it but ive lost hope.
now i am trying to just simply implement the dual covers.
btw, if you have any easy way to create a multiblock let me know because i cant figure it out, tyvm!
there isn't really an easy way to make multis (if you need examples though you can look at the code for any existing multiblock in GTCEuM 1.20.1 branch)
there's a multiblock rework planned for the GTCEu 8.0, though it's only partially completed
As for the covers, could you please share your code?
Here is my cover logic, this is with concurrency working. i also have another version with gui working
any help is much appreciated
package com.jmoiron.ulvcovm.data.covers;
import com.gregtechceu.gtceu.api.capability.ICoverable;
import com.gregtechceu.gtceu.api.cover.CoverBehavior;
import com.gregtechceu.gtceu.api.cover.CoverDefinition;
import com.gregtechceu.gtceu.api.cover.IUICover;
import com.gregtechceu.gtceu.api.gui.GuiTextures;
import com.gregtechceu.gtceu.api.gui.widget.SlotWidget;
import com.gregtechceu.gtceu.api.machine.MachineCoverContainer;
import com.gregtechceu.gtceu.api.machine.MetaMachine;
import com.gregtechceu.gtceu.api.machine.TickableSubscription;
import com.gregtechceu.gtceu.api.registry.GTRegistries;
import com.gregtechceu.gtceu.api.transfer.item.CustomItemStackHandler;
import com.gregtechceu.gtceu.common.cover.ItemFilterCover;
import com.gregtechceu.gtceu.common.cover.PumpCover;
import com.gregtechceu.gtceu.common.cover.RobotArmCover;
import com.lowdragmc.lowdraglib.gui.texture.ColorBorderTexture;
import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup;
import com.lowdragmc.lowdraglib.gui.widget.LabelWidget;
import com.lowdragmc.lowdraglib.gui.widget.Widget;
import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup;
import com.lowdragmc.lowdraglib.syncdata.annotation.DescSynced;
import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted;
import com.lowdragmc.lowdraglib.syncdata.annotation.RequireRerender;
import com.lowdragmc.lowdraglib.syncdata.field.ManagedFieldHolder;
import com.lowdragmc.lowdraglib.utils.LocalizationUtils;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.core.BlockPos;
import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
-
Dynamic Cover - Fixed Version with Proper Subscription Management
*/
public class DynamicCoverBehavior extends CoverBehavior implements IUICover {public static final ManagedFieldHolder MANAGED_FIELD_HOLDER =
new ManagedFieldHolder(DynamicCoverBehavior.class, CoverBehavior.MANAGED_FIELD_HOLDER);private static final int SLOT_COUNT = 9;
@persisted
@DescSynced
public final CustomItemStackHandler coverSlots;@persisted
@DescSynced
@RequireRerender
private int selectedIndex = -1;// Thread-safe collections for concurrent access
private final Map<Integer, CoverBehavior> internalCovers = new ConcurrentHashMap<>();
private final Map<Integer, TickableSubscription> tickSubscriptions = new ConcurrentHashMap<>();
private final List slotWidgets = new ArrayList<>();
private WidgetGroup innerCoverUIArea;// Lifecycle management
private volatile boolean isInitialized = false;
private volatile boolean isDisposed = false;// Prevent double ticking with a proper gate
private final Map<Integer, Long> lastTickTime = new ConcurrentHashMap<>();
private final Map<Integer, Boolean> isCurrentlyTicking = new ConcurrentHashMap<>();public DynamicCoverBehavior(@NotNull CoverDefinition definition,
@NotNull ICoverable coverableView,
@NotNull Direction attachedSide,
int tier) {
super(definition, coverableView, attachedSide);this.coverSlots = new CustomItemStackHandler(SLOT_COUNT) { @Override public int getSlotLimit(int slot) { return 1; } @Override public boolean isItemValid(int slot, @NotNull ItemStack stack) { return findDefinitionFor(stack) != null; } @Override public void onContentsChanged(int slot) { super.onContentsChanged(slot); if (isInitialized && !isDisposed) { handleSlotChange(slot); } } };
}
public DynamicCoverBehavior(@NotNull CoverDefinition definition,
@NotNull ICoverable coverableView,
@NotNull Direction attachedSide) {
this(definition, coverableView, attachedSide, 0);
}@OverRide
public @NotNull ManagedFieldHolder getFieldHolder() {
return MANAGED_FIELD_HOLDER;
}@OverRide
public boolean canAttach() {
return coverHolder instanceof MachineCoverContainer && super.canAttach();
}/* =======================================
FIXED: Proper Subscription Management
======================================= */private void subscribeCoverTicking(int slot, CoverBehavior cover) {
if (isDisposed) return;// Prevent double subscription with proper synchronization synchronized (this) { if (tickSubscriptions.containsKey(slot)) { System.out.println("DEBUG: Slot " + slot + " already has subscription, skipping"); return; } System.out.println("DEBUG: Subscribing cover in slot " + slot + " of type " + cover.getClass().getSimpleName()); MetaMachine machine = getMachine(); if (machine == null) { System.out.println("DEBUG: No machine found for subscription"); return; } try { // Create subscription with proper synchronization TickableSubscription subscription = machine.subscribeServerTick(() -> { if (isDisposed || !internalCovers.containsKey(slot)) return; // Prevent concurrent ticking of the same cover if (isCurrentlyTicking.putIfAbsent(slot, true) != null) { return; // Already ticking } try { long currentTime = System.currentTimeMillis(); Long lastTick = lastTickTime.get(slot); // Ensure minimum 50ms between ticks (20 TPS max) if (lastTick != null && (currentTime - lastTick) < 50) { return; } tickInternalCover(cover); lastTickTime.put(slot, currentTime); } catch (Throwable t) { System.err.println("Error ticking internal cover in slot " + slot + ": " + t.getMessage()); } finally { isCurrentlyTicking.remove(slot); } }); if (subscription != null) { tickSubscriptions.put(slot, subscription); System.out.println("DEBUG: Successfully subscribed cover in slot " + slot); } else { System.out.println("DEBUG: Failed to create subscription for slot " + slot); } } catch (Exception e) { System.err.println("DEBUG: Exception during subscription for slot " + slot + ": " + e.getMessage()); e.printStackTrace(); } }
}
private void unsubscribeCoverTicking(int slot) {
synchronized (this) {
TickableSubscription subscription = tickSubscriptions.remove(slot);
if (subscription != null) {
MetaMachine machine = getMachine();
if (machine != null) {
try {
machine.unsubscribe(subscription);
System.out.println("DEBUG: Unsubscribed cover in slot " + slot);
} catch (Exception e) {
System.err.println("DEBUG: Error unsubscribing slot " + slot + ": " + e.getMessage());
}
}
}// Clean up timing data lastTickTime.remove(slot); isCurrentlyTicking.remove(slot); }
}
@nullable
private MetaMachine getMachine() {
if (coverHolder instanceof MachineCoverContainer machineContainer) {
return machineContainer.getMachine();
}
return null;
}private void tickInternalCover(CoverBehavior cover) {
try {
// Use reflection to find and call the appropriate update method
Method updateMethod = findUpdateMethod(cover.getClass());
if (updateMethod != null) {
updateMethod.setAccessible(true);
updateMethod.invoke(cover);
}
} catch (Exception e) {
// Silent - this is normal for many tick cycles where covers don't need to do anything
}
}private Method findUpdateMethod(Class<?> clazz) {
// Try common update method names
String[] methodNames = {"update", "serverTick", "tick", "onUpdate"};for (String methodName : methodNames) { try { Method method = clazz.getDeclaredMethod(methodName); if (method.getParameterCount() == 0) { return method; } } catch (NoSuchMethodException ignored) { } try { Method method = clazz.getMethod(methodName); if (method.getParameterCount() == 0) { return method; } } catch (NoSuchMethodException ignored) { } } // Try parent class Class<?> superClass = clazz.getSuperclass(); if (superClass != null && superClass != Object.class && superClass != CoverBehavior.class) { return findUpdateMethod(superClass); } return null;
}
/* ============================
Lifecycle Management
============================ */@OverRide
public void onLoad() {
super.onLoad();
isDisposed = false;
isInitialized = false;// Clear any existing state synchronized (this) { internalCovers.clear(); for (int slot : new ArrayList<>(tickSubscriptions.keySet())) { unsubscribeCoverTicking(slot); } } rebuildInternalCoversFromSlots(); validateSelection(); isInitialized = true; System.out.println("DEBUG: Dynamic cover loaded with " + internalCovers.size() + " internal covers");
}
@OverRide
public void onUnload() {
super.onUnload();
isInitialized = false;
isDisposed = true;synchronized (this) { // Unsubscribe all ticking for (int slot : new ArrayList<>(tickSubscriptions.keySet())) { unsubscribeCoverTicking(slot); } // Unload covers for (CoverBehavior cover : internalCovers.values()) { try { cover.onUnload(); } catch (Throwable t) { System.err.println("Error unloading internal cover: " + t.getMessage()); } } // Clear state internalCovers.clear(); }
}
@OverRide
public void onNeighborChanged(Block block, BlockPos fromPos, boolean isMoving) {
super.onNeighborChanged(block, fromPos, isMoving);
for (CoverBehavior cover : internalCovers.values()) {
try {
cover.onNeighborChanged(block, fromPos, isMoving);
} catch (Throwable ignored) {}
}
}@OverRide
public void onRemoved() {
super.onRemoved();
isInitialized = false;
isDisposed = true;synchronized (this) { // Clean up tick subscriptions first for (int slot : new ArrayList<>(tickSubscriptions.keySet())) { unsubscribeCoverTicking(slot); } // Clean up child covers for (CoverBehavior cover : internalCovers.values()) { try { cover.onRemoved(); } catch (Throwable t) { System.err.println("Error removing internal cover: " + t.getMessage()); } } internalCovers.clear(); }
}
@OverRide
@NotNull
public List getAdditionalDrops() {
var list = super.getAdditionalDrops();
for (int i = 0; i < SLOT_COUNT; i++) {
ItemStack stack = coverSlots.getStackInSlot(i);
if (!stack.isEmpty()) list.add(stack.copy());
}
return list;
}/* =========================
Slot & Behavior Management
========================= */private void handleSlotChange(int slot) {
if (!isInitialized || isDisposed) return;ItemStack stack = coverSlots.getStackInSlot(slot); if (stack.isEmpty()) { removeInternalCover(slot); } else { createInternalCover(slot, stack); } validateSelection(); updateInnerCoverInfo(); // Changed from updateInnerCoverUI // Proper synchronization coverHolder.markDirty(); coverHolder.notifyBlockUpdate(); coverHolder.scheduleRenderUpdate();
}
private void rebuildInternalCoversFromSlots() {
System.out.println("DEBUG: Rebuilding internal covers from slots");for (int i = 0; i < SLOT_COUNT; i++) { ItemStack stack = coverSlots.getStackInSlot(i); if (!stack.isEmpty()) { createInternalCover(i, stack); } } System.out.println("DEBUG: Rebuilt " + internalCovers.size() + " internal covers");
}
private void createInternalCover(int slot, ItemStack stack) {
if (isDisposed) return;// Remove any existing cover first removeInternalCover(slot); CoverDefinition def = findDefinitionFor(stack); if (def == null) { System.out.println("DEBUG: No cover definition found for " + stack.getItem()); return; } try { System.out.println("DEBUG: Creating internal cover for slot " + slot + " with definition " + def.getId()); // Create the behavior CoverBehavior behavior = def.createCoverBehavior(coverHolder, attachedSide); // Properly initialize the cover behavior.onLoad(); // Store the cover internalCovers.put(slot, behavior); // Subscribe to ticking subscribeCoverTicking(slot, behavior); // Update selection if needed if (selectedIndex == -1) { selectedIndex = slot; // Force sync of selection coverHolder.markDirty(); } System.out.println("DEBUG: Successfully created internal cover of type " + behavior.getClass().getSimpleName() + " in slot " + slot); } catch (Throwable t) { System.err.println("Error creating internal cover for slot " + slot + ": " + t.getMessage()); t.printStackTrace(); // Clean up on failure internalCovers.remove(slot); unsubscribeCoverTicking(slot); }
}
private void removeInternalCover(int slot) {
if (isDisposed) return;// Unsubscribe first unsubscribeCoverTicking(slot); CoverBehavior existing = internalCovers.remove(slot); if (existing != null) { try { existing.onRemoved(); System.out.println("DEBUG: Removed internal cover from slot " + slot); } catch (Throwable t) { System.err.println("Error removing internal cover from slot " + slot + ": " + t.getMessage()); } } // Update selection if the removed cover was selected if (selectedIndex == slot) { selectedIndex = findNextOccupiedSlot(); // Force sync of selection change coverHolder.markDirty(); }
}
private void validateSelection() {
int oldSelection = selectedIndex;if (selectedIndex >= 0 && !internalCovers.containsKey(selectedIndex)) { selectedIndex = findNextOccupiedSlot(); } // Force sync if selection changed if (oldSelection != selectedIndex) { coverHolder.markDirty(); coverHolder.scheduleRenderUpdate(); }
}
private int findNextOccupiedSlot() {
if (internalCovers.isEmpty()) return -1;
return internalCovers.keySet().stream().min(Integer::compareTo).orElse(-1);
}@nullable
private CoverDefinition findDefinitionFor(ItemStack stack) {
if (stack.isEmpty()) return null;
var item = stack.getItem();// Check if it's a ComponentItem with CoverPlaceBehavior if (item instanceof com.gregtechceu.gtceu.api.item.ComponentItem) { var components = ((com.gregtechceu.gtceu.api.item.ComponentItem) item).getComponents(); for (var component : components) { if (component instanceof com.gregtechceu.gtceu.common.item.CoverPlaceBehavior) { return ((com.gregtechceu.gtceu.common.item.CoverPlaceBehavior) component).coverDefinition(); } } } // Try registry lookup var itemId = ForgeRegistries.ITEMS.getKey(item); if (itemId != null) { var def = GTRegistries.COVERS.get(itemId); if (def != null) { return def; } // Fallback: search all cover definitions for (CoverDefinition coverDef : GTRegistries.COVERS.values()) { var coverItem = ForgeRegistries.ITEMS.getValue(coverDef.getId()); if (coverItem == item) { return coverDef; } } } return null;
}
/* ======================
FIXED UI Implementation
====================== */@OverRide
public Widget createUIWidget() {
// CRITICAL: Create a completely isolated UI that doesn't interact with container slots
WidgetGroup root = new WidgetGroup(0, 0, 176, 200);// Title root.addWidget(new LabelWidget(8, 5, LocalizationUtils.format("cover.dynamic.title"))); // Create slot row - these are the ONLY container slots we should have WidgetGroup slotRow = new WidgetGroup(8, 20, 160, 20); slotWidgets.clear(); for (int i = 0; i < SLOT_COUNT; i++) { final int slotIndex = i; int x = (i % 9) * 18; SlotWidget slot = new SlotWidget(coverSlots, i, x, 0) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (isMouseOverElement(mouseX, mouseY) && button == 1) { // Right click if (!coverSlots.getStackInSlot(slotIndex).isEmpty() && internalCovers.containsKey(slotIndex)) { // Update selection selectedIndex = slotIndex; // Force UI update and synchronization updateSlotHighlights(); updateInnerCoverInfo(); // Changed from updateInnerCoverUI // Critical: ensure proper synchronization coverHolder.markDirty(); coverHolder.notifyBlockUpdate(); coverHolder.scheduleRenderUpdate(); System.out.println("DEBUG: Selected slot " + slotIndex); return true; } } return super.mouseClicked(mouseX, mouseY, button); } }; // Set change listener for slot updates slot.setChangeListener(() -> { updateSlotHighlights(); updateInnerCoverInfo(); // Changed from updateInnerCoverUI updateTooltips(); }); slotWidgets.add(slot); slotRow.addWidget(slot); } root.addWidget(slotRow); // Instructions root.addWidget(new LabelWidget(8, 44, Component.literal("Right-click to select cover").getString())); // CRITICAL: Instead of trying to embed full cover UIs, just show info and basic controls innerCoverUIArea = new WidgetGroup(0, 58, 176, 142); root.addWidget(innerCoverUIArea); // Initialize UI state - but NEVER try to create inner cover UIs that have container slots updateSlotHighlights(); updateInnerCoverInfo(); // Changed from updateInnerCoverUI updateTooltips(); return root;
}
/**
-
CRITICAL: Show cover info without creating problematic nested UIs
-
This prevents container slot conflicts entirely by not creating inner cover UIs
*/
private void updateInnerCoverInfo() {
if (innerCoverUIArea == null) return;innerCoverUIArea.clearAllWidgets();
if (selectedIndex >= 0 && internalCovers.containsKey(selectedIndex)) {
CoverBehavior selected = internalCovers.get(selectedIndex);
System.out.println("DEBUG: Showing info for selected cover: " + selected.getClass().getSimpleName());// Create informational display instead of trying to embed the actual UI Widget infoWidget = createCoverInfoDisplay(selected); innerCoverUIArea.addWidget(infoWidget);
} else if (internalCovers.isEmpty()) {
innerCoverUIArea.addWidget(new LabelWidget(8, 0, "No covers installed. Place cover items in slots above."));
} else {
innerCoverUIArea.addWidget(new LabelWidget(8, 0, "Right-click a cover slot to select it"));
}
}
private void updateSlotHighlights() {
for (int i = 0; i < slotWidgets.size(); i++) {
SlotWidget slot = slotWidgets.get(i);
if (selectedIndex == i && !coverSlots.getStackInSlot(i).isEmpty()) {
// Highlight selected slot
slot.setBackgroundTexture(new GuiTextureGroup(
GuiTextures.SLOT,
new ColorBorderTexture(2, 0xFF00FF00) // Green border
));
} else {
// Normal slot
slot.setBackgroundTexture(GuiTextures.SLOT);
}
}
}private void updateTooltips() {
for (int i = 0; i < SLOT_COUNT && i < slotWidgets.size(); i++) {
slotWidgets.get(i).setHoverTooltips(createTooltipForSlot(i));
}
}private List createTooltipForSlot(int index) {
List tooltip = new ArrayList<>();
ItemStack stack = coverSlots.getStackInSlot(index);if (stack.isEmpty()) { tooltip.add(Component.literal("Empty Cover Slot " + (index + 1))); tooltip.add(Component.literal("Place a cover item here")); } else { if (selectedIndex == index) { tooltip.add(Component.literal("§a[SELECTED] " + stack.getDisplayName().getString())); } else { tooltip.add(Component.literal(stack.getDisplayName().getString())); } if (internalCovers.containsKey(index)) { if (tickSubscriptions.containsKey(index)) { tooltip.add(Component.literal("§2Status: Active & Ticking")); } else { tooltip.add(Component.literal("§6Status: Loaded but not ticking")); } } else { tooltip.add(Component.literal("§cStatus: Failed to load")); } tooltip.add(Component.literal("§7Right-click to select")); } return tooltip;
}
/* ==========
Utilities
========== */public boolean addCover(CoverDefinition def, int slotIndex) {
if (slotIndex < 0 || slotIndex >= SLOT_COUNT) return false;
if (!coverSlots.getStackInSlot(slotIndex).isEmpty()) return false;var item = ForgeRegistries.ITEMS.getValue(def.getId()); if (item == null) return false; ItemStack stack = new ItemStack(item); if (stack.isEmpty()) return false; coverSlots.setStackInSlot(slotIndex, stack.copy()); return true;
}
public int getSelectedIndex() {
return selectedIndex;
}@nullable
public CoverBehavior getSelectedCover() {
return internalCovers.get(selectedIndex);
}public Map<Integer, CoverBehavior> getInternalCovers() {
return Collections.unmodifiableMap(internalCovers);
}/**
-
Creates a safe informational display about a cover without creating container slots
-
This completely avoids the IndexOutOfBoundsException by not embedding actual cover UIs
*/
private Widget createCoverInfoDisplay(CoverBehavior cover) {
WidgetGroup group = new WidgetGroup(0, 0, 176, 120);
int yOffset = 0;// Cover type and status
String coverType = cover.getClass().getSimpleName().replace("Cover", "");
group.addWidget(new LabelWidget(8, yOffset, "§6" + coverType + " Cover"));
yOffset += 15;// Status indicator
if (tickSubscriptions.containsKey(selectedIndex)) {
group.addWidget(new LabelWidget(8, yOffset, "§aStatus: Active & Running"));
} else {
group.addWidget(new LabelWidget(8, yOffset, "§eStatus: Loaded but not ticking"));
}
yOffset += 15;// Cover-specific information
try {
if (cover instanceof RobotArmCover) {
group.addWidget(new LabelWidget(8, yOffset, "§7Type: Item Transfer System"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§7Function: Automated item import/export"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§bNote: This cover is working concurrently"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cConfiguration: Use cover directly on machine"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cfor full settings (import/export modes, etc.)"));
} else if (cover instanceof PumpCover) {
group.addWidget(new LabelWidget(8, yOffset, "§7Type: Fluid Transfer System"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§7Function: Automated fluid import/export"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§bNote: This cover is working concurrently"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cConfiguration: Use cover directly on machine"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cfor full settings (pump speed, modes, etc.)"));
} else if (cover.getClass().getSimpleName().contains("Controller")) {
group.addWidget(new LabelWidget(8, yOffset, "§7Type: Machine Control System"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§7Function: Redstone-based machine control"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§bNote: This cover is working concurrently"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cConfiguration: Use cover directly on machine"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cfor redstone settings and control modes"));
} else if (cover instanceof ItemFilterCover) {
group.addWidget(new LabelWidget(8, yOffset, "§7Type: Item Filter System"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§7Function: Selective item filtering"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§bNote: This cover is working concurrently"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cConfiguration: Use cover directly on machine"));
} else {
group.addWidget(new LabelWidget(8, yOffset, "§7Type: " + coverType + " System"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§bNote: This cover is working concurrently"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§cConfiguration: Limited in dynamic covers"));
}
} catch (Throwable t) {
group.addWidget(new LabelWidget(8, yOffset, "§7Cover is active and functioning"));
yOffset += 12;
}yOffset += 10;
group.addWidget(new LabelWidget(8, yOffset, "§8" + "─".repeat(18)));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§eWhy limited configuration?"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§7Dynamic covers can't embed full UIs"));
yOffset += 12;
group.addWidget(new LabelWidget(8, yOffset, "§7due to container slot conflicts."));// Adjust group size
group.setSize(176, yOffset + 20);
return group;
}
// Add method to force UI refresh (useful for debugging)
public void forceUIRefresh() {
if (isInitialized) {
updateSlotHighlights();
updateInnerCoverInfo(); // Changed from updateInnerCoverUI
updateTooltips();
coverHolder.scheduleRenderUpdate();
}
}
} -