mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-09 15:04:56 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e3aefd9fd | ||
|
|
c4b091888b | ||
|
|
68fbdf8a81 | ||
|
|
f3bf5fc1ea | ||
|
|
9f9ab7acd9 | ||
|
|
0fe5a74863 | ||
|
|
d650c173bf |
No files matched your search
@@ -3,6 +3,7 @@
|
||||
dependencies {
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.11.21:dev")
|
||||
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.110-GTNH:dev")
|
||||
|
||||
testImplementation(platform('org.junit:junit-bom:5.9.2'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testRuntimeOnly ('org.junit.platform:junit-platform-launcher')
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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,7 +30,6 @@ import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.event.MouseEvent;
|
||||
import net.minecraftforge.client.event.RenderWorldLastEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.config.ConfigElement;
|
||||
@@ -41,7 +40,6 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX;
|
||||
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
|
||||
import com.gtnewhorizon.structurelib.net.RegistryOrderSyncMessage;
|
||||
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
|
||||
|
||||
@@ -55,8 +53,6 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
|
||||
import cpw.mods.fml.common.network.FMLNetworkEvent;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
|
||||
public class ClientProxy extends CommonProxy {
|
||||
|
||||
@@ -564,35 +560,6 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
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
|
||||
public void onEntityJoinWorld(EntityJoinWorldEvent e) {
|
||||
if (e.world.isRemote && e.entity instanceof EntityClientPlayerMP) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import net.minecraftforge.common.MinecraftForge;
|
||||
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
|
||||
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.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
@@ -65,8 +64,6 @@ public class CommonProxy {
|
||||
|
||||
public void preInit(FMLPreInitializationEvent e) {}
|
||||
|
||||
public void init(FMLInitializationEvent e) {}
|
||||
|
||||
public long getOverworldTime() {
|
||||
return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime();
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.gtnewhorizon.structurelib.util.XSTR;
|
||||
|
||||
import cpw.mods.fml.common.Mod;
|
||||
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.FMLLoadCompleteEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
@@ -129,11 +128,6 @@ public class StructureLib {
|
||||
DevelopHelper.onPreInit();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void init(FMLInitializationEvent e) {
|
||||
proxy.init(e);
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void loadComplete(FMLLoadCompleteEvent e) {
|
||||
proxy.loadComplete(e);
|
||||
|
||||
+4
-11
@@ -38,9 +38,7 @@ public class ChannelDataAccessor {
|
||||
* If given subchannel does not exist, masterStack may or may not be returned to save performance. Code within
|
||||
* StructureLib can depend on this behavior, you cannot.
|
||||
*
|
||||
* @param masterStack trigger item. can be the return value of another
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor#withChannel(net.minecraft.item.ItemStack, String)}
|
||||
* call
|
||||
* @param masterStack trigger item. can be the return value of another {@link #withChannel(ItemStack, String)} call
|
||||
* @param channel subchannel to use. will use current master channel data instead if this channel does not exist
|
||||
* Note: all channel identifiers are supposed to be lower case and not empty.
|
||||
* @return a trigger item with new master channel
|
||||
@@ -109,13 +107,13 @@ public class ChannelDataAccessor {
|
||||
*
|
||||
* @param masterStack trigger stack to check
|
||||
* @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty.
|
||||
* @param data positive subchannel data
|
||||
* @param data subchannel data. should always be a positive value
|
||||
*/
|
||||
public static void setChannelData(ItemStack masterStack, String channel, int data) {
|
||||
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
|
||||
if (data < 1) throw new IllegalArgumentException("Channel data must be positive");
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
if (data <= 0) throw new IllegalArgumentException();
|
||||
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
|
||||
NBTTagCompound main = masterStack.stackTagCompound;
|
||||
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
|
||||
@@ -148,12 +146,7 @@ public class ChannelDataAccessor {
|
||||
*/
|
||||
public static void wipeChannelData(ItemStack masterStack) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
if (masterStack.stackTagCompound != null) {
|
||||
masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
|
||||
if (masterStack.stackTagCompound.hasNoTags()) {
|
||||
masterStack.stackTagCompound = null;
|
||||
}
|
||||
}
|
||||
if (masterStack.stackTagCompound != null) masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ 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. <value> must be positive integer";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,14 +108,11 @@ public class CommandConfigureChannels extends CommandBase {
|
||||
throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
|
||||
}
|
||||
|
||||
if (value < 1) {
|
||||
ChannelDataAccessor.unsetChannelData(heldItem, channel);
|
||||
} else {
|
||||
ChannelDataAccessor.setChannelData(heldItem, channel, value);
|
||||
}
|
||||
if (value <= 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
|
||||
|
||||
ChannelDataAccessor.setChannelData(heldItem, channel, value);
|
||||
player.inventoryContainer.detectAndSendChanges();
|
||||
sender.addChatMessage(
|
||||
new ChatComponentText(value < 1 ? channel + " no value" : channel + " value: " + value));
|
||||
sender.addChatMessage(new ChatComponentText(channel + " value: " + value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizon.structurelib.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -8,9 +7,7 @@ import java.util.Set;
|
||||
|
||||
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
|
||||
|
||||
import cpw.mods.fml.relauncher.FMLLaunchHandler;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
|
||||
@IFMLLoadingPlugin.MCVersion("1.7.10")
|
||||
public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
|
||||
@@ -47,11 +44,6 @@ public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
|
||||
|
||||
@Override
|
||||
public List<String> getMixins(Set<String> loadedCoreMods) {
|
||||
List<String> mixins = new ArrayList<>(
|
||||
Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld"));
|
||||
if (FMLLaunchHandler.side() == Side.CLIENT) {
|
||||
mixins.add("MixinGuiContainer_PickChannel");
|
||||
}
|
||||
return mixins;
|
||||
return Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld");
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
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,22 +23,15 @@ import io.netty.buffer.ByteBuf;
|
||||
public class SetChannelDataMessage implements IMessage {
|
||||
|
||||
private final List<Map.Entry<String, Integer>> data = new ArrayList<>();
|
||||
private boolean onCursor;
|
||||
|
||||
public SetChannelDataMessage() {}
|
||||
|
||||
public SetChannelDataMessage(ItemStack trigger) {
|
||||
this(trigger, false);
|
||||
}
|
||||
|
||||
public SetChannelDataMessage(ItemStack trigger, final boolean onCursor) {
|
||||
this.onCursor = onCursor;
|
||||
ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(ByteBuf buf) {
|
||||
onCursor = buf.readBoolean();
|
||||
data.clear();
|
||||
int size = ByteBufUtils.readVarShort(buf);
|
||||
for (int i = 0; i < size; i++) {
|
||||
@@ -48,7 +41,6 @@ public class SetChannelDataMessage implements IMessage {
|
||||
|
||||
@Override
|
||||
public void toBytes(ByteBuf buf) {
|
||||
buf.writeBoolean(onCursor);
|
||||
ByteBufUtils.writeVarShort(buf, data.size());
|
||||
for (Entry<String, Integer> e : data) {
|
||||
ByteBufUtils.writeUTF8String(buf, e.getKey());
|
||||
@@ -61,11 +53,11 @@ public class SetChannelDataMessage implements IMessage {
|
||||
@Override
|
||||
public IMessage onMessage(SetChannelDataMessage message, MessageContext ctx) {
|
||||
EntityPlayerMP player = ctx.getServerHandler().playerEntity;
|
||||
ItemStack target = message.onCursor ? player.inventory.getItemStack() : player.getHeldItem();
|
||||
if (target != null && target.getItem() instanceof ItemConstructableTrigger) {
|
||||
ChannelDataAccessor.wipeChannelData(target);
|
||||
ItemStack heldItem = player.getHeldItem();
|
||||
if (heldItem != null && heldItem.getItem() instanceof ItemConstructableTrigger) {
|
||||
ChannelDataAccessor.wipeChannelData(heldItem);
|
||||
for (Entry<String, Integer> e : message.data) {
|
||||
ChannelDataAccessor.setChannelData(target, e.getKey(), e.getValue());
|
||||
ChannelDataAccessor.setChannelData(heldItem, e.getKey(), e.getValue());
|
||||
}
|
||||
// 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
|
||||
@@ -73,7 +65,7 @@ public class SetChannelDataMessage implements IMessage {
|
||||
StructureLib.LOGGER.warn(
|
||||
"{} trying to set channel data on {}, which is not a hologram projector!",
|
||||
player.getUniqueID(),
|
||||
target);
|
||||
heldItem);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
/**
|
||||
* Describes a channel used by one structure definition.
|
||||
*
|
||||
* <p>
|
||||
* Channel values are integers stored on the constructable trigger item. Implementations may override the default
|
||||
* validation and normalization policy when a simple bounded range is not sufficient.
|
||||
* </p>
|
||||
*/
|
||||
public interface IChannelDefinition {
|
||||
|
||||
/**
|
||||
* Creates a channel with a bounded inclusive range and clamping normalization.
|
||||
*
|
||||
* @param channel channel identifier
|
||||
* @param minimumValue inclusive minimum value
|
||||
* @param maximumValue inclusive maximum value
|
||||
* @return bounded channel definition
|
||||
*/
|
||||
static IChannelDefinition bounded(String channel, int minimumValue, int maximumValue) {
|
||||
if (minimumValue < 1 || maximumValue < minimumValue) {
|
||||
throw new IllegalArgumentException("Invalid channel bounds");
|
||||
}
|
||||
return new IChannelDefinition() {
|
||||
|
||||
@Override
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumValue() {
|
||||
return minimumValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumValue() {
|
||||
return maximumValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel accepting every positive integer.
|
||||
*
|
||||
* @param channel channel identifier
|
||||
* @return unbounded channel definition
|
||||
*/
|
||||
static IChannelDefinition unbounded(String channel) {
|
||||
return () -> channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lower-case channel identifier.
|
||||
*
|
||||
* @return channel identifier
|
||||
*/
|
||||
String getChannel();
|
||||
|
||||
/**
|
||||
* Returns the smallest value advertised by this definition. The default is {@code 1}.
|
||||
*
|
||||
* @return minimum value
|
||||
*/
|
||||
default int getMinimumValue() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the largest value advertised by this definition. The default is {@link java.lang.Integer#MAX_VALUE}.
|
||||
*
|
||||
* @return maximum value
|
||||
*/
|
||||
default int getMaximumValue() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a value is already valid without normalization.
|
||||
*
|
||||
* @param value value to check
|
||||
* @return true when the value is valid
|
||||
*/
|
||||
default boolean isValueValid(int value) {
|
||||
return value >= getMinimumValue() && value <= getMaximumValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a stored value to the value used by the structure definition.
|
||||
*
|
||||
* @param value stored value
|
||||
* @return normalized value
|
||||
*/
|
||||
default int normalizeValue(int value) {
|
||||
return Math.max(getMinimumValue(), Math.min(getMaximumValue(), value));
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
|
||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -58,48 +56,6 @@ import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
*/
|
||||
public interface IStructureDefinition<T> {
|
||||
|
||||
/**
|
||||
* Returns channel definitions declared by this structure.
|
||||
*
|
||||
* @return an immutable or otherwise read-only channel collection
|
||||
*/
|
||||
default Collection<IChannelDefinition> getChannelDefinitions() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns channel definitions explicitly added by the structure builder.
|
||||
*
|
||||
* @return explicitly declared channel definitions
|
||||
*/
|
||||
default Collection<IChannelDefinition> getExplicitChannelDefinitions() {
|
||||
return getChannelDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a channel definition by identifier.
|
||||
*
|
||||
* @param channel channel identifier
|
||||
* @return channel definition, or {@code null} when it is not declared
|
||||
*/
|
||||
default IChannelDefinition getChannelDefinition(String channel) {
|
||||
for (IChannelDefinition definition : getChannelDefinitions()) {
|
||||
if (definition.getChannel().equals(channel)) return definition;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one channel using the standard trigger-item channel semantics.
|
||||
*
|
||||
* @param trigger trigger item containing channel data
|
||||
* @param channel channel identifier
|
||||
* @return stored channel data, or the trigger stack size when absent
|
||||
*/
|
||||
default int getChannelData(ItemStack trigger, String channel) {
|
||||
return ChannelDataAccessor.getChannelData(trigger, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used internally
|
||||
* <p>
|
||||
@@ -111,7 +67,7 @@ public interface IStructureDefinition<T> {
|
||||
* @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly
|
||||
* matches
|
||||
*/
|
||||
IStructureElement<T>[] getStructureFor(String name);
|
||||
IStructureElement<? super T>[] getStructureFor(String name);
|
||||
|
||||
boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC);
|
||||
|
||||
@@ -400,7 +356,7 @@ public interface IStructureDefinition<T> {
|
||||
* @return true if iteration completed successfully.
|
||||
* @param <T> type of context object.
|
||||
*/
|
||||
static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<T>[] elements, World world,
|
||||
static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<? super T>[] elements, World world,
|
||||
ExtendedFacing extendedFacing, int basePositionX, int basePositionY, int basePositionZ, int basePositionA,
|
||||
int basePositionB, int basePositionC, boolean hintsOnly, Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
|
||||
if (!world.isRemote && hintsOnly) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
@@ -29,20 +28,14 @@ import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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> {
|
||||
|
||||
boolean check(T t, World world, int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Returns the channels used by this element.
|
||||
*
|
||||
* @return lower-case channel identifiers
|
||||
*/
|
||||
default Collection<String> getChannels() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure (stateless and side effect free) function to test if current block could be valid. Used to give user hints
|
||||
* about incorrectly placed blocks given a controller and trigger item. If couldBeValid(...) == false for a set of
|
||||
@@ -223,7 +216,7 @@ public interface IStructureElement<T> {
|
||||
* Forget the messed up class dependency graph for now. this is just so convenient.
|
||||
*/
|
||||
default IStructureElementNoPlacement<T> noPlacement() {
|
||||
return new IStructureElementNoPlacement<T>() {
|
||||
return new IStructureElementNoPlacement<>() {
|
||||
|
||||
@Override
|
||||
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> {
|
||||
|
||||
IStructureElement<T>[] fallbacks();
|
||||
IStructureElement<? super T>[] fallbacks();
|
||||
|
||||
@Override
|
||||
default boolean check(T t, World world, int x, int y, int z) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
if (fallback.check(t, world, x, y, z)) {
|
||||
return true;
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
|
||||
@Override
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
if (fallback.couldBeValid(t, world, x, y, z, trigger)) {
|
||||
return true;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
|
||||
@Override
|
||||
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
if (fallback.spawnHint(t, world, x, y, z, trigger)) {
|
||||
return true;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
|
||||
@Override
|
||||
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
if (fallback.placeBlock(t, world, x, y, z, trigger)) {
|
||||
return true;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
@Override
|
||||
default List<String> getDescription(T context) {
|
||||
Set<String> descriptions = new LinkedHashSet<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
List<String> desc = fallback.getDescription(context);
|
||||
if (desc != null) {
|
||||
descriptions.addAll(desc);
|
||||
@@ -82,7 +82,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
AutoPlaceEnvironment env) {
|
||||
Predicate<ItemStack> predicate = null;
|
||||
List<Iterable<ItemStack>> is = new ArrayList<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
BlocksToPlace e = fallback.getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
if (e == null) continue;
|
||||
if (predicate == null) predicate = e.getPredicate();
|
||||
@@ -99,7 +99,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
boolean allContinue = true;
|
||||
List<IChatComponent> bufferedNoise = new ArrayList<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add);
|
||||
switch (result) {
|
||||
case REJECT_CONTINUE:
|
||||
@@ -124,7 +124,7 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
AutoPlaceEnvironment env) {
|
||||
boolean allContinue = true;
|
||||
List<IChatComponent> bufferedNoise = new ArrayList<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
for (IStructureElement<? super T> fallback : fallbacks()) {
|
||||
PlaceResult result = fallback
|
||||
.survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add));
|
||||
switch (result) {
|
||||
|
||||
@@ -10,36 +10,36 @@ import net.minecraft.world.World;
|
||||
/**
|
||||
* Use StructureUtility to instantiate
|
||||
*/
|
||||
interface IStructureNavigate<T> extends IStructureElement<T> {
|
||||
interface IStructureNavigate extends IStructureElement<Object> {
|
||||
|
||||
@Override
|
||||
default boolean check(T t, World world, int x, int y, int z) {
|
||||
default boolean check(Object t, World world, int x, int y, int z) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
default boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
default boolean spawnHint(Object t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
default boolean placeBlock(Object t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
default PlaceResult survivalPlaceBlock(Object t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
return PlaceResult.SKIP;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
default PlaceResult survivalPlaceBlock(Object t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return PlaceResult.SKIP;
|
||||
}
|
||||
|
||||
@@ -21,40 +21,42 @@ public interface IStructureWalker<T> {
|
||||
* @param c The relative C position of the element in structure-local coordinates (pre transform)
|
||||
* @return False to stop iterating, true to continue.
|
||||
*/
|
||||
boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
|
||||
boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, int c);
|
||||
|
||||
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
|
||||
default boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
return new IStructureWalker<>() {
|
||||
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
return walker.visit(element, world, x, y, z, a, b, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
public boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a,
|
||||
int b, int c) {
|
||||
return walker.visit(element, world, x, y, z, a, b, c);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
return new IStructureWalker<>() {
|
||||
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
return walker.visit(element, world, x, y, z, a, b, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
public boolean blockNotLoaded(IStructureElement<? super T> element, World world, int x, int y, int z, int a,
|
||||
int b, int c) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,14 +13,14 @@ import net.minecraft.world.World;
|
||||
|
||||
class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
|
||||
private Function<T, IStructureElement<T>> to;
|
||||
private IStructureElement<T> elem;
|
||||
private Function<? super T, ? extends IStructureElement<? super T>> to;
|
||||
private IStructureElement<? super T> elem;
|
||||
|
||||
public LazyStructureElement(Function<T, IStructureElement<T>> to) {
|
||||
public LazyStructureElement(Function<? super T, ? extends IStructureElement<? super T>> to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
private IStructureElement<T> get(T t) {
|
||||
private IStructureElement<? super T> get(T t) {
|
||||
if (to != null) {
|
||||
elem = to.apply(t);
|
||||
to = null;
|
||||
|
||||
@@ -5,12 +5,8 @@ import static com.gtnewhorizon.structurelib.structure.StructureUtility.notAir;
|
||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.step;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
@@ -20,31 +16,21 @@ import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
|
||||
public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<Character, IStructureElement<? super T>> elements;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, IStructureElement<T>[]> structures;
|
||||
private final Map<String, OccupiedSpace> occupiedSpaces;
|
||||
private final Map<String, IChannelDefinition> channelDefinitions;
|
||||
private final Set<String> explicitChannels;
|
||||
private final Collection<IChannelDefinition> channelDefinitionValues;
|
||||
private final Collection<IChannelDefinition> explicitChannelDefinitions;
|
||||
private final Map<String, IStructureElement<? super T>[]> structures;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
|
||||
public static <B> Builder<B> builder() {
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
|
||||
Map<String, IStructureElement<T>[]> structures, Map<String, OccupiedSpace> occupiedSpaces,
|
||||
Map<String, IChannelDefinition> channelDefinitions, Set<String> explicitChannels) {
|
||||
private StructureDefinition(Map<Character, IStructureElement<? super T>> elements, Map<String, String> shapes,
|
||||
Map<String, IStructureElement<? super T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
|
||||
this.elements = elements;
|
||||
this.shapes = shapes;
|
||||
this.structures = structures;
|
||||
this.occupiedSpaces = occupiedSpaces;
|
||||
this.channelDefinitions = Collections.unmodifiableMap(new HashMap<>(channelDefinitions));
|
||||
this.explicitChannels = Collections.unmodifiableSet(new HashSet<>(explicitChannels));
|
||||
this.channelDefinitionValues = Collections.unmodifiableCollection(this.channelDefinitions.values());
|
||||
this.explicitChannelDefinitions = Collections.unmodifiableList(
|
||||
this.explicitChannels.stream().map(this.channelDefinitions::get).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static class Builder<T> {
|
||||
@@ -54,22 +40,18 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
private static final char C = '\uC000';
|
||||
private char d = '\uD000';
|
||||
private final Map<Vec3Impl, Character> navigates;
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<Character, IStructureElement<? super T>> elements;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, OccupiedSpace> occupiedSpaces;
|
||||
private final Map<String, IChannelDefinition> channels;
|
||||
private final Set<String> explicitChannels;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
|
||||
private Builder() {
|
||||
navigates = new HashMap<>();
|
||||
elements = new HashMap<>();
|
||||
shapes = new HashMap<>();
|
||||
occupiedSpaces = new HashMap<>();
|
||||
channels = new HashMap<>();
|
||||
explicitChannels = new HashSet<>();
|
||||
}
|
||||
|
||||
public Map<Character, IStructureElement<T>> getElements() {
|
||||
public Map<Character, IStructureElement<? super T>> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
@@ -113,19 +95,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
builder.setLength(builder.length() - 1);
|
||||
}
|
||||
int sizeA = 0;
|
||||
int sizeB = 0;
|
||||
long occupiedCount = 0;
|
||||
for (String[] strings : structurePiece) {
|
||||
sizeB = Math.max(sizeB, strings.length);
|
||||
for (String string : strings) {
|
||||
sizeA = Math.max(sizeA, string.length());
|
||||
for (int i = 0; i < string.length(); i++) {
|
||||
if (string.charAt(i) != ' ') occupiedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
OccupiedSpace occupiedSpace = new OccupiedSpace(sizeA, sizeB, structurePiece.length, occupiedCount);
|
||||
Set<Vec3Impl> occupiedSpace = new HashSet<>();
|
||||
// these track the global current location
|
||||
int aa = 0, bb = 0, cc = 0;
|
||||
// these track the vec3 towards next meaningful element
|
||||
@@ -139,7 +109,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
} else if (ch == '~') {
|
||||
builder.setCharAt(i, A);
|
||||
ch = A;
|
||||
occupiedSpace.add(aa, bb, cc);
|
||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
||||
}
|
||||
if (ch == A) {
|
||||
aa++;
|
||||
@@ -165,13 +135,13 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
addElement(navigate, step(vec3));
|
||||
}
|
||||
builder.setCharAt(i - 1, navigate);
|
||||
occupiedSpace.add(aa, bb, cc);
|
||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
||||
aa++;
|
||||
a = 0;
|
||||
b = 0;
|
||||
c = 0;
|
||||
} else {
|
||||
occupiedSpace.add(aa, bb, cc);
|
||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
||||
aa++;
|
||||
}
|
||||
}
|
||||
@@ -211,66 +181,27 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
* @deprecated use the unboxed version
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
|
||||
if (elements.putIfAbsent(name, structurePiece) == null) registerElementChannel(structurePiece);
|
||||
public Builder<T> addElement(Character name, IStructureElement<? super T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
|
||||
if (elements.putIfAbsent(name, structurePiece) == null) registerElementChannel(structurePiece);
|
||||
public Builder<T> addElement(char name, IStructureElement<? super T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> addChannel(IChannelDefinition definition) {
|
||||
validateChannelDefinition(definition);
|
||||
channels.put(definition.getChannel(), definition);
|
||||
explicitChannels.add(definition.getChannel());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> addChannel(String channel, int minimumValue, int maximumValue) {
|
||||
return addChannel(IChannelDefinition.bounded(channel, minimumValue, maximumValue));
|
||||
}
|
||||
|
||||
private void registerElementChannel(IStructureElement<T> structurePiece) {
|
||||
for (String channel : structurePiece.getChannels()) {
|
||||
validateChannelName(channel);
|
||||
if (!explicitChannels.contains(channel)) {
|
||||
channels.putIfAbsent(channel, IChannelDefinition.unbounded(channel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateChannelDefinition(IChannelDefinition definition) {
|
||||
if (definition == null) throw new IllegalArgumentException("Channel definition cannot be null");
|
||||
validateChannelName(definition.getChannel());
|
||||
if (definition.getMinimumValue() < 1 || definition.getMaximumValue() < definition.getMinimumValue()) {
|
||||
throw new IllegalArgumentException("Invalid channel definition values");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateChannelName(String channel) {
|
||||
if (channel == null || channel.isEmpty() || !channel.toLowerCase(Locale.ROOT).equals(channel)) {
|
||||
throw new IllegalArgumentException("Channel name must be non-empty and lower case");
|
||||
}
|
||||
}
|
||||
|
||||
public IStructureDefinition<T> build() {
|
||||
for (IStructureElement<T> element : elements.values()) {
|
||||
registerElementChannel(element);
|
||||
}
|
||||
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
|
||||
Map<String, IStructureElement<? super T>[]> structures = compileStructureMap();
|
||||
return new StructureDefinition<>(
|
||||
new HashMap<>(elements),
|
||||
new HashMap<>(shapes),
|
||||
structures,
|
||||
occupiedSpaces,
|
||||
channels,
|
||||
explicitChannels);
|
||||
occupiedSpaces);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, IStructureElement<T>[]> compileElementSetMap() {
|
||||
private Map<String, IStructureElement<? super T>[]> compileElementSetMap() {
|
||||
Set<Integer> missing = findMissing();
|
||||
if (missing.isEmpty()) {
|
||||
return shapes.entrySet().stream().collect(
|
||||
@@ -286,7 +217,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, IStructureElement<T>[]> compileStructureMap() {
|
||||
private Map<String, IStructureElement<? super T>[]> compileStructureMap() {
|
||||
Set<Integer> missing = findMissing();
|
||||
if (missing.isEmpty()) {
|
||||
return shapes.entrySet().stream().collect(
|
||||
@@ -307,7 +238,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Character, IStructureElement<T>> getElements() {
|
||||
public Map<Character, IStructureElement<? super T>> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
@@ -315,77 +246,21 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public Map<String, IStructureElement<T>[]> getStructures() {
|
||||
public Map<String, IStructureElement<? super T>[]> getStructures() {
|
||||
return structures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<IChannelDefinition> getChannelDefinitions() {
|
||||
return channelDefinitionValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<IChannelDefinition> getExplicitChannelDefinitions() {
|
||||
return explicitChannelDefinitions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChannelDefinition getChannelDefinition(String channel) {
|
||||
return channelDefinitions.get(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IStructureElement<T>[] getStructureFor(String name) {
|
||||
IStructureElement<T>[] elements = structures.get(name);
|
||||
public IStructureElement<? super T>[] getStructureFor(String name) {
|
||||
IStructureElement<? super T>[] elements = structures.get(name);
|
||||
if (elements == null) throw new NoSuchElementException(name);
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) {
|
||||
OccupiedSpace occupiedSpace = occupiedSpaces.get(name);
|
||||
Set<Vec3Impl> occupiedSpace = occupiedSpaces.get(name);
|
||||
if (occupiedSpace == null) throw new NoSuchElementException(name);
|
||||
return occupiedSpace.contains(offsetA, offsetB, offsetC);
|
||||
}
|
||||
|
||||
private static class OccupiedSpace {
|
||||
|
||||
private final int sizeA;
|
||||
private final int sizeB;
|
||||
private final int sizeC;
|
||||
private final BitSet occupied;
|
||||
private final Set<Vec3Impl> sparseOccupied;
|
||||
|
||||
private OccupiedSpace(int sizeA, int sizeB, int sizeC, long occupiedCount) {
|
||||
this.sizeA = sizeA;
|
||||
this.sizeB = sizeB;
|
||||
this.sizeC = sizeC;
|
||||
long volume = (long) sizeA * sizeB * sizeC;
|
||||
if (volume <= Integer.MAX_VALUE && volume <= Math.max(1024L, occupiedCount * 256L)) {
|
||||
occupied = new BitSet((int) volume);
|
||||
sparseOccupied = null;
|
||||
} else {
|
||||
occupied = null;
|
||||
sparseOccupied = new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
private void add(int a, int b, int c) {
|
||||
if (occupied != null) {
|
||||
occupied.set(index(a, b, c));
|
||||
} else {
|
||||
sparseOccupied.add(new Vec3Impl(a, b, c));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean contains(int a, int b, int c) {
|
||||
if (a < 0 || b < 0 || c < 0 || a >= sizeA || b >= sizeB || c >= sizeC) return false;
|
||||
if (occupied != null) return occupied.get(index(a, b, c));
|
||||
return sparseOccupied.contains(new Vec3Impl(a, b, c));
|
||||
}
|
||||
|
||||
private int index(int a, int b, int c) {
|
||||
return (c * sizeB + b) * sizeA + a;
|
||||
}
|
||||
return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -37,7 +37,7 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
public boolean visit(IStructureElement<? super T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
env.offsetABC[0] = a;
|
||||
env.offsetABC[1] = b;
|
||||
env.offsetABC[2] = c;
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -174,37 +174,6 @@ StructureLib API documentation.
|
||||
master channel.
|
||||
</p>
|
||||
|
||||
<h3>Metadata-only channels</h3>
|
||||
|
||||
<p>
|
||||
A channel does not need to represent a block or an element in a structure shape. Structure definitions can declare
|
||||
metadata-only channels for values such as a widget selection or a variable structure height. These declarations are
|
||||
independent of <code>addElement</code> and do not require a fake block or a shape character.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
IStructureDefinition.<Controller>builder()
|
||||
.addChannel("height", 3, 100)
|
||||
.addChannel("length", 4, 50)
|
||||
.addChannel(IChannelDefinition.unbounded("widget"))
|
||||
.addElement('A', ...)
|
||||
.build();
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Channel values are still stored as integers in the trigger item's <code>channels</code> NBT compound. The
|
||||
generic channel GUI has no active structure definition, so it edits integers and does not impose one
|
||||
structure's range on every other structure using the same channel name. Structure-aware code reads values through
|
||||
<code>IStructureDefinition#getChannelData</code>, which uses the definition's default when the subchannel is absent
|
||||
and then applies that definition's local normalization policy.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Existing elements wrapped with <code>StructureUtility.withChannel</code> are discovered automatically. An explicit
|
||||
channel declaration supplies its local range and normalization policy for that structure. The same channel name may
|
||||
therefore use different ranges in different structure definitions.
|
||||
</p>
|
||||
|
||||
<h3>why is this called channel?</h3>
|
||||
|
||||
Just give me a better name and I'd do the renaming.
|
||||
|
||||
@@ -44,11 +44,7 @@ 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
|
||||
|
||||
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
|
||||
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.tooltip.indicator_dnd=Drop into GUI to select this value for this channel
|
||||
|
||||
structurelib.autoplace.built_stat=Built %s blocks
|
||||
structurelib.autoplace.complete=§aThis multiblock has completed!
|
||||
|
||||
Reference in new issue
Block a user