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