Compare commits

..
Author SHA1 Message Date
Alex 4fdea65537 feat(middle-click): Add in GUI and in-world middle-click pick to set and reset channels (#74) 2026-09-21 10:36:57 +08:00
Chitak 985 89057ebb2d Clean up empty NBT when clearing projector channels (#75) 2026-09-20 09:13:19 +08:00
Eldrinn Elantey d829ae466b Localize and unify missing block messages in autoplace (#70)
StructureLib built the missing block name on the server, where only
en_US is loaded, so players on dedicated servers saw English names in
chat. Send the item's lang key and let the client translate it. Stacks
whose name is not a plain key, GT meta items among them, keep the old
server side name.

Players also saw two shapes of the same message: a bracketed name with
rarity color and an item tooltip from two of the survivalPlaceBlocks
branches, a bare yellow name from the stack list branch. All three now
build the name the same way.
2026-09-09 09:21:27 +08:00
21 changed files with 646 additions and 324 deletions

No files matched your search

-1
View File
@@ -3,7 +3,6 @@
dependencies { dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.11.21:dev") api("com.github.GTNewHorizons:GTNHLib:0.11.21:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.110-GTNH:dev") implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.110-GTNH:dev")
testImplementation(platform('org.junit:junit-bom:5.9.2')) testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly ('org.junit.platform:junit-platform-launcher') testRuntimeOnly ('org.junit.platform:junit-platform-launcher')
@@ -0,0 +1,88 @@
package com.gtnewhorizon.structurelib;
import java.util.Collection;
import java.util.Map;
import javax.annotation.Nullable;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
/**
* Handler for middle-click channel picking. When the player holds a hologram projector on the cursor and middle-clicks
* a registered indicator item, the corresponding channel is set on the projector. The same applies in world when the
* projector is held in hand and a registered indicator block is middle-clicked. Shift+middle-click wipes all channels.
* Was made side neutral to support server-side validation at some point if necessary.
*/
public final class ChannelPickHandler {
private ChannelPickHandler() {}
/**
* Handle a middle-click event while holding a hologram projector on the cursor.
*/
public static boolean handleMiddleClick(EntityPlayer player, ItemStack holoStack, @Nullable ItemStack targetStack,
final boolean isSneaking) {
return handlePick(player, holoStack, targetStack, isSneaking, true);
}
public static boolean handleWorldPick(World world, EntityPlayer player, ItemStack holoStack,
@Nullable MovingObjectPosition hit, final boolean isSneaking) {
ItemStack targetStack = null;
if (hit != null) {
Block block = world.getBlock(hit.blockX, hit.blockY, hit.blockZ);
if (!block.isAir(world, hit.blockX, hit.blockY, hit.blockZ)) {
targetStack = block.getPickBlock(hit, world, hit.blockX, hit.blockY, hit.blockZ, player);
}
}
// A null target still goes through so that shift+middle-click wipes even when aiming at nothing
return handlePick(player, holoStack, targetStack, isSneaking, false);
}
private static boolean handlePick(EntityPlayer player, ItemStack holoStack, @Nullable ItemStack targetStack,
final boolean isSneaking, final boolean onCursor) {
// Clear channels on shift+middle-click. The "press again to confirm" gesture is gated client-side
// (see ChannelWipeConfirmation) before this method is ever called.
if (isSneaking) {
ChannelDataAccessor.wipeChannelData(holoStack);
sendChannelData(player, holoStack, onCursor);
player.addChatMessage(new ChatComponentTranslation("structurelib.pickchannel.cleared"));
return true;
}
if (targetStack == null) return false; // nothing hovered, cancel silently
Collection<Map.Entry<String, Integer>> channels = ChannelDescription.iterate(targetStack);
if (channels.isEmpty()) {
player.addChatMessage(new ChatComponentTranslation("structurelib.pickchannel.nomapping"));
return false;
}
// 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());
sendChannelData(player, holoStack, onCursor);
player.addChatMessage(
new ChatComponentTranslation("structurelib.pickchannel.success", first.getKey(), first.getValue()));
return true;
}
/**
* Player is the one triggering this update, so should already be in-sync.
*/
private static void sendChannelData(EntityPlayer player, ItemStack holoStack, boolean onCursor) {
if (!(player instanceof EntityPlayerMP)) {
StructureLib.net.sendToServer(new SetChannelDataMessage(holoStack, onCursor));
}
}
}
@@ -0,0 +1,33 @@
package com.gtnewhorizon.structurelib;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ChatComponentTranslation;
/**
* Client-side-only confirmation gate for "wipe all channels" shift+middle-click gesture.
*/
public final class ChannelWipeConfirmation {
private static final long CONFIRM_WINDOW_MS = 5000;
private static long armedAtMs = -1;
private ChannelWipeConfirmation() {}
/**
* Call this before invoking {@link ChannelPickHandler} whenever the wipe gesture (shift+middle-click) is detected.
*
* @return true if this is a confirming second press within the window and the wipe should proceed now; false if
* this press only armed the confirmation.
*/
public static boolean confirm(EntityPlayer player) {
long now = Minecraft.getSystemTime();
if (now - armedAtMs > CONFIRM_WINDOW_MS) {
armedAtMs = now;
player.addChatMessage(new ChatComponentTranslation("structurelib.pickchannel.confirmwipe"));
return false;
}
armedAtMs = -1;
return true;
}
}
@@ -30,6 +30,7 @@ import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon; import net.minecraft.util.IIcon;
import net.minecraft.util.Vec3; import net.minecraft.util.Vec3;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.ConfigElement; import net.minecraftforge.common.config.ConfigElement;
@@ -40,6 +41,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX; import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.net.RegistryOrderSyncMessage; import com.gtnewhorizon.structurelib.net.RegistryOrderSyncMessage;
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage; import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
@@ -53,6 +55,8 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent; import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.common.gameevent.TickEvent.Phase;
import cpw.mods.fml.common.network.FMLNetworkEvent; import cpw.mods.fml.common.network.FMLNetworkEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public class ClientProxy extends CommonProxy { public class ClientProxy extends CommonProxy {
@@ -560,6 +564,35 @@ public class ClientProxy extends CommonProxy {
public static class ForgeEventHandler { public static class ForgeEventHandler {
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onMouseEvent(MouseEvent event) {
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
if (player == null || player.isDead) {
return;
}
final ItemStack heldItem = player.getHeldItem();
if (heldItem == null || !(heldItem.getItem() instanceof ItemConstructableTrigger)) return;
if (event.button != 2 || !event.buttonstate) return;
// Sink the middle click as soon as a projector is held, so vanilla pick block does not run
event.setCanceled(true);
final boolean isSneaking = player.isSneaking();
if (isSneaking && !ChannelWipeConfirmation.confirm(player)) return;
ChannelPickHandler.handleWorldPick(
player.worldObj,
player,
heldItem,
Minecraft.getMinecraft().objectMouseOver,
isSneaking);
}
@SubscribeEvent @SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent e) { public void onEntityJoinWorld(EntityJoinWorldEvent e) {
if (e.world.isRemote && e.entity instanceof EntityClientPlayerMP) { if (e.world.isRemote && e.entity instanceof EntityClientPlayerMP) {
@@ -17,6 +17,7 @@ import net.minecraftforge.common.MinecraftForge;
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage; import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage; import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLLoadCompleteEvent; import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent;
@@ -64,6 +65,8 @@ public class CommonProxy {
public void preInit(FMLPreInitializationEvent e) {} public void preInit(FMLPreInitializationEvent e) {}
public void init(FMLInitializationEvent e) {}
public long getOverworldTime() { public long getOverworldTime() {
return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime(); return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime();
} }
@@ -33,6 +33,7 @@ import com.gtnewhorizon.structurelib.util.XSTR;
import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLInterModComms; import cpw.mods.fml.common.event.FMLInterModComms;
import cpw.mods.fml.common.event.FMLLoadCompleteEvent; import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent;
@@ -128,6 +129,11 @@ public class StructureLib {
DevelopHelper.onPreInit(); DevelopHelper.onPreInit();
} }
@Mod.EventHandler
public void init(FMLInitializationEvent e) {
proxy.init(e);
}
@Mod.EventHandler @Mod.EventHandler
public void loadComplete(FMLLoadCompleteEvent e) { public void loadComplete(FMLLoadCompleteEvent e) {
proxy.loadComplete(e); proxy.loadComplete(e);
@@ -146,7 +146,12 @@ public class ChannelDataAccessor {
*/ */
public static void wipeChannelData(ItemStack masterStack) { public static void wipeChannelData(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException(); if (masterStack == null) throw new IllegalArgumentException();
if (masterStack.stackTagCompound != null) masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG); if (masterStack.stackTagCompound != null) {
masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
if (masterStack.stackTagCompound.hasNoTags()) {
masterStack.stackTagCompound = null;
}
}
} }
/** /**
@@ -1,5 +1,6 @@
package com.gtnewhorizon.structurelib.core; package com.gtnewhorizon.structurelib.core;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -7,7 +8,9 @@ import java.util.Set;
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader; import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
import cpw.mods.fml.relauncher.FMLLaunchHandler;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin; import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import cpw.mods.fml.relauncher.Side;
@IFMLLoadingPlugin.MCVersion("1.7.10") @IFMLLoadingPlugin.MCVersion("1.7.10")
public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader { public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
@@ -44,6 +47,11 @@ public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
@Override @Override
public List<String> getMixins(Set<String> loadedCoreMods) { public List<String> getMixins(Set<String> loadedCoreMods) {
return Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld"); List<String> mixins = new ArrayList<>(
Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld"));
if (FMLLaunchHandler.side() == Side.CLIENT) {
mixins.add("MixinGuiContainer_PickChannel");
}
return mixins;
} }
} }
@@ -0,0 +1,64 @@
package com.gtnewhorizon.structurelib.mixins.early;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.gtnewhorizon.structurelib.ChannelPickHandler;
import com.gtnewhorizon.structurelib.ChannelWipeConfirmation;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.guihook.GuiContainerManager;
import cpw.mods.fml.common.Loader;
@Mixin(value = GuiContainer.class, priority = 100)
public abstract class MixinGuiContainer_PickChannel {
@Inject(method = "mouseClicked(III)V", at = @At("HEAD"), cancellable = true)
private void slib$onMouseClicked(int mouseX, int mouseY, int mouseButton, CallbackInfo ci) {
if (mouseButton != 2) return;
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
if (player == null) return;
ItemStack cursorStack = player.inventory.getItemStack();
if (cursorStack == null || !(cursorStack.getItem() instanceof ItemConstructableTrigger)) return;
// Sink middle click as soon as projector is picked up
ci.cancel();
ItemStack hoveredStack = null;
try {
Slot slot = slib$invokeGetSlotAtPosition(mouseX, mouseY);
if (slot != null) {
hoveredStack = slot.getStack();
}
// try NEI
if (hoveredStack == null && Loader.isModLoaded("NotEnoughItems") && !NEIClientConfig.isHidden()) {
hoveredStack = GuiContainerManager.getStackMouseOver((GuiContainer) (Object) this);
}
} catch (Exception e) {
StructureLib.LOGGER.error("Error while resolving hovered slot for channel pick", e);
}
boolean isSneaking = GuiScreen.isShiftKeyDown();
if (isSneaking && !ChannelWipeConfirmation.confirm(player)) return;
ChannelPickHandler.handleMiddleClick(player, cursorStack, hoveredStack, isSneaking);
}
@Invoker("getSlotAtPosition")
protected abstract Slot slib$invokeGetSlotAtPosition(int x, int y);
}
@@ -23,15 +23,22 @@ import io.netty.buffer.ByteBuf;
public class SetChannelDataMessage implements IMessage { public class SetChannelDataMessage implements IMessage {
private final List<Map.Entry<String, Integer>> data = new ArrayList<>(); private final List<Map.Entry<String, Integer>> data = new ArrayList<>();
private boolean onCursor;
public SetChannelDataMessage() {} public SetChannelDataMessage() {}
public SetChannelDataMessage(ItemStack trigger) { public SetChannelDataMessage(ItemStack trigger) {
this(trigger, false);
}
public SetChannelDataMessage(ItemStack trigger, final boolean onCursor) {
this.onCursor = onCursor;
ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add); ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add);
} }
@Override @Override
public void fromBytes(ByteBuf buf) { public void fromBytes(ByteBuf buf) {
onCursor = buf.readBoolean();
data.clear(); data.clear();
int size = ByteBufUtils.readVarShort(buf); int size = ByteBufUtils.readVarShort(buf);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
@@ -41,6 +48,7 @@ public class SetChannelDataMessage implements IMessage {
@Override @Override
public void toBytes(ByteBuf buf) { public void toBytes(ByteBuf buf) {
buf.writeBoolean(onCursor);
ByteBufUtils.writeVarShort(buf, data.size()); ByteBufUtils.writeVarShort(buf, data.size());
for (Entry<String, Integer> e : data) { for (Entry<String, Integer> e : data) {
ByteBufUtils.writeUTF8String(buf, e.getKey()); ByteBufUtils.writeUTF8String(buf, e.getKey());
@@ -53,11 +61,11 @@ public class SetChannelDataMessage implements IMessage {
@Override @Override
public IMessage onMessage(SetChannelDataMessage message, MessageContext ctx) { public IMessage onMessage(SetChannelDataMessage message, MessageContext ctx) {
EntityPlayerMP player = ctx.getServerHandler().playerEntity; EntityPlayerMP player = ctx.getServerHandler().playerEntity;
ItemStack heldItem = player.getHeldItem(); ItemStack target = message.onCursor ? player.inventory.getItemStack() : player.getHeldItem();
if (heldItem != null && heldItem.getItem() instanceof ItemConstructableTrigger) { if (target != null && target.getItem() instanceof ItemConstructableTrigger) {
ChannelDataAccessor.wipeChannelData(heldItem); ChannelDataAccessor.wipeChannelData(target);
for (Entry<String, Integer> e : message.data) { for (Entry<String, Integer> e : message.data) {
ChannelDataAccessor.setChannelData(heldItem, e.getKey(), e.getValue()); ChannelDataAccessor.setChannelData(target, e.getKey(), e.getValue());
} }
// since this is a set all channel request from the client, we would assume client already know // since this is a set all channel request from the client, we would assume client already know
// what this would look like on the client, so no sync // what this would look like on the client, so no sync
@@ -65,7 +73,7 @@ public class SetChannelDataMessage implements IMessage {
StructureLib.LOGGER.warn( StructureLib.LOGGER.warn(
"{} trying to set channel data on {}, which is not a hologram projector!", "{} trying to set channel data on {}, which is not a hologram projector!",
player.getUniqueID(), player.getUniqueID(),
heldItem); target);
} }
return null; return null;
} }
@@ -67,7 +67,7 @@ public interface IStructureDefinition<T> {
* @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly * @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly
* matches * matches
*/ */
IStructureElement<? super T>[] getStructureFor(String name); IStructureElement<T>[] getStructureFor(String name);
boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC); boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC);
@@ -356,7 +356,7 @@ public interface IStructureDefinition<T> {
* @return true if iteration completed successfully. * @return true if iteration completed successfully.
* @param <T> type of context object. * @param <T> type of context object.
*/ */
static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<? super T>[] elements, World world, static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<T>[] elements, World world,
ExtendedFacing extendedFacing, int basePositionX, int basePositionY, int basePositionZ, int basePositionA, ExtendedFacing extendedFacing, int basePositionX, int basePositionY, int basePositionZ, int basePositionA,
int basePositionB, int basePositionC, boolean hintsOnly, Boolean checkBlocksIfNotNullForceCheckAllIfTrue) { int basePositionB, int basePositionC, boolean hintsOnly, Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
if (!world.isRemote && hintsOnly) { if (!world.isRemote && hintsOnly) {
@@ -28,9 +28,6 @@ import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
/** /**
* Use StructureUtility to instantiate. These are the building blocks for your {@link IStructureDefinition}. It * Use StructureUtility to instantiate. These are the building blocks for your {@link IStructureDefinition}. It
* represents what a particular element can be and how this particular element should be autoplaced. * represents what a particular element can be and how this particular element should be autoplaced.
*
* @param <T> Type of the context object. {@link IStructureElement} is contravariant over T, so parameters and fields of
* type {@link IStructureElement} should be declared as {@code IStructureElement<? super T>}
*/ */
public interface IStructureElement<T> { public interface IStructureElement<T> {
@@ -216,7 +213,7 @@ public interface IStructureElement<T> {
* Forget the messed up class dependency graph for now. this is just so convenient. * Forget the messed up class dependency graph for now. this is just so convenient.
*/ */
default IStructureElementNoPlacement<T> noPlacement() { default IStructureElementNoPlacement<T> noPlacement() {
return new IStructureElementNoPlacement<>() { return new IStructureElementNoPlacement<T>() {
@Override @Override
public boolean check(T t, World world, int x, int y, int z) { public boolean check(T t, World world, int x, int y, int z) {
@@ -21,11 +21,11 @@ import com.google.common.collect.Iterables;
*/ */
public interface IStructureElementChain<T> extends IStructureElement<T> { public interface IStructureElementChain<T> extends IStructureElement<T> {
IStructureElement<? super T>[] fallbacks(); IStructureElement<T>[] fallbacks();
@Override @Override
default boolean check(T t, World world, int x, int y, int z) { default boolean check(T t, World world, int x, int y, int z) {
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.check(t, world, x, y, z)) { if (fallback.check(t, world, x, y, z)) {
return true; return true;
} }
@@ -35,7 +35,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override @Override
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) { default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.couldBeValid(t, world, x, y, z, trigger)) { if (fallback.couldBeValid(t, world, x, y, z, trigger)) {
return true; return true;
} }
@@ -45,7 +45,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override @Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.spawnHint(t, world, x, y, z, trigger)) { if (fallback.spawnHint(t, world, x, y, z, trigger)) {
return true; return true;
} }
@@ -55,7 +55,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override @Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.placeBlock(t, world, x, y, z, trigger)) { if (fallback.placeBlock(t, world, x, y, z, trigger)) {
return true; return true;
} }
@@ -67,7 +67,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
@Override @Override
default List<String> getDescription(T context) { default List<String> getDescription(T context) {
Set<String> descriptions = new LinkedHashSet<>(); Set<String> descriptions = new LinkedHashSet<>();
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
List<String> desc = fallback.getDescription(context); List<String> desc = fallback.getDescription(context);
if (desc != null) { if (desc != null) {
descriptions.addAll(desc); descriptions.addAll(desc);
@@ -82,7 +82,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
AutoPlaceEnvironment env) { AutoPlaceEnvironment env) {
Predicate<ItemStack> predicate = null; Predicate<ItemStack> predicate = null;
List<Iterable<ItemStack>> is = new ArrayList<>(); List<Iterable<ItemStack>> is = new ArrayList<>();
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
BlocksToPlace e = fallback.getBlocksToPlace(t, world, x, y, z, trigger, env); BlocksToPlace e = fallback.getBlocksToPlace(t, world, x, y, z, trigger, env);
if (e == null) continue; if (e == null) continue;
if (predicate == null) predicate = e.getPredicate(); if (predicate == null) predicate = e.getPredicate();
@@ -99,7 +99,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
EntityPlayerMP actor, Consumer<IChatComponent> chatter) { EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
boolean allContinue = true; boolean allContinue = true;
List<IChatComponent> bufferedNoise = new ArrayList<>(); List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add); PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add);
switch (result) { switch (result) {
case REJECT_CONTINUE: case REJECT_CONTINUE:
@@ -124,7 +124,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
AutoPlaceEnvironment env) { AutoPlaceEnvironment env) {
boolean allContinue = true; boolean allContinue = true;
List<IChatComponent> bufferedNoise = new ArrayList<>(); List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<? super T> fallback : fallbacks()) { for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback PlaceResult result = fallback
.survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add)); .survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add));
switch (result) { switch (result) {
@@ -10,36 +10,36 @@ import net.minecraft.world.World;
/** /**
* Use StructureUtility to instantiate * Use StructureUtility to instantiate
*/ */
interface IStructureNavigate extends IStructureElement<Object> { interface IStructureNavigate<T> extends IStructureElement<T> {
@Override @Override
default boolean check(Object t, World world, int x, int y, int z) { default boolean check(T t, World world, int x, int y, int z) {
return true; return true;
} }
@Override @Override
default boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) { default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return true; return true;
} }
@Override @Override
default boolean spawnHint(Object t, World world, int x, int y, int z, ItemStack trigger) { default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return true; return true;
} }
@Override @Override
default boolean placeBlock(Object t, World world, int x, int y, int z, ItemStack trigger) { default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return true; return true;
} }
@Override @Override
default PlaceResult survivalPlaceBlock(Object t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
EntityPlayerMP actor, Consumer<IChatComponent> chatter) { EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return PlaceResult.SKIP; return PlaceResult.SKIP;
} }
@Override @Override
default PlaceResult survivalPlaceBlock(Object t, World world, int x, int y, int z, ItemStack trigger, default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) { AutoPlaceEnvironment env) {
return PlaceResult.SKIP; return PlaceResult.SKIP;
} }
@@ -21,42 +21,40 @@ public interface IStructureWalker<T> {
* @param c The relative C position of the element in structure-local coordinates (pre transform) * @param c The relative C position of the element in structure-local coordinates (pre transform)
* @return False to stop iterating, true to continue. * @return False to stop iterating, true to continue.
*/ */
boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, int c); boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
default boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int c) { int c) {
return false; return false;
} }
static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) { static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) {
return new IStructureWalker<>() { return new IStructureWalker<T>() {
@Override @Override
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
int c) {
return walker.visit(element, world, x, y, z, a, b, c); return walker.visit(element, world, x, y, z, a, b, c);
} }
@Override @Override
public boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a, public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int b, int c) { int c) {
return walker.visit(element, world, x, y, z, a, b, c); return walker.visit(element, world, x, y, z, a, b, c);
} }
}; };
} }
static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) { static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) {
return new IStructureWalker<>() { return new IStructureWalker<T>() {
@Override @Override
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
int c) {
return walker.visit(element, world, x, y, z, a, b, c); return walker.visit(element, world, x, y, z, a, b, c);
} }
@Override @Override
public boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a, public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int b, int c) { int c) {
return true; return true;
} }
}; };
@@ -13,14 +13,14 @@ import net.minecraft.world.World;
class LazyStructureElement<T> implements IStructureElementDeferred<T> { class LazyStructureElement<T> implements IStructureElementDeferred<T> {
private Function<? super T, ? extends IStructureElement<? super T>> to; private Function<T, IStructureElement<T>> to;
private IStructureElement<? super T> elem; private IStructureElement<T> elem;
public LazyStructureElement(Function<? super T, ? extends IStructureElement<? super T>> to) { public LazyStructureElement(Function<T, IStructureElement<T>> to) {
this.to = to; this.to = to;
} }
private IStructureElement<? super T> get(T t) { private IStructureElement<T> get(T t) {
if (to != null) { if (to != null) {
elem = to.apply(t); elem = to.apply(t);
to = null; to = null;
@@ -16,17 +16,17 @@ import com.gtnewhorizon.structurelib.util.Vec3Impl;
public class StructureDefinition<T> implements IStructureDefinition<T> { public class StructureDefinition<T> implements IStructureDefinition<T> {
private final Map<Character, IStructureElement<? super T>> elements; private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes; private final Map<String, String> shapes;
private final Map<String, IStructureElement<? super T>[]> structures; private final Map<String, IStructureElement<T>[]> structures;
private final Map<String, Set<Vec3Impl>> occupiedSpaces; private final Map<String, Set<Vec3Impl>> occupiedSpaces;
public static <B> Builder<B> builder() { public static <B> Builder<B> builder() {
return new Builder<>(); return new Builder<>();
} }
private StructureDefinition(Map<Character, IStructureElement<? super T>> elements, Map<String, String> shapes, private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
Map<String, IStructureElement<? super T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) { Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
this.elements = elements; this.elements = elements;
this.shapes = shapes; this.shapes = shapes;
this.structures = structures; this.structures = structures;
@@ -40,7 +40,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
private static final char C = '\uC000'; private static final char C = '\uC000';
private char d = '\uD000'; private char d = '\uD000';
private final Map<Vec3Impl, Character> navigates; private final Map<Vec3Impl, Character> navigates;
private final Map<Character, IStructureElement<? super T>> elements; private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes; private final Map<String, String> shapes;
private final Map<String, Set<Vec3Impl>> occupiedSpaces; private final Map<String, Set<Vec3Impl>> occupiedSpaces;
@@ -51,7 +51,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
occupiedSpaces = new HashMap<>(); occupiedSpaces = new HashMap<>();
} }
public Map<Character, IStructureElement<? super T>> getElements() { public Map<Character, IStructureElement<T>> getElements() {
return elements; return elements;
} }
@@ -181,18 +181,18 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
* @deprecated use the unboxed version * @deprecated use the unboxed version
*/ */
@Deprecated @Deprecated
public Builder<T> addElement(Character name, IStructureElement<? super T> structurePiece) { public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece); elements.putIfAbsent(name, structurePiece);
return this; return this;
} }
public Builder<T> addElement(char name, IStructureElement<? super T> structurePiece) { public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece); elements.putIfAbsent(name, structurePiece);
return this; return this;
} }
public IStructureDefinition<T> build() { public IStructureDefinition<T> build() {
Map<String, IStructureElement<? super T>[]> structures = compileStructureMap(); Map<String, IStructureElement<T>[]> structures = compileStructureMap();
return new StructureDefinition<>( return new StructureDefinition<>(
new HashMap<>(elements), new HashMap<>(elements),
new HashMap<>(shapes), new HashMap<>(shapes),
@@ -201,7 +201,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Map<String, IStructureElement<? super T>[]> compileElementSetMap() { private Map<String, IStructureElement<T>[]> compileElementSetMap() {
Set<Integer> missing = findMissing(); Set<Integer> missing = findMissing();
if (missing.isEmpty()) { if (missing.isEmpty()) {
return shapes.entrySet().stream().collect( return shapes.entrySet().stream().collect(
@@ -217,7 +217,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Map<String, IStructureElement<? super T>[]> compileStructureMap() { private Map<String, IStructureElement<T>[]> compileStructureMap() {
Set<Integer> missing = findMissing(); Set<Integer> missing = findMissing();
if (missing.isEmpty()) { if (missing.isEmpty()) {
return shapes.entrySet().stream().collect( return shapes.entrySet().stream().collect(
@@ -238,7 +238,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
} }
} }
public Map<Character, IStructureElement<? super T>> getElements() { public Map<Character, IStructureElement<T>> getElements() {
return elements; return elements;
} }
@@ -246,13 +246,13 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
return shapes; return shapes;
} }
public Map<String, IStructureElement<? super T>[]> getStructures() { public Map<String, IStructureElement<T>[]> getStructures() {
return structures; return structures;
} }
@Override @Override
public IStructureElement<? super T>[] getStructureFor(String name) { public IStructureElement<T>[] getStructureFor(String name) {
IStructureElement<? super T>[] elements = structures.get(name); IStructureElement<T>[] elements = structures.get(name);
if (elements == null) throw new NoSuchElementException(name); if (elements == null) throw new NoSuchElementException(name);
return elements; return elements;
} }
File diff suppressed because it is too large. Load diff
@@ -37,7 +37,7 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
} }
@Override @Override
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, int c) { public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
env.offsetABC[0] = a; env.offsetABC[0] = a;
env.offsetABC[1] = b; env.offsetABC[1] = b;
env.offsetABC[2] = c; env.offsetABC[2] = c;
@@ -0,0 +1,64 @@
package com.gtnewhorizon.structurelib.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableMap;
import java.util.Spliterator;
import java.util.TreeMap;
import java.util.function.Consumer;
public class SortedRegistry<V> implements Iterable<V> {
private final NavigableMap<String, V> store = new TreeMap<>();
/**
* Store stuff in an array for faster iteration access than a red black tree
*/
private List<V> baked = Collections.emptyList();
public void register(String key, V val) {
if (key == null || val == null) throw new NullPointerException();
V old = store.putIfAbsent(key, val);
if (old != null) {
throw new IllegalArgumentException("Duplicate key: " + key);
}
// TODO allow a user to edit the order with a configuration file
baked = new ArrayList<>(store.values());
}
public int size() {
return store.size();
}
public boolean isEmpty() {
return store.isEmpty();
}
public boolean containsKey(String key) {
return store.containsKey(key);
}
public boolean containsValue(V value) {
return store.containsValue(value);
}
public V get(String key) {
return store.get(key);
}
@Override
public Iterator<V> iterator() {
return baked.iterator();
}
@Override
public void forEach(Consumer<? super V> action) {
baked.forEach(action);
}
@Override
public Spliterator<V> spliterator() {
return baked.spliterator();
}
}
@@ -44,7 +44,11 @@ item.structurelib.constructableTrigger.gui.info=Many multiblocks support some de
channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong
structurelib.tooltip.channelvalue=§7Value §b%d§r§7 for subchannel §b%s structurelib.tooltip.channelvalue=§7Value §b%d§r§7 for subchannel §b%s
structurelib.tooltip.indicator_dnd=Drop into GUI to select this value for this channel structurelib.tooltip.indicator_dnd=Drop into GUI or middle-click to select this value for this channel
structurelib.pickchannel.success=§aSet §b%s§r§a to §b%d§r
structurelib.pickchannel.cleared=§aAll channels cleared§r
structurelib.pickchannel.confirmwipe=§6Sneak+Middle-click again to clear all channels
structurelib.pickchannel.nomapping=§cNo channel mapping found for this item
structurelib.autoplace.built_stat=Built %s blocks structurelib.autoplace.built_stat=Built %s blocks
structurelib.autoplace.complete=§aThis multiblock has completed! structurelib.autoplace.complete=§aThis multiblock has completed!