decouple channels from master

This commit is contained in:
ABKQPO committed 2026-10-24 10:11:26 -02:00
1 parent 04f776e27a
commit cb39702a90
12 files changed
+155 -27

No files matched your search

@@ -9,16 +9,22 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.item.ItemStack;
import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import cpw.mods.fml.common.Loader;
// this is not an API! go check StructureLibAPI instead!
public class ChannelDescription {
// pseudo channel key for editing master channel override from GUI and command. '#' is never allowed in a real
// channel name, so this can never collide with a subchannel
public static final String MASTER_KEY = "#master";
private static final ItemStackMap<Set<ChannelDescription>> itemToChannels = new ItemStackMap<>(true);
private static final Map<String, ChannelDescription> registry = new HashMap<>();
private final String channel;
@@ -64,6 +70,34 @@ public class ChannelDescription {
itemToChannels.computeIfAbsent(stack, s -> new HashSet<>()).add(inst);
}
public static boolean isSet(ItemStack trigger, String key) {
return MASTER_KEY.equals(key) ? ChannelDataAccessor.hasMasterData(trigger)
: ChannelDataAccessor.hasSubChannel(trigger, key);
}
public static void setData(ItemStack trigger, String key, int value) {
if (MASTER_KEY.equals(key)) ChannelDataAccessor.setMasterData(trigger, value);
else ChannelDataAccessor.setChannelData(trigger, key, value);
}
public static void unsetData(ItemStack trigger, String key) {
if (MASTER_KEY.equals(key)) ChannelDataAccessor.unsetMasterData(trigger);
else ChannelDataAccessor.unsetChannelData(trigger, key);
}
public static Stream<Map.Entry<String, Integer>> iterateData(ItemStack trigger) {
Stream<Map.Entry<String, Integer>> channels = ChannelDataAccessor.iterateChannelData(trigger).sorted();
if (!ChannelDataAccessor.hasMasterData(trigger)) return channels;
return Stream.concat(
Stream.of(
new AbstractMap.SimpleImmutableEntry<>(MASTER_KEY, ChannelDataAccessor.getMasterData(trigger))),
channels);
}
public static int countData(ItemStack trigger) {
return ChannelDataAccessor.countChannelData(trigger) + (ChannelDataAccessor.hasMasterData(trigger) ? 1 : 0);
}
public static Map<String, ChannelDescription> getAll() {
return Collections.unmodifiableMap(registry);
}
@@ -70,7 +70,7 @@ public final class ChannelPickHandler {
// Assuming there's only 1 channel per item for now (same behavior as drag and drop)
Map.Entry<String, Integer> first = channels.iterator().next();
ChannelDataAccessor.setChannelData(holoStack, first.getKey(), first.getValue());
ChannelDescription.setData(holoStack, first.getKey(), first.getValue());
sendChannelData(player, holoStack, onCursor);
player.addChatMessage(
new ChatComponentTranslation("structurelib.pickchannel.success", first.getKey(), first.getValue()));
@@ -125,7 +125,7 @@ public class StructureLib {
InventoryUtility.init();
ChannelDescription.set(CHANNEL_SHOW_ERROR, MOD_ID, "channels.structurelib.show_errors");
ChannelDescription.set(StructureLibAPI.CHANNEL_TIER, MOD_ID, "channels.structurelib.tier");
ChannelDescription.set(ChannelDescription.MASTER_KEY, MOD_ID, "channels.structurelib.master");
DevelopHelper.onPreInit();
}
@@ -44,7 +44,6 @@ public class StructureLibAPI {
public static final int HINT_BLOCK_META_AIR = 13;
public static final int HINT_BLOCK_META_NOT_AIR = 14;
public static final int HINT_BLOCK_META_ERROR = 15;
public static final String CHANNEL_TIER = "tier";
public static final String CHANNEL_SHOW_ERROR = "show_error";
static final ThreadLocal<Object> instrument = new ThreadLocal<>();
@@ -25,9 +25,89 @@ import com.gtnewhorizon.structurelib.StructureLibAPI;
public class ChannelDataAccessor {
private static final String SECONDARY_HINT_TAG = "channels";
private static final String MASTER_OVERRIDE_TAG = "master";
private ChannelDataAccessor() {}
/**
* Return a trigger item that
* <ul>
* <li>Still carry all the subchannel in some unspecified ways</li>
* <li>Have its master channel data replaced with master channel override</li>
* <li>No longer carry the master channel override</li>
* </ul>
*
* If given trigger item has no master channel override, masterStack may or may not be returned to save performance.
* Code within StructureLib can depend on this behavior, you cannot.
* <p>
* Should be called once on the raw trigger item before any {@link #withChannel(ItemStack, String)} call.
* StructureLib already does this for hologram projector. If you call
* {@link IConstructable#construct(ItemStack, boolean)} or similar methods with a raw trigger item yourself, call
* this first.
*
* @param masterStack raw trigger item
* @return a trigger item with new master channel
*/
public static ItemStack withMasterData(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException();
if (!hasMasterData(masterStack)) return masterStack;
NBTTagCompound tag = (NBTTagCompound) masterStack.stackTagCompound.copy();
ItemStack ret = new ItemStack(
masterStack.getItem(),
tag.getInteger(MASTER_OVERRIDE_TAG),
Items.feather.getDamage(masterStack));
tag.removeTag(MASTER_OVERRIDE_TAG);
if (!tag.hasNoTags()) ret.setTagCompound(tag);
return ret;
}
/**
* Check if given trigger item contains master channel override
*
* @param masterStack trigger stack to check
* @return true if contains master channel override
*/
public static boolean hasMasterData(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException();
return masterStack.hasTagCompound() && masterStack.stackTagCompound.hasKey(MASTER_OVERRIDE_TAG, NBT.TAG_INT);
}
/**
* Get the master channel data from given trigger item. Will use stack size instead if not overridden.
*
* @param masterStack trigger stack to query from
* @return master channel data
*/
public static int getMasterData(ItemStack masterStack) {
if (!hasMasterData(masterStack)) return masterStack.stackSize;
return masterStack.stackTagCompound.getInteger(MASTER_OVERRIDE_TAG);
}
/**
* Set the master channel override on given trigger item
*
* @param masterStack trigger stack to set
* @param data positive master channel data
*/
public static void setMasterData(ItemStack masterStack, int data) {
if (masterStack == null) throw new IllegalArgumentException();
if (data < 1) throw new IllegalArgumentException("Channel data must be positive");
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
masterStack.stackTagCompound.setInteger(MASTER_OVERRIDE_TAG, data);
}
/**
* Clear the master channel override from given trigger item, if it exists
*
* @param masterStack trigger stack to unset
*/
public static void unsetMasterData(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException();
if (masterStack.stackTagCompound == null) return;
masterStack.stackTagCompound.removeTag(MASTER_OVERRIDE_TAG);
if (masterStack.stackTagCompound.hasNoTags()) masterStack.stackTagCompound = null;
}
/**
* Return a trigger item that
* <ul>
@@ -142,14 +222,15 @@ public class ChannelDataAccessor {
}
/**
* Wipe all subchannel data on given trigger item
*
* Wipe all subchannel data and master channel override on given trigger item
*
* @param masterStack trigger stack to wipe
*/
public static void wipeChannelData(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException();
if (masterStack.stackTagCompound != null) {
masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
masterStack.stackTagCompound.removeTag(MASTER_OVERRIDE_TAG);
if (masterStack.stackTagCompound.hasNoTags()) {
masterStack.stackTagCompound = null;
}
@@ -28,7 +28,7 @@ public class ConstructableUtility {
public static boolean handle(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ,
int aSide) {
ItemStack effectiveStack = ChannelDataAccessor.withChannel(aStack, StructureLibAPI.CHANNEL_TIER);
ItemStack effectiveStack = ChannelDataAccessor.withMasterData(aStack);
StructureLibAPI.startHinting(aWorld);
boolean ret = handle0(effectiveStack, aPlayer, aWorld, aX, aY, aZ, aSide);
StructureLibAPI.endHinting(aWorld);
@@ -12,6 +12,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import com.gtnewhorizon.structurelib.ChannelDescription;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
@@ -26,7 +27,10 @@ public class CommandConfigureChannels extends CommandBase {
public String getCommandUsage(ICommandSender p_71518_1_) {
return "/sl_channel <wipe/getall>\n" + "/sl_channel <get/unset> <channel name>\n"
+ "/sl_channel <set> <channel name> <value>\n"
+ "<channel name> can have space in it. Values below 1 clear the channel";
+ "<channel name> can have space in it. Values below 1 clear the channel\n"
+ "Use "
+ ChannelDescription.MASTER_KEY
+ " as <channel name> to override the projector stack size";
}
@Override
@@ -56,14 +60,14 @@ public class CommandConfigureChannels extends CommandBase {
switch (args[0]) {
case "getall":
if (!ChannelDataAccessor.hasSubChannel(heldItem)) {
if (ChannelDescription.countData(heldItem) == 0) {
sender.addChatMessage(new ChatComponentText("No subchannel"));
} else {
sender.addChatMessage(
new ChatComponentTranslation(
"item.structurelib.constructableTrigger.desc.lshift.0",
ChannelDataAccessor.countChannelData(heldItem)));
ChannelDataAccessor.iterateChannelData(heldItem).map(e -> e.getKey() + ": " + e.getValue())
ChannelDescription.iterateData(heldItem).map(e -> e.getKey() + ": " + e.getValue())
.map(ChatComponentText::new).forEach(player::addChatMessage);
}
return;
@@ -86,9 +90,12 @@ public class CommandConfigureChannels extends CommandBase {
switch (args[0]) {
case "get":
if (ChannelDataAccessor.hasSubChannel(heldItem, channel)) sender.addChatMessage(
if (ChannelDescription.isSet(heldItem, channel)) sender.addChatMessage(
new ChatComponentText(
channel + " value: " + ChannelDataAccessor.getChannelData(heldItem, channel)));
channel + " value: "
+ (ChannelDescription.MASTER_KEY.equals(channel)
? ChannelDataAccessor.getMasterData(heldItem)
: ChannelDataAccessor.getChannelData(heldItem, channel))));
else sender.addChatMessage(new ChatComponentText(channel + " value: N/A"));
break;
case "clear":
@@ -109,9 +116,9 @@ public class CommandConfigureChannels extends CommandBase {
}
if (value < 1) {
ChannelDataAccessor.unsetChannelData(heldItem, channel);
ChannelDescription.unsetData(heldItem, channel);
} else {
ChannelDataAccessor.setChannelData(heldItem, channel, value);
ChannelDescription.setData(heldItem, channel, value);
}
player.inventoryContainer.detectAndSendChanges();
sender.addChatMessage(
@@ -317,7 +317,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
// this will be called from setText of key and value. NEVER UPDATE THE VALUE OF THESE HERE OR GET A
// STACKOVERFLOW!
String keyText = key.getText();
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDescription.isSet(trigger, keyText);
getButtonList().get(ADD_BTN).displayString = existing ? I18n.format(I18N_PREFIX + "set")
: I18n.format(I18N_PREFIX + "add");
getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(key.getText())
@@ -343,10 +343,10 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
case ADD_BTN:
int value = getValue();
if (value <= 0) return;
ChannelDataAccessor.setChannelData(trigger, key.getText(), value);
ChannelDescription.setData(trigger, key.getText(), value);
break;
case UNSET_BTN:
ChannelDataAccessor.unsetChannelData(trigger, key.getText());
ChannelDescription.unsetData(trigger, key.getText());
break;
case WIPE_BTN:
if (!wipePending) {
@@ -501,7 +501,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
case 1:
default: // TODO implement a GUI to select which pair to use, instead of blindly using the first
Entry<String, Integer> e = channels.iterator().next();
ChannelDataAccessor.setChannelData(trigger, e.getKey(), e.getValue());
ChannelDescription.setData(trigger, e.getKey(), e.getValue());
key.setText(e.getKey());
value.setText(e.getValue().toString());
draggedStack.stackSize = 0;
@@ -625,12 +625,12 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
@Override
public Entry<String, Integer> getElementAt(int index) {
return ChannelDataAccessor.iterateChannelData(trigger).sorted().skip(index).findFirst().orElse(null);
return ChannelDescription.iterateData(trigger).skip(index).findFirst().orElse(null);
}
@Override
public int getNumElements() {
return ChannelDataAccessor.countChannelData(trigger);
return ChannelDescription.countData(trigger);
}
@Override
@@ -640,7 +640,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
&& mYRelative <= margin / 2 + 5) {
Entry<String, Integer> e = getElementAt(elementIndex);
if (e != null) {
ChannelDataAccessor.unsetChannelData(trigger, e.getKey());
ChannelDescription.unsetData(trigger, e.getKey());
updateButtons();
return false;
}
@@ -650,7 +650,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
@Override
protected void prepareDrawElements() {
cache = ChannelDataAccessor.iterateChannelData(trigger).sorted().collect(Collectors.toList());
cache = ChannelDescription.iterateData(trigger).collect(Collectors.toList());
}
@Override
@@ -671,7 +671,8 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
GL11.glColor4f(1, 1, 1, 1);
drawTexturedModalRect(x + margin + 1, y + margin / 2 + 1, 251, 251, 5, 5);
}
fontRendererObj.drawString(trim(e.getKey(), keyMaxWidth - 9), x + margin + 8, y + margin / 2, 0xffffff);
int keyColor = ChannelDescription.MASTER_KEY.equals(e.getKey()) ? 0xffaa00 : 0xffffff;
fontRendererObj.drawString(trim(e.getKey(), keyMaxWidth - 9), x + margin + 8, y + margin / 2, keyColor);
int valueX = x + margin + keyMaxWidth + margin;
fontRendererObj.drawString(trim(e.getValue().toString(), maxX - valueX), valueX, y + margin / 2, 0xffffff);
}
@@ -14,6 +14,7 @@ import net.minecraft.world.World;
import org.lwjgl.input.Keyboard;
import com.gtnewhorizon.structurelib.ChannelDescription;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
@@ -52,8 +53,7 @@ public class ItemConstructableTrigger extends Item {
translateToLocalFormatted(
"item.structurelib.constructableTrigger.desc.lshift.0",
ChannelDataAccessor.countChannelData(stack)));
ChannelDataAccessor.iterateChannelData(stack).map(e -> e.getKey() + ": " + e.getValue())
.forEach(tooltip::add);
ChannelDescription.iterateData(stack).map(e -> e.getKey() + ": " + e.getValue()).forEach(tooltip::add);
} else {
// Tooltip text:
// Triggers Constructable Interface
@@ -67,7 +67,7 @@ public class ItemConstructableTrigger extends Item {
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.2"));
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.3"));
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.4"));
if (ChannelDataAccessor.hasSubChannel(stack))
if (ChannelDataAccessor.hasSubChannel(stack) || ChannelDataAccessor.hasMasterData(stack))
tooltip.add(translateToLocal("item.structurelib.constructableTrigger.desc.5"));
}
}
@@ -24,6 +24,8 @@ public class SetChannelDataMessage implements IMessage {
private final List<Map.Entry<String, Integer>> data = new ArrayList<>();
private boolean onCursor;
// 0 means no master override
private int master;
public SetChannelDataMessage() {}
@@ -34,11 +36,13 @@ public class SetChannelDataMessage implements IMessage {
public SetChannelDataMessage(ItemStack trigger, final boolean onCursor) {
this.onCursor = onCursor;
ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add);
master = ChannelDataAccessor.hasMasterData(trigger) ? ChannelDataAccessor.getMasterData(trigger) : 0;
}
@Override
public void fromBytes(ByteBuf buf) {
onCursor = buf.readBoolean();
master = ByteBufUtils.readVarInt(buf, 5);
data.clear();
int size = ByteBufUtils.readVarShort(buf);
for (int i = 0; i < size; i++) {
@@ -49,6 +53,7 @@ public class SetChannelDataMessage implements IMessage {
@Override
public void toBytes(ByteBuf buf) {
buf.writeBoolean(onCursor);
ByteBufUtils.writeVarInt(buf, master, 5);
ByteBufUtils.writeVarShort(buf, data.size());
for (Entry<String, Integer> e : data) {
ByteBufUtils.writeUTF8String(buf, e.getKey());
@@ -64,6 +69,7 @@ public class SetChannelDataMessage implements IMessage {
ItemStack target = message.onCursor ? player.inventory.getItemStack() : player.getHeldItem();
if (target != null && target.getItem() instanceof ItemConstructableTrigger) {
ChannelDataAccessor.wipeChannelData(target);
if (message.master > 0) ChannelDataAccessor.setMasterData(target, message.master);
for (Entry<String, Integer> e : message.data) {
ChannelDataAccessor.setChannelData(target, e.getKey(), e.getValue());
}
@@ -42,7 +42,7 @@ item.structurelib.constructableTrigger.gui.channels.from=§rMod: §9§o%s
item.structurelib.constructableTrigger.gui.info=Many multiblocks support some degree of variation in their structure and a sub channel allows indication of which variant to use.\n All sub channels have a sequence of lowercase characters as key and a positive integer as value. Most known sub channels should be available as typeahead completions.\n You can also drag and drop items from NEI to set the corresponding sub channel value. These items all have a tooltip of "§oIndicates§r §ovalue§r §oX§r §ofor§r §osub§r §ochannel§r §oY§r".
channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong
channels.structurelib.tier=Selects the constructable tier and overrides the projector stack size
channels.structurelib.master=Overrides the projector stack size, i.e. the value used by every sub channel that is not set
structurelib.tooltip.channelvalue=§7Value §b%d§r§7 for subchannel §b%s
structurelib.tooltip.indicator_dnd=Drop into GUI or middle-click to select this value for this channel
@@ -41,7 +41,7 @@ item.structurelib.constructableTrigger.gui.channels.from=§r模组: §9§o%s
item.structurelib.constructableTrigger.gui.info=许多多方块的结构都是可变的,在自动放置时,可以使用子信道来向多方块指明该使用哪一个变体。\n子信道由一个字符串标识、并拥有一个正整数作为值。这个GUI中的信道名支持自动补全到绝大多数已知的子信道。\n你也可以将部分物品从NEI中拖拽至GUI内来设置子信道值为该物品表示的值。这些物品的物品说明中都带有§o表示子信道 X 为 Y§r。
channels.structurelib.show_errors=添加后,在投影时会高亮标出必然错误的部分
channels.structurelib.tier=选择多方块结构等级,并覆盖投影仪堆叠数量
channels.structurelib.master=覆盖投影仪堆叠数量,即所有未设置的子信道所使用的值
structurelib.tooltip.channelvalue=§7表示子信道 §b%2$s§r§7 为 §b%d§r
structurelib.tooltip.indicator_dnd=拖入GUI中来选择该值