mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-12 00:07:27 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9672d40b34 | ||
|
|
4fdea65537 | ||
|
|
89057ebb2d | ||
|
|
d829ae466b |
No files matched your search
@@ -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);
|
||||||
|
|||||||
+11
-4
@@ -38,7 +38,9 @@ public class ChannelDataAccessor {
|
|||||||
* If given subchannel does not exist, masterStack may or may not be returned to save performance. Code within
|
* 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.
|
* StructureLib can depend on this behavior, you cannot.
|
||||||
*
|
*
|
||||||
* @param masterStack trigger item. can be the return value of another {@link #withChannel(ItemStack, String)} call
|
* @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 channel subchannel to use. will use current master channel data instead if this channel does not exist
|
* @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.
|
* Note: all channel identifiers are supposed to be lower case and not empty.
|
||||||
* @return a trigger item with new master channel
|
* @return a trigger item with new master channel
|
||||||
@@ -107,13 +109,13 @@ public class ChannelDataAccessor {
|
|||||||
*
|
*
|
||||||
* @param masterStack trigger stack to check
|
* @param masterStack trigger stack to check
|
||||||
* @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty.
|
* @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty.
|
||||||
* @param data subchannel data. should always be a positive value
|
* @param data positive subchannel data
|
||||||
*/
|
*/
|
||||||
public static void setChannelData(ItemStack masterStack, String channel, int data) {
|
public static void setChannelData(ItemStack masterStack, String channel, int data) {
|
||||||
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
|
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))
|
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||||
if (data <= 0) throw new IllegalArgumentException();
|
|
||||||
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
|
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
|
||||||
NBTTagCompound main = masterStack.stackTagCompound;
|
NBTTagCompound main = masterStack.stackTagCompound;
|
||||||
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
|
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
|
||||||
@@ -146,7 +148,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class CommandConfigureChannels extends CommandBase {
|
|||||||
public String getCommandUsage(ICommandSender p_71518_1_) {
|
public String getCommandUsage(ICommandSender p_71518_1_) {
|
||||||
return "/sl_channel <wipe/getall>\n" + "/sl_channel <get/unset> <channel name>\n"
|
return "/sl_channel <wipe/getall>\n" + "/sl_channel <get/unset> <channel name>\n"
|
||||||
+ "/sl_channel <set> <channel name> <value>\n"
|
+ "/sl_channel <set> <channel name> <value>\n"
|
||||||
+ "<channel name> can have space in it. <value> must be positive integer";
|
+ "<channel name> can have space in it. Values below 1 clear the channel";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -108,11 +108,14 @@ public class CommandConfigureChannels extends CommandBase {
|
|||||||
throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
|
throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value <= 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
|
if (value < 1) {
|
||||||
|
ChannelDataAccessor.unsetChannelData(heldItem, channel);
|
||||||
ChannelDataAccessor.setChannelData(heldItem, channel, value);
|
} else {
|
||||||
|
ChannelDataAccessor.setChannelData(heldItem, channel, value);
|
||||||
|
}
|
||||||
player.inventoryContainer.detectAndSendChanges();
|
player.inventoryContainer.detectAndSendChanges();
|
||||||
sender.addChatMessage(new ChatComponentText(channel + " value: " + value));
|
sender.addChatMessage(
|
||||||
|
new ChatComponentText(value < 1 ? channel + " no value" : channel + " value: " + value));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+64
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
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,6 +3,8 @@ package com.gtnewhorizon.structurelib.structure;
|
|||||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
|
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
|
||||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
|
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import net.minecraft.entity.player.EntityPlayer;
|
import net.minecraft.entity.player.EntityPlayer;
|
||||||
@@ -56,6 +58,48 @@ import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
|||||||
*/
|
*/
|
||||||
public interface IStructureDefinition<T> {
|
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
|
* Used internally
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
|||||||
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -33,6 +34,15 @@ public interface IStructureElement<T> {
|
|||||||
|
|
||||||
boolean check(T t, World world, int x, int y, int z);
|
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
|
* 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
|
* about incorrectly placed blocks given a controller and trigger item. If couldBeValid(...) == false for a set of
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ import static com.gtnewhorizon.structurelib.structure.StructureUtility.notAir;
|
|||||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.step;
|
import static com.gtnewhorizon.structurelib.structure.StructureUtility.step;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -19,18 +23,28 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
private final Map<Character, IStructureElement<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<T>[]> structures;
|
private final Map<String, IStructureElement<T>[]> structures;
|
||||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
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;
|
||||||
|
|
||||||
public static <B> Builder<B> builder() {
|
public static <B> Builder<B> builder() {
|
||||||
return new Builder<>();
|
return new Builder<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
|
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
|
||||||
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
|
Map<String, IStructureElement<T>[]> structures, Map<String, OccupiedSpace> occupiedSpaces,
|
||||||
|
Map<String, IChannelDefinition> channelDefinitions, Set<String> explicitChannels) {
|
||||||
this.elements = elements;
|
this.elements = elements;
|
||||||
this.shapes = shapes;
|
this.shapes = shapes;
|
||||||
this.structures = structures;
|
this.structures = structures;
|
||||||
this.occupiedSpaces = occupiedSpaces;
|
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> {
|
public static class Builder<T> {
|
||||||
@@ -42,13 +56,17 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
private final Map<Vec3Impl, Character> navigates;
|
private final Map<Vec3Impl, Character> navigates;
|
||||||
private final Map<Character, IStructureElement<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, OccupiedSpace> occupiedSpaces;
|
||||||
|
private final Map<String, IChannelDefinition> channels;
|
||||||
|
private final Set<String> explicitChannels;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
navigates = new HashMap<>();
|
navigates = new HashMap<>();
|
||||||
elements = new HashMap<>();
|
elements = new HashMap<>();
|
||||||
shapes = new HashMap<>();
|
shapes = new HashMap<>();
|
||||||
occupiedSpaces = new HashMap<>();
|
occupiedSpaces = new HashMap<>();
|
||||||
|
channels = new HashMap<>();
|
||||||
|
explicitChannels = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Character, IStructureElement<T>> getElements() {
|
public Map<Character, IStructureElement<T>> getElements() {
|
||||||
@@ -95,7 +113,19 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
}
|
}
|
||||||
builder.setLength(builder.length() - 1);
|
builder.setLength(builder.length() - 1);
|
||||||
}
|
}
|
||||||
Set<Vec3Impl> occupiedSpace = new HashSet<>();
|
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);
|
||||||
// these track the global current location
|
// these track the global current location
|
||||||
int aa = 0, bb = 0, cc = 0;
|
int aa = 0, bb = 0, cc = 0;
|
||||||
// these track the vec3 towards next meaningful element
|
// these track the vec3 towards next meaningful element
|
||||||
@@ -109,7 +139,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
} else if (ch == '~') {
|
} else if (ch == '~') {
|
||||||
builder.setCharAt(i, A);
|
builder.setCharAt(i, A);
|
||||||
ch = A;
|
ch = A;
|
||||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
occupiedSpace.add(aa, bb, cc);
|
||||||
}
|
}
|
||||||
if (ch == A) {
|
if (ch == A) {
|
||||||
aa++;
|
aa++;
|
||||||
@@ -135,13 +165,13 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
addElement(navigate, step(vec3));
|
addElement(navigate, step(vec3));
|
||||||
}
|
}
|
||||||
builder.setCharAt(i - 1, navigate);
|
builder.setCharAt(i - 1, navigate);
|
||||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
occupiedSpace.add(aa, bb, cc);
|
||||||
aa++;
|
aa++;
|
||||||
a = 0;
|
a = 0;
|
||||||
b = 0;
|
b = 0;
|
||||||
c = 0;
|
c = 0;
|
||||||
} else {
|
} else {
|
||||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
occupiedSpace.add(aa, bb, cc);
|
||||||
aa++;
|
aa++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,22 +212,61 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
|
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
|
||||||
elements.putIfAbsent(name, structurePiece);
|
if (elements.putIfAbsent(name, structurePiece) == null) registerElementChannel(structurePiece);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
|
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
|
||||||
elements.putIfAbsent(name, structurePiece);
|
if (elements.putIfAbsent(name, structurePiece) == null) registerElementChannel(structurePiece);
|
||||||
return this;
|
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() {
|
public IStructureDefinition<T> build() {
|
||||||
|
for (IStructureElement<T> element : elements.values()) {
|
||||||
|
registerElementChannel(element);
|
||||||
|
}
|
||||||
Map<String, IStructureElement<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),
|
||||||
structures,
|
structures,
|
||||||
occupiedSpaces);
|
occupiedSpaces,
|
||||||
|
channels,
|
||||||
|
explicitChannels);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -250,6 +319,21 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
return structures;
|
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
|
@Override
|
||||||
public IStructureElement<T>[] getStructureFor(String name) {
|
public IStructureElement<T>[] getStructureFor(String name) {
|
||||||
IStructureElement<T>[] elements = structures.get(name);
|
IStructureElement<T>[] elements = structures.get(name);
|
||||||
@@ -259,8 +343,49 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) {
|
public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) {
|
||||||
Set<Vec3Impl> occupiedSpace = occupiedSpaces.get(name);
|
OccupiedSpace occupiedSpace = occupiedSpaces.get(name);
|
||||||
if (occupiedSpace == null) throw new NoSuchElementException(name);
|
if (occupiedSpace == null) throw new NoSuchElementException(name);
|
||||||
return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,12 +4,14 @@ import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
|||||||
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
||||||
import static java.lang.Integer.MIN_VALUE;
|
import static java.lang.Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -30,6 +32,7 @@ import javax.annotation.Nullable;
|
|||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.entity.player.EntityPlayer;
|
import net.minecraft.entity.player.EntityPlayer;
|
||||||
import net.minecraft.entity.player.EntityPlayerMP;
|
import net.minecraft.entity.player.EntityPlayerMP;
|
||||||
|
import net.minecraft.event.HoverEvent;
|
||||||
import net.minecraft.init.Blocks;
|
import net.minecraft.init.Blocks;
|
||||||
import net.minecraft.item.Item;
|
import net.minecraft.item.Item;
|
||||||
import net.minecraft.item.ItemBlock;
|
import net.minecraft.item.ItemBlock;
|
||||||
@@ -38,10 +41,9 @@ import net.minecraft.nbt.NBTTagCompound;
|
|||||||
import net.minecraft.tileentity.TileEntity;
|
import net.minecraft.tileentity.TileEntity;
|
||||||
import net.minecraft.util.ChatComponentText;
|
import net.minecraft.util.ChatComponentText;
|
||||||
import net.minecraft.util.ChatComponentTranslation;
|
import net.minecraft.util.ChatComponentTranslation;
|
||||||
import net.minecraft.util.ChatStyle;
|
|
||||||
import net.minecraft.util.EnumChatFormatting;
|
|
||||||
import net.minecraft.util.IChatComponent;
|
import net.minecraft.util.IChatComponent;
|
||||||
import net.minecraft.util.IIcon;
|
import net.minecraft.util.IIcon;
|
||||||
|
import net.minecraft.util.StatCollector;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
import net.minecraftforge.common.util.ForgeDirection;
|
||||||
|
|
||||||
@@ -317,6 +319,25 @@ public class StructureUtility {
|
|||||||
return survivalPlaceBlock(block, meta, world, x, y, z, s, (EntityPlayer) actor, chatter);
|
return survivalPlaceBlock(block, meta, world, x, y, z, s, (EntityPlayer) actor, chatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a chat component naming the given stack, so that the receiving client localizes it.
|
||||||
|
* <p>
|
||||||
|
* Falls back to the server side display name for stacks whose name is not a plain translation key, e.g. items that
|
||||||
|
* assemble their name in {@link Item#getItemStackDisplayName(ItemStack)}.
|
||||||
|
*/
|
||||||
|
private static IChatComponent getStackChatName(ItemStack stack) {
|
||||||
|
String key = stack.getUnlocalizedName() + ".name";
|
||||||
|
if (!StatCollector.canTranslate(key)) return stack.func_151000_E();
|
||||||
|
IChatComponent name = new ChatComponentText("[").appendSibling(new ChatComponentTranslation(key))
|
||||||
|
.appendText("]");
|
||||||
|
NBTTagCompound tag = new NBTTagCompound();
|
||||||
|
stack.writeToNBT(tag);
|
||||||
|
name.getChatStyle()
|
||||||
|
.setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new ChatComponentText(tag.toString())))
|
||||||
|
.setColor(stack.getRarity().rarityColor);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a helper method for implementing
|
* This is a helper method for implementing
|
||||||
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
|
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
|
||||||
@@ -341,7 +362,7 @@ public class StructureUtility {
|
|||||||
ItemStack stack = new ItemStack(itemBlock, 1, itemMeta);
|
ItemStack stack = new ItemStack(itemBlock, 1, itemMeta);
|
||||||
if (!s.takeOne(stack, true)) {
|
if (!s.takeOne(stack, true)) {
|
||||||
if (chatter != null) chatter.accept(
|
if (chatter != null) chatter.accept(
|
||||||
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
|
new ChatComponentTranslation("structurelib.autoplace.missing_block", getStackChatName(stack)));
|
||||||
return PlaceResult.REJECT;
|
return PlaceResult.REJECT;
|
||||||
}
|
}
|
||||||
if (block instanceof ICustomBlockSetting blockCustom) {
|
if (block instanceof ICustomBlockSetting blockCustom) {
|
||||||
@@ -447,7 +468,7 @@ public class StructureUtility {
|
|||||||
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
|
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
|
||||||
if (!assumeStackPresent && !s.takeOne(stack, true)) {
|
if (!assumeStackPresent && !s.takeOne(stack, true)) {
|
||||||
if (chatter != null) chatter.accept(
|
if (chatter != null) chatter.accept(
|
||||||
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
|
new ChatComponentTranslation("structurelib.autoplace.missing_block", getStackChatName(stack)));
|
||||||
return PlaceResult.REJECT;
|
return PlaceResult.REJECT;
|
||||||
}
|
}
|
||||||
if (!stack.copy().tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f))
|
if (!stack.copy().tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f))
|
||||||
@@ -708,7 +729,7 @@ public class StructureUtility {
|
|||||||
Function<T, TIER> getter) {
|
Function<T, TIER> getter) {
|
||||||
List<String> descriptions = null;
|
List<String> descriptions = null;
|
||||||
if (allKnownTiers != null) {
|
if (allKnownTiers != null) {
|
||||||
descriptions = new java.util.ArrayList<>();
|
descriptions = new ArrayList<>();
|
||||||
for (Pair<Block, Integer> tier : allKnownTiers) {
|
for (Pair<Block, Integer> tier : allKnownTiers) {
|
||||||
Item item = Item.getItemFromBlock(tier.getLeft());
|
Item item = Item.getItemFromBlock(tier.getLeft());
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
@@ -1484,10 +1505,10 @@ public class StructureUtility {
|
|||||||
}
|
}
|
||||||
for (ItemStack stack : e.getStacks()) {
|
for (ItemStack stack : e.getStacks()) {
|
||||||
if (!source.takeOne(stack, true)) {
|
if (!source.takeOne(stack, true)) {
|
||||||
IChatComponent name = new ChatComponentText(stack.getDisplayName());
|
env.getChatter().accept(
|
||||||
name.setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW));
|
new ChatComponentTranslation(
|
||||||
env.getChatter()
|
"structurelib.autoplace.missing_block",
|
||||||
.accept(new ChatComponentTranslation("structurelib.autoplace.missing_block", name));
|
getStackChatName(stack)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return StructureUtility.survivalPlaceBlock(
|
return StructureUtility.survivalPlaceBlock(
|
||||||
@@ -2973,7 +2994,14 @@ public class StructureUtility {
|
|||||||
* See channels.md in docs folder
|
* See channels.md in docs folder
|
||||||
*/
|
*/
|
||||||
public static <T> IStructureElement<T> withChannel(String channel, IStructureElement<T> backing) {
|
public static <T> IStructureElement<T> withChannel(String channel, IStructureElement<T> backing) {
|
||||||
return new IStructureElement<T>() {
|
return new IStructureElement<>() {
|
||||||
|
|
||||||
|
private final Collection<String> channels = channelsFor(backing, channel);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getChannels() {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
return backing.check(t, world, x, y, z);
|
return backing.check(t, world, x, y, z);
|
||||||
@@ -3046,6 +3074,13 @@ public class StructureUtility {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T> Collection<String> channelsFor(IStructureElement<T> backing, String channel) {
|
||||||
|
Set<String> channels = new LinkedHashSet<>();
|
||||||
|
channels.add(channel);
|
||||||
|
channels.addAll(backing.getChannels());
|
||||||
|
return Collections.unmodifiableSet(channels);
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -174,6 +174,37 @@ StructureLib API documentation.
|
|||||||
master channel.
|
master channel.
|
||||||
</p>
|
</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>
|
<h3>why is this called channel?</h3>
|
||||||
|
|
||||||
Just give me a better name and I'd do the renaming.
|
Just give me a better name and I'd do the renaming.
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
Reference in new issue
Block a user