From 1282e525daa27df1a26a6cf112aa0c0daf538eb2 Mon Sep 17 00:00:00 2001 From: Boblet Date: Tue, 8 Apr 2025 16:37:39 +0200 Subject: [PATCH] bocci the cock --- .../container/ContainerPneumoTube.java | 5 - .../com/hbm/inventory/gui/GUIPneumoTube.java | 29 +++-- .../network/TileEntityPneumoTube.java | 48 +++++++-- .../networkproviders/PneumaticNetwork.java | 102 +++++++++++++++++- src/main/java/com/hbm/util/BobMathUtil.java | 21 +++- .../gui/storage/gui_pneumatic_pipe.png | Bin 2783 -> 2823 bytes 6 files changed, 174 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/hbm/inventory/container/ContainerPneumoTube.java b/src/main/java/com/hbm/inventory/container/ContainerPneumoTube.java index 5d44904da..9796028ea 100644 --- a/src/main/java/com/hbm/inventory/container/ContainerPneumoTube.java +++ b/src/main/java/com/hbm/inventory/container/ContainerPneumoTube.java @@ -1,7 +1,6 @@ package com.hbm.inventory.container; import com.hbm.inventory.SlotPattern; -import com.hbm.inventory.SlotUpgrade; import com.hbm.tileentity.network.TileEntityPneumoTube; import net.minecraft.entity.player.EntityPlayer; @@ -23,10 +22,6 @@ public class ContainerPneumoTube extends ContainerBase { } } - //upgrades - this.addSlotToContainer(new SlotUpgrade(tube, 15, 152, 23)); - this.addSlotToContainer(new SlotUpgrade(tube, 16, 152, 47)); - playerInv(invPlayer, 8, 103, 161); } diff --git a/src/main/java/com/hbm/inventory/gui/GUIPneumoTube.java b/src/main/java/com/hbm/inventory/gui/GUIPneumoTube.java index 0247ffeb6..3eed4ef98 100644 --- a/src/main/java/com/hbm/inventory/gui/GUIPneumoTube.java +++ b/src/main/java/com/hbm/inventory/gui/GUIPneumoTube.java @@ -58,24 +58,18 @@ public class GUIPneumoTube extends GuiInfoContainer { protected void mouseClicked(int x, int y, int i) { super.mouseClicked(x, y, i); - if(checkClick(x, y, 7, 52, 18, 18)) { + click(x, y, 7, 52, 18, 18, "redstone"); + click(x, y, 6, 36, 20, 8, "pressure"); + click(x, y, 128, 30, 14, 26, "whitelist"); + click(x, y, 151, 16, 18, 18, "receive"); + click(x, y, 151, 52, 18, 18, "send"); + } + + public void click(int x, int y, int left, int top, int sizeX, int sizeY, String name) { + if(checkClick(x, y, left, top, sizeX, sizeY)) { mc.getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F)); NBTTagCompound data = new NBTTagCompound(); - data.setBoolean("redstone", true); - PacketDispatcher.wrapper.sendToServer(new NBTControlPacket(data, tube.xCoord, tube.yCoord, tube.zCoord)); - } - - if(checkClick(x, y, 6, 36, 20, 8)) { - mc.getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F)); - NBTTagCompound data = new NBTTagCompound(); - data.setBoolean("pressure", true); - PacketDispatcher.wrapper.sendToServer(new NBTControlPacket(data, tube.xCoord, tube.yCoord, tube.zCoord)); - } - - if(checkClick(x, y, 128, 30, 14, 26)) { - mc.getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F)); - NBTTagCompound data = new NBTTagCompound(); - data.setBoolean("whitelist", true); + data.setBoolean(name, true); PacketDispatcher.wrapper.sendToServer(new NBTControlPacket(data, tube.xCoord, tube.yCoord, tube.zCoord)); } } @@ -101,6 +95,9 @@ public class GUIPneumoTube extends GuiInfoContainer { } else { drawTexturedModalRect(guiLeft + 139, guiTop + 47, 176, 0, 3, 6); } + + drawTexturedModalRect(guiLeft + 151, guiTop + 16, 197, 18 * tube.receiveOrder, 18, 18); + drawTexturedModalRect(guiLeft + 151, guiTop + 52, 215, 18 * tube.sendOrder, 18, 18); drawTexturedModalRect(guiLeft + 6 + 4 * (tube.compair.getPressure() - 1), guiTop + 36, 179, 18, 4, 8); GaugeUtil.drawSmoothGauge(guiLeft + 16, guiTop + 25, this.zLevel, (double) tube.compair.getFill() / (double) tube.compair.getMaxFill(), 5, 2, 1, 0xCA6C43, 0xAB4223); diff --git a/src/main/java/com/hbm/tileentity/network/TileEntityPneumoTube.java b/src/main/java/com/hbm/tileentity/network/TileEntityPneumoTube.java index b6f89b8f4..a232dd65b 100644 --- a/src/main/java/com/hbm/tileentity/network/TileEntityPneumoTube.java +++ b/src/main/java/com/hbm/tileentity/network/TileEntityPneumoTube.java @@ -12,6 +12,7 @@ import com.hbm.tileentity.IGUIProvider; import com.hbm.tileentity.TileEntityMachineBase; import com.hbm.uninos.GenNode; import com.hbm.uninos.UniNodespace; +import com.hbm.uninos.networkproviders.PneumaticNetwork; import com.hbm.uninos.networkproviders.PneumaticNetworkProvider; import com.hbm.util.Compat; import com.hbm.util.EnumUtil; @@ -43,13 +44,16 @@ public class TileEntityPneumoTube extends TileEntityMachineBase implements IGUIP public boolean whitelist = false; public boolean redstone = false; + public byte sendOrder = 0; + public byte receiveOrder = 0; + public boolean didSend = false; public FluidTank compair; protected PneumaticNode node; public TileEntityPneumoTube() { - super(17); + super(15); this.compair = new FluidTank(Fluids.AIR, 4_000).withPressure(1); } @@ -80,26 +84,46 @@ public class TileEntityPneumoTube extends TileEntityMachineBase implements IGUIP } if(this.isCompressor()) { + + int randTime = Math.abs((int) (worldObj.getTotalWorldTime() + this.getIdentifier(xCoord, yCoord, zCoord))); + if(worldObj.getTotalWorldTime() % 10 == 0) for(ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) { if(dir != this.insertionDir && dir != this.ejectionDir) { this.trySubscribe(compair.getTankType(), worldObj, xCoord + dir.offsetX, yCoord + dir.offsetY, zCoord + dir.offsetZ, dir); } } - if(worldObj.getTotalWorldTime() % 40 == 0 && this.compair.getFill() > 0) { - //this.compair.setFill(0); - //worldObj.playSoundEffect(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5, "hbm:weapon.reload.tubeFwoomp", 1.0F, 1.0F); + if(randTime % 40 == 0 && didSend) { + worldObj.playSoundEffect(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5, "hbm:weapon.reload.tubeFwoomp", 0.5F, 0.9F + worldObj.rand.nextFloat() * 0.2F); + } + + if(randTime % 5 == 0 && this.node != null && !this.node.expired && this.node.net != null && this.compair.getFill() >= 50) { + TileEntity sendFrom = Compat.getTileStandard(worldObj, xCoord + insertionDir.offsetX, yCoord + insertionDir.offsetY, zCoord + insertionDir.offsetZ); + + if(sendFrom instanceof IInventory) { + PneumaticNetwork net = node.net; + + if(net.send((IInventory) sendFrom, this, this.insertionDir.getOpposite(), sendOrder, receiveOrder)) { + this.didSend = true; + this.compair.setFill(this.compair.getFill() - 50); + } + } } } if(this.isEndpoint() && this.node != null && this.node.net != null && worldObj.getTotalWorldTime() % 10 == 0) { TileEntity tile = Compat.getTileStandard(worldObj, xCoord + this.ejectionDir.offsetX, yCoord + this.ejectionDir.offsetY, zCoord + this.ejectionDir.offsetZ); - if(tile instanceof IInventory) this.node.net.addReceiver((IInventory) tile); + if(tile instanceof IInventory) this.node.net.addReceiver((IInventory) tile, this.ejectionDir); } this.networkPackNT(15); } } + + // tactfully copy pasted from BlockPos + public static int getIdentifier(int x, int y, int z) { + return (y + z * 27644437) * 27644437 + x; + } @Override public long getReceiverSpeed(FluidType type, int pressure) { @@ -130,6 +154,8 @@ public class TileEntityPneumoTube extends TileEntityMachineBase implements IGUIP super.serialize(buf); buf.writeBoolean(redstone); buf.writeBoolean(whitelist); + buf.writeByte(sendOrder); + buf.writeByte(receiveOrder); pattern.serialize(buf); compair.serialize(buf); } @@ -139,6 +165,8 @@ public class TileEntityPneumoTube extends TileEntityMachineBase implements IGUIP super.deserialize(buf); this.redstone = buf.readBoolean(); this.whitelist = buf.readBoolean(); + this.sendOrder = buf.readByte(); + this.receiveOrder = buf.readByte(); pattern.deserialize(buf); compair.deserialize(buf); } @@ -215,6 +243,14 @@ public class TileEntityPneumoTube extends TileEntityMachineBase implements IGUIP if(pressure > 5) pressure = 1; this.compair.withPressure(pressure); } + if(data.hasKey("send")) { + this.sendOrder++; + if(this.sendOrder > 2) this.sendOrder = 0; + } + if(data.hasKey("receive")) { + this.receiveOrder++; + if(this.receiveOrder > 1) this.receiveOrder = 0; + } this.markDirty(); } @@ -225,7 +261,7 @@ public class TileEntityPneumoTube extends TileEntityMachineBase implements IGUIP @Override public FluidTank[] getAllTanks() { return new FluidTank[] {compair}; } @Override public FluidTank[] getReceivingTanks() { return new FluidTank[] {compair}; } - public static class PneumaticNode extends GenNode { + public static class PneumaticNode extends GenNode { public PneumaticNode(BlockPos... positions) { super(PneumaticNetworkProvider.THE_PROVIDER, positions); diff --git a/src/main/java/com/hbm/uninos/networkproviders/PneumaticNetwork.java b/src/main/java/com/hbm/uninos/networkproviders/PneumaticNetwork.java index 4fdc5bb81..eb9d851f4 100644 --- a/src/main/java/com/hbm/uninos/networkproviders/PneumaticNetwork.java +++ b/src/main/java/com/hbm/uninos/networkproviders/PneumaticNetwork.java @@ -1,11 +1,107 @@ package com.hbm.uninos.networkproviders; -import com.hbm.tileentity.network.TileEntityPneumoTube.PneumaticNode; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +import com.hbm.tileentity.network.TileEntityPneumoTube; import com.hbm.uninos.NodeNet; +import com.hbm.util.BobMathUtil; +import com.hbm.util.Tuple.Pair; import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.ISidedInventory; +import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.common.util.ForgeDirection; -public class PneumaticNetwork extends NodeNet { +public class PneumaticNetwork extends NodeNet { - @Override public void update() { } + public static final byte SEND_FIRST = 0; + public static final byte SEND_LAST = 1; + public static final byte SEND_RANDOM = 2; + public static final byte RECEIVE_ROBIN = 0; + public static final byte RECEIVE_RANDOM = 1; + + public int nextReceiver = 0; + + protected static int timeout = 1_000; + + public HashMap> receivers = new HashMap(); + + public void addReceiver(IInventory inventory, ForgeDirection pipeDir) { + receivers.put(inventory, new Pair(pipeDir, System.currentTimeMillis())); + } + + @Override public void update() { + + long timestamp = System.currentTimeMillis(); + receivers.entrySet().removeIf(x -> { return (timestamp - x.getValue().getValue() > timeout) || NodeNet.isBadLink(x.getKey()); }); + } + + public boolean send(IInventory source, TileEntityPneumoTube tube, ForgeDirection dir, int sendOrder, int receiveOrder) { + + if(receivers.isEmpty()) return false; + + int[] slotAccess; + + if(source instanceof ISidedInventory) { + ISidedInventory sided = (ISidedInventory) source; + slotAccess = sided.getAccessibleSlotsFromSide(dir.ordinal()); + } else { + slotAccess = new int[source.getSizeInventory()]; + for(int i = 0; i < source.getSizeInventory(); i++) slotAccess[i] = i; + } + + if(sendOrder == SEND_LAST) BobMathUtil.reverseIntArray(slotAccess); + if(sendOrder == SEND_RANDOM) BobMathUtil.shuffleIntArray(slotAccess); + + nextReceiver++; + + ReceiverComparator comparator = new ReceiverComparator(tube); + List>> receiverList = new ArrayList(receivers.size()); + receiverList.addAll(receivers.entrySet()); + receiverList.sort(comparator); + + int index = nextReceiver % receivers.size(); + Entry> chosenReceiverEntry = receiverList.get(index); + + //TBI - the painful part + + return false; + } + + public static class ReceiverComparator implements Comparator>> { + + private TileEntityPneumoTube origin; + + public ReceiverComparator(TileEntityPneumoTube origin) { + this.origin = origin; + } + + @Override + public int compare(Entry> o1, Entry> o2) { + + TileEntity tile1 = o1.getKey() instanceof TileEntity ? (TileEntity) o1.getKey() : null; + TileEntity tile2 = o2.getKey() instanceof TileEntity ? (TileEntity) o2.getKey() : null; + + //prioritize actual TileEntities + if(tile1 == null && tile2 != null) return 1; + if(tile1 != null && tile2 == null) return -1; + if(tile1 == null && tile2 == null) return 0; + + //calculate distances from origin + int dist1 = (tile1.xCoord - origin.xCoord) * (tile1.xCoord - origin.xCoord) + (tile1.yCoord - origin.yCoord) * (tile1.yCoord - origin.yCoord) + (tile1.zCoord - origin.zCoord) * (tile1.zCoord - origin.zCoord); + int dist2 = (tile2.xCoord - origin.xCoord) * (tile2.xCoord - origin.xCoord) + (tile2.yCoord - origin.yCoord) * (tile2.yCoord - origin.yCoord) + (tile2.zCoord - origin.zCoord) * (tile2.zCoord - origin.zCoord); + + //tier-breaker: use hash value instead + if(dist1 == dist2) { + return TileEntityPneumoTube.getIdentifier(tile1.xCoord, tile1.yCoord, tile1.zCoord) - TileEntityPneumoTube.getIdentifier(tile2.xCoord, tile2.yCoord, tile2.zCoord); + } + + //no tie? return difference of the distances + return dist1 - dist2; + } + } } diff --git a/src/main/java/com/hbm/util/BobMathUtil.java b/src/main/java/com/hbm/util/BobMathUtil.java index 029ba1a7e..df6cc8f44 100644 --- a/src/main/java/com/hbm/util/BobMathUtil.java +++ b/src/main/java/com/hbm/util/BobMathUtil.java @@ -260,7 +260,26 @@ public class BobMathUtil { public static int[] collectionToIntArray(Collection in, ToIntFunction mapper) { return Arrays.stream(in.toArray()).mapToInt(mapper).toArray(); - } + } + + public static void shuffleIntArray(int[] array) { + Random rand = new Random(); + for(int i = array.length - 1; i > 0; i--) { + int r = rand.nextInt(i + 1); + int temp = array[r]; + array[r] = array[i]; + array[i] = temp; + } + } + + public static void reverseIntArray(int[] array) { + int len = array.length; + for(int i = 0; i < len / 2; i++) { + int temp = array[i]; + array[i] = array[len - 1 - i]; + array[len - 1 - i] = temp; + } + } /** Soft peak sine */ public static double sps(double x) { diff --git a/src/main/resources/assets/hbm/textures/gui/storage/gui_pneumatic_pipe.png b/src/main/resources/assets/hbm/textures/gui/storage/gui_pneumatic_pipe.png index 658703030f8f2ddb9c133590c7e6d56f9bb3c6e8..cd192079291ea8a12d5e2014c54279187704c614 100644 GIT binary patch literal 2823 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