package gregtech.api.multitileentity.multiblock.base; import static com.google.common.math.LongMath.log2; import static gregtech.api.enums.GTValues.B; import static gregtech.api.enums.Textures.BlockIcons.FLUID_IN_SIGN; import static gregtech.api.enums.Textures.BlockIcons.FLUID_OUT_SIGN; import static gregtech.api.enums.Textures.BlockIcons.ITEM_IN_SIGN; import static gregtech.api.enums.Textures.BlockIcons.ITEM_OUT_SIGN; import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ENERGY_IN_MULTI; import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ENERGY_OUT_MULTI; import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_PIPE_IN; import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_PIPE_OUT; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ChunkCoordinates; import net.minecraft.util.StatCollector; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; import com.gtnewhorizons.modularui.api.screen.ModularWindow.Builder; import com.gtnewhorizons.modularui.api.screen.UIBuildContext; import com.gtnewhorizons.modularui.common.widget.DrawableWidget; import gregtech.api.enums.GTValues.NBT; import gregtech.api.enums.InventoryType; import gregtech.api.fluid.GTFluidTank; import gregtech.api.gui.GUIHost; import gregtech.api.gui.GUIProvider; import gregtech.api.interfaces.ITexture; import gregtech.api.logic.FluidInventoryLogic; import gregtech.api.logic.ItemInventoryLogic; import gregtech.api.logic.NullPowerLogic; import gregtech.api.logic.PowerLogic; import gregtech.api.logic.interfaces.PowerLogicHost; import gregtech.api.multitileentity.MultiTileEntityRegistry; import gregtech.api.multitileentity.WeakTargetRef; import gregtech.api.multitileentity.base.NonTickableMultiTileEntity; import gregtech.api.multitileentity.enums.MultiTileCasingPurpose; import gregtech.api.multitileentity.interfaces.IMultiBlockController; import gregtech.api.multitileentity.interfaces.IMultiBlockPart; import gregtech.api.render.TextureFactory; import gregtech.api.util.GTUtility; import gregtech.common.covers.CoverInfo; import gregtech.common.gui.PartGUIProvider; import mcp.mobius.waila.api.IWailaConfigHandler; import mcp.mobius.waila.api.IWailaDataAccessor; public abstract class MultiBlockPart extends NonTickableMultiTileEntity implements IMultiBlockPart, PowerLogicHost, GUIHost { public static final int NOTHING = 0, ENERGY_IN = B[0], ENERGY_OUT = B[1], FLUID_IN = B[2], FLUID_OUT = B[3], ITEM_IN = B[4], ITEM_OUT = B[5]; protected final List BASIC_MODES = new ArrayList<>( Arrays.asList(NOTHING, ENERGY_IN, ENERGY_OUT, FLUID_IN, FLUID_OUT, ITEM_IN, ITEM_OUT)); protected Set registeredPurposes = new HashSet<>(); protected final WeakTargetRef controller = new WeakTargetRef<>( IMultiBlockController.class, false); protected int allowedModes = NOTHING; // BITMASK - Modes allowed for this part protected int mode = 0; // Mode selected for this part protected UUID lockedInventory; protected int mLockedInventoryIndex = 0; protected GTFluidTank configurationTank = new GTFluidTank(); @Nonnull protected final GUIProvider guiProvider = createGUIProvider(); /** * What Part Tier is this part? All Basic Casings are Tier 1, and will allow: Energy, Item, Fluid input/output. Some * of the more advanced modes can be set to require a higher tier part. */ public int getPartTier() { return 1; } @Override public UUID getLockedInventory() { return lockedInventory; } public void setTarget(IMultiBlockController newController, int aAllowedModes) { final IMultiBlockController currentController = getTarget(false); if (currentController != null && currentController != newController) { for (MultiTileCasingPurpose purpose : registeredPurposes) { unregisterPurpose(purpose); } } allowedModes = aAllowedModes; if (newController != currentController) { registerCovers(newController); registerPurposes(); } } protected void registerPurpose(MultiTileCasingPurpose purpose) { IMultiBlockController target = getTarget(false); if (target != null) { target.registerCaseWithPurpose(purpose, this); registeredPurposes.add(purpose); } } protected void unregisterPurpose(MultiTileCasingPurpose purpose) { IMultiBlockController target = getTarget(false); if (target != null) { target.unregisterCaseWithPurpose(purpose, this); } registeredPurposes.remove(purpose); } @Override protected void addDebugInfo(EntityPlayer aPlayer, int aLogLevel, ArrayList tList) { final IMultiBlockController controller = getTarget(false); if (controller != null) { tList.add("Has controller"); } else { tList.add("No Controller"); } tList.add("Casing Mode: " + getModeName(mode)); } @Override public void getWailaBody(ItemStack itemStack, List currentTip, IWailaDataAccessor accessor, IWailaConfigHandler config) { super.getWailaBody(itemStack, currentTip, accessor, config); currentTip.add(String.format("Mode: %s", getModeName(mode))); if (modeSelected(FLUID_OUT)) { if (configurationTank != null && configurationTank.get() != null) { currentTip.add( String.format( "Locked to: %s", configurationTank.get() .getLocalizedName())); } else { currentTip.add("Locked to: Nothing"); } } } public IMultiBlockController getTarget(boolean aCheckValidity) { final IMultiBlockController res = controller.get(); if (res != null && aCheckValidity) { return res.checkStructure(false) ? res : null; } return res; } public void registerCovers(IMultiBlockController controller) { for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { final CoverInfo coverInfo = getCoverInfoAtSide(side); if (coverInfo.isValid() && coverInfo.getTickRate() > 0) { controller.registerCoveredPartOnSide(side, this); } } } protected void registerPurposes() { for (MultiTileCasingPurpose purpose : registeredPurposes) { registerPurpose(purpose); } } @Override public void setCoverItemAtSide(ForgeDirection side, ItemStack aCover) { super.setCoverItemAtSide(side, aCover); // TODO: Filter on tickable covers final IMultiBlockController tTarget = getTarget(true); if (tTarget == null) { return; } final CoverInfo coverInfo = getCoverInfoAtSide(side); if (coverInfo.isValid() && coverInfo.getTickRate() > 0) { tTarget.registerCoveredPartOnSide(side, this); } } public void unregisterCovers(IMultiBlockController controller) { for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { if (getCoverInfoAtSide(side).isValid()) { controller.unregisterCoveredPartOnSide(side, this); } } } @Override public boolean dropCover(ForgeDirection side, ForgeDirection droppedSide, boolean aForced) { final boolean res = super.dropCover(side, droppedSide, aForced); final IMultiBlockController tTarget = getTarget(true); if (tTarget != null) { tTarget.unregisterCoveredPartOnSide(side, this); } return res; } @Override public void readMultiTileNBT(NBTTagCompound aNBT) { if (aNBT.hasKey(NBT.ALLOWED_MODES)) allowedModes = aNBT.getInteger(NBT.ALLOWED_MODES); if (aNBT.hasKey(NBT.MODE)) setMode(aNBT.getByte(NBT.MODE)); if (aNBT.hasKey(NBT.TARGET)) { controller .setPosition(aNBT.getInteger(NBT.TARGET_X), aNBT.getShort(NBT.TARGET_Y), aNBT.getInteger(NBT.TARGET_Z)); controller.setWorld(worldObj); } if (aNBT.hasKey(NBT.LOCKED_INVENTORY)) { lockedInventory = UUID.fromString(aNBT.getString(NBT.LOCKED_INVENTORY)); } if (aNBT.hasKey(NBT.LOCKED_INVENTORY_INDEX)) { mLockedInventoryIndex = aNBT.getInteger(NBT.LOCKED_INVENTORY_INDEX); } if (aNBT.hasKey(NBT.LOCKED_FLUID)) { configurationTank.readFromNBT(aNBT, NBT.LOCKED_FLUID); } if (modeSelected(ITEM_OUT)) { registeredPurposes.add(MultiTileCasingPurpose.ItemOutput); } if (modeSelected(FLUID_OUT)) { registeredPurposes.add(MultiTileCasingPurpose.FluidOutput); } } @Override public void writeMultiTileNBT(NBTTagCompound nbt) { if (allowedModes != NOTHING) nbt.setInteger(NBT.ALLOWED_MODES, allowedModes); if (mode != 0) nbt.setInteger(NBT.MODE, mode); final ChunkCoordinates pos = controller.getPosition(); if (pos.posY >= 0) { // Valid position nbt.setBoolean(NBT.TARGET, true); nbt.setInteger(NBT.TARGET_X, pos.posX); nbt.setShort(NBT.TARGET_Y, (short) pos.posY); nbt.setInteger(NBT.TARGET_Z, pos.posZ); } if (lockedInventory != null) { nbt.setString(NBT.LOCKED_INVENTORY, lockedInventory.toString()); } if (mLockedInventoryIndex != 0) { nbt.setInteger(NBT.LOCKED_INVENTORY_INDEX, mLockedInventoryIndex); } configurationTank.writeToNBT(nbt, NBT.LOCKED_FLUID); } @Override public void setLockedInventoryIndex(int index) { mLockedInventoryIndex = index; } @Override public int getLockedInventoryIndex() { return mLockedInventoryIndex; } @Override public ChunkCoordinates getTargetPos() { return controller.getPosition(); } @Override public void setMode(int mode) { if (this.mode == mode) return; if (modeSelected(FLUID_OUT)) { unregisterPurpose(MultiTileCasingPurpose.FluidOutput); } if (modeSelected(ITEM_OUT)) { unregisterPurpose(MultiTileCasingPurpose.ItemOutput); } this.mode = mode; if (modeSelected(FLUID_OUT)) { registerPurpose(MultiTileCasingPurpose.FluidOutput); } if (modeSelected(ITEM_OUT)) { registerPurpose(MultiTileCasingPurpose.ItemOutput); } } @Override public int getMode() { return mode; } @Override public int getAllowedModes() { return allowedModes; } @Override public void setAllowedModes(int aAllowedModes) { allowedModes = aAllowedModes; } /** * True if `mode` is one of the allowed modes */ public boolean hasMode(int mode) { // This is not sent to the client return (allowedModes & mode) != 0; } /** * Returns true if the part has any of the modes provided, and that mode is the currently selected mode */ public boolean modeSelected(int... modes) { for (int mode : modes) { if (hasMode(mode) && this.mode == getModeOrdinal(mode)) return true; } return false; } @Override public boolean onBlockBroken() { final IMultiBlockController tTarget = getTarget(false); if (tTarget != null) { unregisterCovers(tTarget); tTarget.onStructureChange(); } return false; } @Override public void onBlockAdded() { for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { final TileEntity te = getTileEntityAtSide(side); if (te instanceof MultiBlockPart part) { final IMultiBlockController tController = part.getTarget(false); if (tController != null) tController.onStructureChange(); } else if (te instanceof IMultiBlockController blockController) { blockController.onStructureChange(); } } } @Override public ITexture getTexture(ForgeDirection side) { ITexture texture = super.getTexture(side); if (mode != 0 && side == facing) { if (mode == getModeOrdinal(ITEM_IN)) { return TextureFactory.of( texture, TextureFactory.of(OVERLAY_PIPE_IN), TextureFactory.of(ITEM_IN_SIGN), getCoverTexture(side)); } if (mode == getModeOrdinal(ITEM_OUT)) { return TextureFactory.of( texture, TextureFactory.of(OVERLAY_PIPE_OUT), TextureFactory.of(ITEM_OUT_SIGN), getCoverTexture(side)); } if (mode == getModeOrdinal(FLUID_IN)) { return TextureFactory.of( texture, TextureFactory.of(OVERLAY_PIPE_IN), TextureFactory.of(FLUID_IN_SIGN), getCoverTexture(side)); } if (mode == getModeOrdinal(FLUID_OUT)) { return TextureFactory.of( texture, TextureFactory.of(OVERLAY_PIPE_OUT), TextureFactory.of(FLUID_OUT_SIGN), getCoverTexture(side)); } if (mode == getModeOrdinal(ENERGY_IN)) { return TextureFactory.of(texture, TextureFactory.of(OVERLAY_ENERGY_IN_MULTI), getCoverTexture(side)); } if (mode == getModeOrdinal(ENERGY_OUT)) { return TextureFactory.of(texture, TextureFactory.of(OVERLAY_ENERGY_OUT_MULTI), getCoverTexture(side)); } } return TextureFactory.of(texture, getCoverTexture(side)); } protected String getModeName(int aMode) { if (aMode == NOTHING) return "Nothing"; if (aMode == getModeOrdinal(ITEM_IN)) return "Item Input"; if (aMode == getModeOrdinal(ITEM_OUT)) return "Item Output"; if (aMode == getModeOrdinal(FLUID_IN)) return "Fluid Input"; if (aMode == getModeOrdinal(FLUID_OUT)) return "Fluid Output"; if (aMode == getModeOrdinal(ENERGY_IN)) return "Energy Input"; if (aMode == getModeOrdinal(ENERGY_OUT)) return "Energy Output"; return "Unknown"; } protected byte getModeOrdinal(int aMode) { // log2 returns the bit position of the only bit set, add 1 to account for 0 being NOTHING // NOTE: Must be a power of 2 (single bit) return (byte) (log2(aMode, RoundingMode.UNNECESSARY) + 1); } protected byte getNextAllowedMode(List allowedModes) { if (this.allowedModes == NOTHING) return NOTHING; final int numModes = allowedModes.size(); for (byte i = 1; i <= numModes; i++) { final byte curMode = (byte) ((mode + i) % numModes); if (curMode == NOTHING || hasMode(1 << (curMode - 1))) return curMode; } // Nothing valid found return 0; } @Override public boolean onMalletRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX, float aY, float aZ) { if (allowedModes == NOTHING) return true; if (mode == NOTHING) { facing = wrenchSide; } setMode(getNextAllowedMode(BASIC_MODES)); if (aPlayer.isSneaking()) { facing = wrenchSide; } GTUtility.sendChatToPlayer(aPlayer, "Mode set to `" + getModeName(mode) + "' (" + mode + ")"); sendClientData((EntityPlayerMP) aPlayer); return true; } @Override public void setLightValue(byte aLightValue) {} @Override public byte getComparatorValue(ForgeDirection side) { return 0; } @Override public String getTileEntityName() { return "gt.multitileentity.multiblock.part"; } @Override public boolean shouldTick(long tickTimer) { return modeSelected(ITEM_OUT, FLUID_OUT); } /** * TODO: Make sure the energy/item/fluid hatch is facing that way! or has that mode enabled on that side Check * SIDE_UNKNOWN for or coverbehavior */ // #region Fluid - Depending on the part type - proxy it to the multiblock controller, if we have one @Override @Nullable public FluidInventoryLogic getFluidLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) { if (side != facing && side != ForgeDirection.UNKNOWN) return null; if (!modeSelected(FLUID_IN, FLUID_OUT)) return null; IMultiBlockController controller = getTarget(false); if (controller == null) return null; return controller .getFluidLogic(modeSelected(FLUID_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory); } // #endregion Fluid // #region Energy - Depending on the part type - proxy to the multiblock controller, if we have one @Override @Nonnull public PowerLogic getPowerLogic(@Nonnull ForgeDirection side) { if (side != facing && side != ForgeDirection.UNKNOWN) { return new NullPowerLogic(); } if (!modeSelected(ENERGY_IN, ENERGY_OUT)) { return new NullPowerLogic(); } final IMultiBlockController controller = getTarget(true); if (controller == null) { return new NullPowerLogic(); } return controller.getPowerLogic(); } @Override public boolean isEnetInput() { return modeSelected(ENERGY_IN); } @Override public boolean isEnetOutput() { return modeSelected(ENERGY_OUT); } // #endregion Energy // #region Item - Depending on the part type - proxy to the multiblock controller, if we have one @Override @Nullable public ItemInventoryLogic getItemLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType unused) { if (side != facing && side != ForgeDirection.UNKNOWN) return null; if (!modeSelected(ITEM_IN, ITEM_OUT)) return null; final IMultiBlockController controller = getTarget(false); if (controller == null) return null; return controller .getItemLogic(modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory); } @Override @Nullable public InventoryType getItemInventoryType() { if (!modeSelected(ITEM_IN, ITEM_OUT)) return InventoryType.Both; return modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output; } // #endregion Item // === Modular UI === @Override public boolean useModularUI() { return true; } @Override public String getLocalName() { if (modeSelected(ITEM_IN)) return "Input Inventory"; if (modeSelected(ITEM_OUT)) return "Output Inventory"; if (modeSelected(FLUID_IN)) return "Fluid Input Hatch"; if (modeSelected(FLUID_OUT)) return "Fluid Output Hatch"; return "Unknown"; } @Override public boolean hasGui(ForgeDirection side) { if (modeSelected(ENERGY_IN, ENERGY_OUT) && facing == side) { return false; } return getTarget(true) != null; } protected boolean isWrongFluid(Fluid fluid) { if (fluid == null) { return true; } Fluid lockedFluid = getLockedFluid(); if (lockedFluid != null) { return !fluid.equals(lockedFluid); } return false; } protected Fluid getLockedFluid() { if (configurationTank.get() != null && configurationTank.get() .getFluid() != null) { return configurationTank.get() .getFluid(); } return null; } @Override public void addUIWidgets(Builder builder, UIBuildContext buildContext) { super.addUIWidgets(builder, buildContext); IMultiBlockController controller = getTarget(false); if (controller == null) { return; } if ((modeSelected(ITEM_IN, ITEM_OUT))) { builder.widget( controller .getItemLogic(modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory) .getGuiPart() .setSize(18 * 4 + 4, 18 * 5) .setPos(52, 7)); } if ((modeSelected(FLUID_IN, FLUID_OUT))) { builder.widget( controller .getFluidLogic(modeSelected(FLUID_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory) .getGuiPart() .setSize(18 * 4 + 4, 18 * 5) .setPos(52, 7)); } } protected boolean canOpenControllerGui() { return true; } @Override protected int getGUIHeight() { return super.getGUIHeight() + 20; } @Override public void addGregTechLogo(Builder builder) { if (modeSelected(ITEM_IN, ITEM_OUT)) { builder.widget( new DrawableWidget().setDrawable(getGUITextureSet().getGregTechLogo()) .setSize(17, 17) .setPos(152, 74)); } else if (modeSelected(FLUID_IN, FLUID_OUT)) { builder.widget( new DrawableWidget().setDrawable(getGUITextureSet().getGregTechLogo()) .setSize(17, 17) .setPos(152, 82)); } else { super.addGregTechLogo(builder); } } @Override public void addToolTips(List list, ItemStack stack, boolean f3_h) { list.add("A MultiTileEntity Casing"); } public String getInventoryName() { IMultiBlockController controller = getTarget(false); if (controller == null) return ""; if (modeSelected(ITEM_IN, ITEM_OUT)) { InventoryType type = modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output; ItemInventoryLogic itemLogic = controller.getItemLogic(type, lockedInventory); return itemLogic.getDisplayName(); } if (modeSelected(FLUID_IN, FLUID_OUT)) { InventoryType type = modeSelected(FLUID_IN) ? InventoryType.Input : InventoryType.Output; FluidInventoryLogic fluidLogic = controller.getFluidLogic(type, lockedInventory); return fluidLogic.getDisplayName(); } return ""; } @Override @Nonnull public ForgeDirection getPowerOutputSide() { if (!modeSelected(ENERGY_OUT)) return ForgeDirection.UNKNOWN; return facing; } @Nonnull protected GUIProvider createGUIProvider() { return new PartGUIProvider<>(this); } @Override @Nonnull public GUIProvider getGUI(@Nonnull UIBuildContext uiContext) { IMultiBlockController controller = getTarget(false); if (controller == null) return guiProvider; if (!modeSelected(NOTHING, ENERGY_IN, ENERGY_OUT)) return guiProvider; if (!canOpenControllerGui()) return guiProvider; if (uiContext.getPlayer() .isSneaking()) return guiProvider; return controller.getGUI(uiContext); } @Override public ItemStack getAsItem() { return MultiTileEntityRegistry.getRegistry(getMultiTileEntityRegistryID()) .getItem(getMultiTileEntityID()); } @Override public String getMachineName() { return StatCollector.translateToLocal(getAsItem().getUnlocalizedName()); } }