From 377108166d92c521f33b9f7e6d2312dafb4d88a6 Mon Sep 17 00:00:00 2001 From: Boblet Date: Wed, 21 Sep 2022 16:43:34 +0200 Subject: [PATCH] foundry utility block base code, more crucible stuff --- .../java/api/hbm/block/ICrucibleAcceptor.java | 25 +++++++ .../container/ContainerCrucible.java | 11 +-- .../com/hbm/inventory/gui/GUICrucible.java | 53 ++++++++++++- .../hbm/inventory/gui/GUICrystallizer.java | 1 - .../hbm/inventory/gui/GUIFurnaceSteel.java | 1 - .../machine/TileEntityCrucible.java | 28 ++++++- .../machine/TileEntityFoundryBase.java | 70 ++++++++++++++++++ src/main/java/com/hbm/util/InventoryUtil.java | 9 +++ .../gui/processing/gui_crystallizer_alt.png | Bin 2912 -> 2635 bytes 9 files changed, 187 insertions(+), 11 deletions(-) create mode 100644 src/main/java/api/hbm/block/ICrucibleAcceptor.java create mode 100644 src/main/java/com/hbm/tileentity/machine/TileEntityFoundryBase.java diff --git a/src/main/java/api/hbm/block/ICrucibleAcceptor.java b/src/main/java/api/hbm/block/ICrucibleAcceptor.java new file mode 100644 index 000000000..3d7724972 --- /dev/null +++ b/src/main/java/api/hbm/block/ICrucibleAcceptor.java @@ -0,0 +1,25 @@ +package api.hbm.block; + +import com.hbm.inventory.material.Mats.MaterialStack; + +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +public interface ICrucibleAcceptor { + + /* + * Pouring: The metal leaves the channel/crucible and usually (but not always) falls down. The additional double coords give a more precise impact location. + * Also useful for entities like large crucibles since they are filled from the top. + */ + public boolean canAcceptPour(World world, int x, int y, int z, double dX, double dY, double dZ, ForgeDirection side, MaterialStack stack); + public MaterialStack canAcceptPartialPour(World world, int x, int y, int z, double dX, double dY, double dZ, ForgeDirection side, MaterialStack stack); + public void pour(World world, int x, int y, int z, double dX, double dY, double dZ, ForgeDirection side, MaterialStack stack); + + /* + * Flowing: The "safe" transfer of metal using a channel or other means, usually from block to block and usually horizontally (but not necessarily). + * May also be used for entities like minecarts that could be loaded from the side. + */ + public boolean canAcceptFlow(World world, int x, int y, int z, ForgeDirection side, MaterialStack stack); + public MaterialStack canAcceptPartialFlow(World world, int x, int y, int z, ForgeDirection side, MaterialStack stack); + public void flow(World world, int x, int y, int z, ForgeDirection side, MaterialStack stack); +} diff --git a/src/main/java/com/hbm/inventory/container/ContainerCrucible.java b/src/main/java/com/hbm/inventory/container/ContainerCrucible.java index 3f9fb375f..7d7074a47 100644 --- a/src/main/java/com/hbm/inventory/container/ContainerCrucible.java +++ b/src/main/java/com/hbm/inventory/container/ContainerCrucible.java @@ -1,6 +1,7 @@ package com.hbm.inventory.container; import com.hbm.tileentity.machine.TileEntityCrucible; +import com.hbm.util.InventoryUtil; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.InventoryPlayer; @@ -15,16 +16,16 @@ public class ContainerCrucible extends Container { public ContainerCrucible(InventoryPlayer invPlayer, TileEntityCrucible crucible) { this.crucible = crucible; + //template + this.addSlotToContainer(new Slot(crucible, 0, 107, 81)); + //input for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { - this.addSlotToContainer(new Slot(crucible, j + i * 3, 107 + j * 18, 18 + i * 18)); + this.addSlotToContainer(new Slot(crucible, j + i * 3 + 1, 107 + j * 18, 18 + i * 18)); } } - //template - this.addSlotToContainer(new Slot(crucible, 9, 107, 81)); - for(int i = 0; i < 3; i++) { for(int j = 0; j < 9; j++) { this.addSlotToContainer(new Slot(invPlayer, j + i * 9 + 9, 8 + j * 18, 132 + i * 18)); @@ -52,7 +53,7 @@ public class ContainerCrucible extends Container { slot.onSlotChange(originalStack, stack); - } else if(!this.mergeItemStack(originalStack, 0, 10, false)) { + } else if(!InventoryUtil.mergeItemStack(this.inventorySlots, originalStack, 0, 10, false)) { return null; } diff --git a/src/main/java/com/hbm/inventory/gui/GUICrucible.java b/src/main/java/com/hbm/inventory/gui/GUICrucible.java index a41d3c199..959394c0e 100644 --- a/src/main/java/com/hbm/inventory/gui/GUICrucible.java +++ b/src/main/java/com/hbm/inventory/gui/GUICrucible.java @@ -1,17 +1,22 @@ package com.hbm.inventory.gui; +import java.awt.Color; +import java.util.ArrayList; import java.util.List; import org.lwjgl.opengl.GL11; import com.hbm.inventory.container.ContainerCrucible; +import com.hbm.inventory.material.Mats; import com.hbm.inventory.material.Mats.MaterialStack; +import com.hbm.inventory.material.NTMMaterial.SmeltingBehavior; import com.hbm.lib.RefStrings; import com.hbm.tileentity.machine.TileEntityCrucible; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.ResourceLocation; public class GUICrucible extends GuiInfoContainer { @@ -30,6 +35,12 @@ public class GUICrucible extends GuiInfoContainer { @Override public void drawScreen(int x, int y, float interp) { super.drawScreen(x, y, interp); + + drawStackInfo(crucible.wasteStack, x, y, 16, 17); + drawStackInfo(crucible.recipeStack, x, y, 61, 17); + + this.drawCustomInfoStat(x, y, guiLeft + 125, guiTop + 81, 34, 7, x, y, new String[] { String.format("%,d", crucible.progress) + " / " + String.format("%,d", crucible.processTime) + "TU" }); + this.drawCustomInfoStat(x, y, guiLeft + 125, guiTop + 90, 34, 7, x, y, new String[] { String.format("%,d", crucible.heat) + " / " + String.format("%,d", crucible.maxHeat) + "TU" }); } @Override @@ -51,11 +62,47 @@ public class GUICrucible extends GuiInfoContainer { int hGauge = crucible.heat * 33 / crucible.maxHeat; if(hGauge > 0) drawTexturedModalRect(guiLeft + 126, guiTop + 91, 176, 5, hGauge, 5); - if(!crucible.recipeStack.isEmpty()) drawStack(crucible.recipeStack, 62, 97); - if(!crucible.wasteStack.isEmpty()) drawStack(crucible.wasteStack, 17, 97); + if(!crucible.recipeStack.isEmpty()) drawStack(crucible.recipeStack, crucible.recipeCapacity, 62, 97); + if(!crucible.wasteStack.isEmpty()) drawStack(crucible.wasteStack, crucible.wasteCapacity, 17, 97); } - protected void drawStack(List stack, int x, int y) { + protected void drawStackInfo(List stack, int mouseX, int mouseY, int x, int y) { + List list = new ArrayList(); + + if(stack.isEmpty()) + list.add(EnumChatFormatting.RED + "Empty"); + + for(MaterialStack sta : stack) { + list.add(EnumChatFormatting.YELLOW + sta.material.names[0] + ": " + Mats.formatAmount(sta.amount)); + } + + this.drawCustomInfoStat(mouseX, mouseY, guiLeft + x, guiTop + y, 36, 81, mouseX, mouseY, list); + } + + protected void drawStack(List stack, int capacity, int x, int y) { + + if(stack.isEmpty()) return; + + int lastHeight = 0; + int lastQuant = 0; + + for(MaterialStack sta : stack) { + + int targetHeight = (lastQuant + sta.amount) * 79 / capacity; + + if(lastHeight == targetHeight) continue; //skip draw calls that would be 0 pixels high + + int offset = sta.material.smeltable == SmeltingBehavior.ADDITIVE ? 34 : 0; //additives use a differnt texture + + Color color = new Color(sta.material.moltenColor); + GL11.glColor3f(color.getRed() / 255F, color.getGreen() / 255F, color.getBlue() / 255F); + drawTexturedModalRect(guiLeft + x, guiTop + y - targetHeight, 176 + offset, 89 - targetHeight, 34, targetHeight - lastHeight); + + lastQuant += sta.amount; + lastHeight = targetHeight; + } + + GL11.glColor3f(255, 255, 255); } } diff --git a/src/main/java/com/hbm/inventory/gui/GUICrystallizer.java b/src/main/java/com/hbm/inventory/gui/GUICrystallizer.java index 6f1a1d418..e9b03cea8 100644 --- a/src/main/java/com/hbm/inventory/gui/GUICrystallizer.java +++ b/src/main/java/com/hbm/inventory/gui/GUICrystallizer.java @@ -3,7 +3,6 @@ package com.hbm.inventory.gui; import org.lwjgl.opengl.GL11; import com.hbm.inventory.container.ContainerCrystallizer; -import com.hbm.inventory.fluid.tank.FluidTank; import com.hbm.lib.RefStrings; import com.hbm.tileentity.machine.TileEntityMachineCrystallizer; import com.hbm.util.I18nUtil; diff --git a/src/main/java/com/hbm/inventory/gui/GUIFurnaceSteel.java b/src/main/java/com/hbm/inventory/gui/GUIFurnaceSteel.java index cad0be5e6..a7dcb6eb8 100644 --- a/src/main/java/com/hbm/inventory/gui/GUIFurnaceSteel.java +++ b/src/main/java/com/hbm/inventory/gui/GUIFurnaceSteel.java @@ -32,7 +32,6 @@ public class GUIFurnaceSteel extends GuiInfoContainer { this.drawCustomInfoStat(x, y, guiLeft + 53, guiTop + 17 + 18 * i, 70, 7, x, y, new String[] { String.format("%,d", furnace.progress[i]) + " / " + String.format("%,d", furnace.processTime) + "TU" }); this.drawCustomInfoStat(x, y, guiLeft + 53, guiTop + 26 + 18 * i, 70, 7, x, y, new String[] { "Bonus: " + furnace.bonus[i] + "%" }); } - //this.drawCustomInfoStat(x, y, guiLeft + 52, guiTop + 44, 71, 7, x, y, new String[] { (furnace.burnTime / 20) + "s" }); this.drawCustomInfoStat(x, y, guiLeft + 151, guiTop + 18, 9, 50, x, y, new String[] { String.format("%,d", furnace.heat) + " / " + String.format("%,d", furnace.maxHeat) + "TU" }); } diff --git a/src/main/java/com/hbm/tileentity/machine/TileEntityCrucible.java b/src/main/java/com/hbm/tileentity/machine/TileEntityCrucible.java index fd5ce804d..d16a8a914 100644 --- a/src/main/java/com/hbm/tileentity/machine/TileEntityCrucible.java +++ b/src/main/java/com/hbm/tileentity/machine/TileEntityCrucible.java @@ -19,6 +19,7 @@ import api.hbm.tile.IHeatSource; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.Container; import net.minecraft.item.ItemStack; @@ -60,6 +61,31 @@ public class TileEntityCrucible extends TileEntityMachineBase implements IGUIPro if(!worldObj.isRemote) { tryPullHeat(); + if(worldObj.getTotalWorldTime() % 5 == 0) { + List list = worldObj.getEntitiesWithinAABB(EntityItem.class, AxisAlignedBB.getBoundingBox(xCoord - 0.5, yCoord + 0.5, zCoord - 0.5, xCoord + 1.5, yCoord + 1, zCoord + 1.5)); + + for(EntityItem item : list) { + ItemStack stack = item.getEntityItem(); + if(this.isItemSmeltable(stack)) { + + for(int i = 1; i < 10; i++) { + if(slots[i] == null) { + + if(stack.stackSize == 1) { + slots[i] = stack.copy(); + } else { + slots[i] = stack.copy(); + slots[i].stackSize = 1; + stack.stackSize--; + } + + this.markChanged(); + } + } + } + } + } + if(!trySmelt()) { this.progress = 0; } @@ -90,7 +116,7 @@ public class TileEntityCrucible extends TileEntityMachineBase implements IGUIPro int[] was = nbt.getIntArray("was"); for(int i = 0; i < was.length / 2; i++) { - recipeStack.add(new MaterialStack(Mats.matById.get(was[i * 2]), was[i * 2 + 1])); + wasteStack.add(new MaterialStack(Mats.matById.get(was[i * 2]), was[i * 2 + 1])); } this.progress = nbt.getInteger("progress"); diff --git a/src/main/java/com/hbm/tileentity/machine/TileEntityFoundryBase.java b/src/main/java/com/hbm/tileentity/machine/TileEntityFoundryBase.java new file mode 100644 index 000000000..5b4aa28ae --- /dev/null +++ b/src/main/java/com/hbm/tileentity/machine/TileEntityFoundryBase.java @@ -0,0 +1,70 @@ +package com.hbm.tileentity.machine; + +import com.hbm.inventory.material.Mats; +import com.hbm.inventory.material.NTMMaterial; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.Packet; +import net.minecraft.network.play.server.S35PacketUpdateTileEntity; +import net.minecraft.tileentity.TileEntity; + +/** + * Base class for all foundry channel type blocks - channels, casts, basins, tanks, etc. + * Foundry type blocks can only hold one type at a time and usually either store or move it around. + * @author hbm + * + */ +public abstract class TileEntityFoundryBase extends TileEntity { + + public NTMMaterial type; + protected NTMMaterial lastType; + public int amount; + protected int lastAmount; + + @Override + public void updateEntity() { + + if(worldObj.isRemote) { + + if(this.lastType != this.type || this.lastAmount != this.amount) { + worldObj.markBlockForUpdate(xCoord, yCoord, zCoord); + this.lastType = this.type; + this.lastAmount = this.amount; + } + } + } + + @Override + public Packet getDescriptionPacket() { + NBTTagCompound nbt = new NBTTagCompound(); + this.writeToNBT(nbt); + return new S35PacketUpdateTileEntity(this.xCoord, this.yCoord, this.zCoord, 0, nbt); + } + + @Override + public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) { + this.readFromNBT(pkt.func_148857_g()); + } + + @Override + public void readFromNBT(NBTTagCompound nbt) { + super.readFromNBT(nbt); + this.type = Mats.matById.get(nbt.getInteger("type")); + this.amount = nbt.getInteger("amount"); + } + + @Override + public void writeToNBT(NBTTagCompound nbt) { + super.writeToNBT(nbt); + + if(this.type == null) + nbt.setInteger("type", -1); + else + nbt.setInteger("type", this.type.id); + + nbt.setInteger("amount", this.amount); + } + + public abstract int getCapacity(); +} diff --git a/src/main/java/com/hbm/util/InventoryUtil.java b/src/main/java/com/hbm/util/InventoryUtil.java index 8132c728a..492ce36f2 100644 --- a/src/main/java/com/hbm/util/InventoryUtil.java +++ b/src/main/java/com/hbm/util/InventoryUtil.java @@ -448,6 +448,15 @@ public class InventoryUtil { return true; } + /** + * A fixed re-implementation of the original Container.mergeItemStack that repects stack size and slot restrictions. + * @param slots + * @param stack + * @param start + * @param end + * @param reverse + * @return + */ public static boolean mergeItemStack(List slots, ItemStack stack, int start, int end, boolean reverse) { boolean success = false; diff --git a/src/main/resources/assets/hbm/textures/gui/processing/gui_crystallizer_alt.png b/src/main/resources/assets/hbm/textures/gui/processing/gui_crystallizer_alt.png index a427d1652dff3da7237ecb8c85b4b880acc396ee..ae0884951f3620178de4a4653b06c3bf13375093 100644 GIT binary patch literal 2635 zcmb7Fdpy)x8$UCKnPl9<&ZMEskQ8}P-z>?b zlC;Pwa>=c=ED4P#*$ieXK)k1$5P~mnb+%T}jV0>=^AdfuZaK!D}z4_td+ZWH_&|?#Y zXTI@2@2x?_o*@0cP2c0_&jsGdc6m+HGmnVg+ant*0_EGP4Z9E*gE)6QjD3@wa6UWs zg;1mkMXj>KIek$jwhMe~zLiqYoWJL2^{V9EGAw&*%jI1{t~QL$tnjDi*J7CKFP2+g z3zr=~Om@}}y^@yRmVDp3s{AuwBJJc%3d%i=5^ruAQjB6W9 zm(Ck%L_3bOx3~9}22_2UG*}t>qj~5^gevm#LUJ0=T4m~Yys~0ebVsb7wqf!Yzc!U# zt%?y&^@nH_u!A1+W_&Vtg=2=Y5R`N2>FIVD-M3S#&i;yw)U`PP1AhWMEU8(8&b()?Q+X(4!nBx7B|+bDXG)uHJ2v-RnYP^ z=z6AUVDpi%lFG zmdFSlw81a=WT=`18B!uSe;yklFt58;Oa4r#N#n-+gJ%~Fq;RGlO zakF0^$qFR8M4J9u;}1+=XDf;kZokMAew)5KDbs;w&&;vA1?bQ;3hr z5HbDIXbbw*dy^D!oKEaoPo~(M&rC0rVf{&l@;ec}{IArYGT8gZh46*qo$KXbSnIbwgR$ki!E93c{s6)zop$z)2{dw0;3EAO({k6Vp4t0D3S3 z>urr1zzL$PBRx|e=%516@@_#Mq7{u|L**;nZjte$}Vt4QR0Zti-UZE3;Ig#Zoma{ z*;A%V6rl?zs|iQJo7#FLTY@hy3k^YppE|-Nk~nstV(c^=8;1kFW87 zJDKWwfPh{Y5RS%G@~s+UQ*c%_r&mc-*G`xq)|_>7ZF{yl1EhXWXfW$F3OobX%KKI& z`c0T0&KLP129OYi1(Cq?rM_QSaLlLo9_uA{xJMBx(0B|#yKgMjeRE5Bxc|bfW9f~O zZBLO&PDfLOC4D-j72k6bcyY-#j*gBmu2Z;Un7%)fM zs5bgyy7Ow)&?}Fq0tz`{JJ=)IIC!`HN<_p*Gt_Uk+v69ohk%{_cbkSlZkEiNql!HOf*vyT4|tA;K_TEsTsGlXz4;J-uA>sT}jr3J)dp1?CD>iC{A~Z>LqE-I3=$To+sO@L9L$R9SI`z4rSM z4~Zl&4*rd|1R~=PBcf~M^;?Lnq*}3I1MRUi`Hk~eg4k^K z(#G<{fKq0z;`0l|RNcfJFQo+s9Y&Ii9&>kBf4@6MH?i4Ar75rejoV+aUCyf| znKEA{2ezq!JI095f$-gTEE7uYqwirJy&L0Ms1#Z|qs{~@G45e{b_IVf+1BZL$fu+Y z%NGGt$rNJ;XA~{{?*CYx=iY*xWvKxe#>x{Ud*>76GkB%Olfj97V6PLPG@(8R-ac)Z zRj5;(4>K_C4l}qCUW@woDs>F{Kj!(@YT9Kv5i>Z=%{TNkRWmvOk-sp^TeD*+Y==5p pZR4MLmLS{MqEGeoS);KJ6X?sT>>Wa?6mD%ian#wN#LoMNe*i^SiFE(~ literal 2912 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

Ov z)s1k{lP6}z`u}b2B)wGJmoYctW#B(?gieJE%`|Ju7moD%_T)DT#n2tc(4 zATPs#mEga+MRUmNS9Tx!hhq1wWILjGsR8nz86~!;ove?#X5C1Sg^|~j9KjDsjP5q| z=={$s{Y}I~%b{EEQHou<3t}j9>FiEfihRa|ECxF`wYcL@g9&Wrc8f8Znd(rr0=nnt@