From b31052879e7601dbfea4c7fb53b94119c7bbf5c6 Mon Sep 17 00:00:00 2001 From: querns <33518699+querns@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:03:17 -0500 Subject: Adds Metrics Transmitter Cover and Advanced GT Sensor Card for Information Panels (#2289) * Adds several UI elements to multiblock drills * Adds metrics transmitter cover and associated sensor card * Fixes item icons and item name * Adds tooltips for adv. sensor card and metrics panel, fixes card/overlay icons, adds recipe for metrics cover * Refactor cover tab sync to send much smaller ISerializable payloads * Remove unused variable * Additional master merge cleanup * Adds interface for custom metrics export, adds oil drill support * Adds support for metrics covers retaining attached machine name for tooltip, metrics * * Adds discrete coordinates to Metrics Cover data * Adds machine to advanced sensor card tooltip * Adds cycle time and minimum energy hatch tier to multiblock ore drill tooltips * Moves many tooltip strings to i10n * Remove old GT_DisabledWhileActiveButton.java file that crept in during a messy merge * Spotless * Improve oil and ore drill metrics, more i18n * Addresses PR review concerns, adds more situations for self-destructing sensor cards * Remediates further PR concerns --- src/main/java/gregtech/api/enums/GT_Values.java | 2 + src/main/java/gregtech/api/enums/ItemList.java | 4 +- src/main/java/gregtech/api/enums/Textures.java | 1 + .../metatileentity/IMetricsExporter.java | 28 ++ .../api/metatileentity/BaseMetaPipeEntity.java | 8 +- .../api/metatileentity/BaseMetaTileEntity.java | 9 +- .../api/metatileentity/CoverableTileEntity.java | 101 ++++- .../gregtech/api/util/GT_CoverBehaviorBase.java | 36 ++ src/main/java/gregtech/common/GT_Proxy.java | 2 + .../java/gregtech/common/covers/CoverInfo.java | 8 + .../covers/GT_Cover_Metrics_Transmitter.java | 212 +++++++++ .../common/events/BaseMetricsCoverEvent.java | 21 + .../common/events/MetricsCoverDataEvent.java | 37 ++ .../events/MetricsCoverHostDeconstructedEvent.java | 16 + .../events/MetricsCoverSelfDestructEvent.java | 15 + .../common/items/GT_AdvancedSensorCard_Item.java | 323 ++++++++++++++ .../common/items/GT_MetaGenerated_Item_03.java | 20 + .../gregtech/common/items/GT_SensorCard_Item.java | 2 +- .../items/behaviors/Behaviour_Cover_Tool.java | 24 +- .../common/misc/GlobalMetricsCoverDatabase.java | 492 +++++++++++++++++++++ .../multi/GT_MetaTileEntity_DrillerBase.java | 27 +- .../multi/GT_MetaTileEntity_OilDrillBase.java | 51 ++- .../GT_MetaTileEntity_OreDrillingPlantBase.java | 59 ++- .../loaders/postload/recipes/AssemblerRecipes.java | 13 + .../preload/GT_Loader_Item_Block_And_Fluid.java | 11 + 25 files changed, 1473 insertions(+), 49 deletions(-) create mode 100644 src/main/java/gregtech/api/interfaces/metatileentity/IMetricsExporter.java create mode 100644 src/main/java/gregtech/common/covers/GT_Cover_Metrics_Transmitter.java create mode 100644 src/main/java/gregtech/common/events/BaseMetricsCoverEvent.java create mode 100644 src/main/java/gregtech/common/events/MetricsCoverDataEvent.java create mode 100644 src/main/java/gregtech/common/events/MetricsCoverHostDeconstructedEvent.java create mode 100644 src/main/java/gregtech/common/events/MetricsCoverSelfDestructEvent.java create mode 100644 src/main/java/gregtech/common/items/GT_AdvancedSensorCard_Item.java create mode 100644 src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java (limited to 'src/main/java') diff --git a/src/main/java/gregtech/api/enums/GT_Values.java b/src/main/java/gregtech/api/enums/GT_Values.java index 1dcbc419f2..98d9e5a176 100644 --- a/src/main/java/gregtech/api/enums/GT_Values.java +++ b/src/main/java/gregtech/api/enums/GT_Values.java @@ -605,6 +605,8 @@ public class GT_Values { + EnumChatFormatting.LIGHT_PURPLE + "minecraft7771"; + public static final String AuthorQuerns = "Author: " + EnumChatFormatting.RED + "Querns"; + // 7.5F comes from GT_Tool_Turbine_Large#getBaseDamage() given huge turbines are the most efficient now. public static double getMaxPlasmaTurbineEfficiencyFromMaterial(Materials material) { return (5F + (7.5F + material.mToolQuality)) / 10.0; diff --git a/src/main/java/gregtech/api/enums/ItemList.java b/src/main/java/gregtech/api/enums/ItemList.java index 4e57d1b148..4d667cc495 100644 --- a/src/main/java/gregtech/api/enums/ItemList.java +++ b/src/main/java/gregtech/api/enums/ItemList.java @@ -2019,7 +2019,9 @@ public enum ItemList implements IItemContainer { ReinforcedPhotolithographicFrameworkCasing, RadiationProofPhotolithographicFrameworkCasing, InfinityCooledCasing, - Machine_Multi_TranscendentPlasmaMixer; + Machine_Multi_TranscendentPlasmaMixer, + Cover_Metrics_Transmitter, + NC_AdvancedSensorCard; public static final ItemList[] DYE_ONLY_ITEMS = { Color_00, Color_01, Color_02, Color_03, Color_04, Color_05, Color_06, Color_07, Color_08, Color_09, Color_10, Color_11, Color_12, Color_13, Color_14, Color_15 }, diff --git a/src/main/java/gregtech/api/enums/Textures.java b/src/main/java/gregtech/api/enums/Textures.java index 7dc0a28db2..cbc1ffc576 100644 --- a/src/main/java/gregtech/api/enums/Textures.java +++ b/src/main/java/gregtech/api/enums/Textures.java @@ -510,6 +510,7 @@ public class Textures { OVERLAY_WIRELESS_ITEM_DETECTOR, OVERLAY_WIRELESS_FLUID_DETECTOR, OVERLAY_WIRELESS_MAINTENANCE_DETECTOR, + OVERLAY_METRICS_TRANSMITTER, OVERLAY_FLUID_STORAGE_MONITOR0, OVERLAY_FLUID_STORAGE_MONITOR1, diff --git a/src/main/java/gregtech/api/interfaces/metatileentity/IMetricsExporter.java b/src/main/java/gregtech/api/interfaces/metatileentity/IMetricsExporter.java new file mode 100644 index 0000000000..f97cd79ed6 --- /dev/null +++ b/src/main/java/gregtech/api/interfaces/metatileentity/IMetricsExporter.java @@ -0,0 +1,28 @@ +package gregtech.api.interfaces.metatileentity; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +import gregtech.api.metatileentity.BaseMetaTileEntity; + +/** + * Metrics Transmitter covers look for this interface for retrieving custom metrics for a machine. If this interface is + * not present on the machine housing the cover, it will use {@link BaseMetaTileEntity#getInfoData()} to retrieve info + * instead. + */ +public interface IMetricsExporter { + + /** + * Attached metrics covers will call this method to receive reported metrics. + *

+ * When reporting metrics, try to keep the number of entries small, and ordering of entries consistent. Advanced + * Sensor Cards allow the user to selectively turn off individual lines using the panel's UI, and doing so is + * aggravated by a metrics set that is inconsistent and/or large. + * + * @return A list of strings to print on the information panel the advanced sensor card is utilizing. Each item in + * the list will be printed on its own line. + */ + @NotNull + List reportMetrics(); +} diff --git a/src/main/java/gregtech/api/metatileentity/BaseMetaPipeEntity.java b/src/main/java/gregtech/api/metatileentity/BaseMetaPipeEntity.java index 6a1d303a2f..19e5afebc0 100644 --- a/src/main/java/gregtech/api/metatileentity/BaseMetaPipeEntity.java +++ b/src/main/java/gregtech/api/metatileentity/BaseMetaPipeEntity.java @@ -40,6 +40,7 @@ import gregtech.api.interfaces.tileentity.IGregTechTileEntity; import gregtech.api.interfaces.tileentity.IPipeRenderedTileEntity; import gregtech.api.net.GT_Packet_TileEntity; import gregtech.api.objects.GT_ItemStack; +import gregtech.api.util.GT_CoverBehaviorBase; import gregtech.api.util.GT_Log; import gregtech.api.util.GT_ModHandler; import gregtech.api.util.GT_OreDictUnificator; @@ -951,10 +952,13 @@ public class BaseMetaPipeEntity extends CommonMetaTileEntity if (coverInfo.getCoverID() == 0) { if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sCovers.keySet())) { - if (GregTech_API.getCoverBehaviorNew(tCurrentItem) - .isCoverPlaceable(coverSide, tCurrentItem, this) + final GT_CoverBehaviorBase coverBehavior = GregTech_API.getCoverBehaviorNew(tCurrentItem); + if (coverBehavior.isCoverPlaceable(coverSide, tCurrentItem, this) && mMetaTileEntity.allowCoverOnSide(coverSide, new GT_ItemStack(tCurrentItem))) { + setCoverItemAtSide(coverSide, tCurrentItem); + coverBehavior.onPlayerAttach(aPlayer, tCurrentItem, this, side); + mMetaTileEntity.markDirty(); if (!aPlayer.capabilities.isCreativeMode) tCurrentItem.stackSize--; GT_Utility.sendSoundToPlayers( diff --git a/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java index c67648df9c..625fbba5af 100644 --- a/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java @@ -71,6 +71,7 @@ import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_BasicMachin import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch; import gregtech.api.net.GT_Packet_TileEntity; import gregtech.api.objects.GT_ItemStack; +import gregtech.api.util.GT_CoverBehaviorBase; import gregtech.api.util.GT_Log; import gregtech.api.util.GT_ModHandler; import gregtech.api.util.GT_OreDictUnificator; @@ -1660,10 +1661,14 @@ public class BaseMetaTileEntity extends CommonMetaTileEntity if (getCoverIDAtSide(coverSide) == 0) { if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sCovers.keySet())) { - if (GregTech_API.getCoverBehaviorNew(tCurrentItem) - .isCoverPlaceable(coverSide, tCurrentItem, this) + final GT_CoverBehaviorBase coverBehavior = GregTech_API + .getCoverBehaviorNew(tCurrentItem); + if (coverBehavior.isCoverPlaceable(coverSide, tCurrentItem, this) && mMetaTileEntity.allowCoverOnSide(coverSide, new GT_ItemStack(tCurrentItem))) { + setCoverItemAtSide(coverSide, tCurrentItem); + coverBehavior.onPlayerAttach(aPlayer, tCurrentItem, this, coverSide); + if (!aPlayer.capabilities.isCreativeMode) tCurrentItem.stackSize--; GT_Utility.sendSoundToPlayers( worldObj, diff --git a/src/main/java/gregtech/api/metatileentity/CoverableTileEntity.java b/src/main/java/gregtech/api/metatileentity/CoverableTileEntity.java index 04f8a457b1..68317eea48 100644 --- a/src/main/java/gregtech/api/metatileentity/CoverableTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/CoverableTileEntity.java @@ -9,8 +9,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.IntStream; import net.minecraft.client.Minecraft; import net.minecraft.entity.item.EntityItem; @@ -19,6 +22,7 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.network.PacketBuffer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.StatCollector; @@ -26,6 +30,11 @@ import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.FluidRegistry; +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteStreams; import com.gtnewhorizons.modularui.api.drawable.IDrawable; import com.gtnewhorizons.modularui.api.drawable.ItemDrawable; import com.gtnewhorizons.modularui.api.math.MainAxisAlignment; @@ -34,6 +43,7 @@ import com.gtnewhorizons.modularui.api.screen.UIBuildContext; import com.gtnewhorizons.modularui.api.widget.Widget; import com.gtnewhorizons.modularui.common.widget.ButtonWidget; import com.gtnewhorizons.modularui.common.widget.Column; +import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget; import com.gtnewhorizons.modularui.common.widget.MultiChildWidget; import cpw.mods.fml.relauncher.Side; @@ -56,6 +66,8 @@ import gregtech.api.util.ISerializableObject; import gregtech.common.GT_Client; import gregtech.common.covers.CoverInfo; import gregtech.common.covers.GT_Cover_Fluidfilter; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import mcp.mobius.waila.api.IWailaConfigHandler; import mcp.mobius.waila.api.IWailaDataAccessor; @@ -91,6 +103,7 @@ public abstract class CoverableTileEntity extends BaseTileEntity implements ICov protected short mID = 0; public long mTickTimer = 0; + private Map clientCoverData = new HashMap<>(); protected void writeCoverNBT(NBTTagCompound aNBT, boolean isDrop) { final NBTTagList tList = new NBTTagList(); @@ -230,8 +243,7 @@ public abstract class CoverableTileEntity extends BaseTileEntity implements ICov @Override public void issueCoverUpdate(ForgeDirection side) { // If we've got a null worldObj we're getting called as a part of readingNBT from a non tickable MultiTileEntity - // on chunk load before the world is set - // so we'll want to send a cover update. + // on chunk load before the world is set, so we'll want to send a cover update. final CoverInfo coverInfo = getCoverInfoAtSide(side); if (worldObj == null || (isServerSide() && coverInfo.isDataNeededOnClient())) coverInfo.setNeedsUpdate(true); } @@ -419,7 +431,7 @@ public abstract class CoverableTileEntity extends BaseTileEntity implements ICov @Override public void setStrongOutputRedstoneSignal(ForgeDirection side, byte strength) { - mStrongRedstone |= side.flag; + mStrongRedstone |= (byte) side.flag; setOutputRedstoneSignal(side, strength); } @@ -682,7 +694,7 @@ public abstract class CoverableTileEntity extends BaseTileEntity implements ICov return backgrounds.toArray(new IDrawable[] {}); } }.setOnClick((clickData, widget) -> onTabClicked(clickData, widget, direction)) - .dynamicTooltip(() -> getCoverTabTooltip(direction)) + .dynamicTooltip(() -> getCoverTabTooltip(direction, clientCoverData.get(direction))) .setSize(COVER_TAB_WIDTH, COVER_TAB_HEIGHT)) .addChild( new ItemDrawable(() -> getCoverItemAtSide(direction)).asWidget() @@ -691,10 +703,17 @@ public abstract class CoverableTileEntity extends BaseTileEntity implements ICov (COVER_TAB_HEIGHT - ICON_SIZE) / 2)) .setEnabled(widget -> getCoverItemAtSide(direction) != null)); } + + builder.widget( + new FakeSyncWidget<>( + this::collectCoverData, + data -> clientCoverData = data, + this::writeClientCoverData, + this::readClientCoverData)); } @SideOnly(Side.CLIENT) - protected List getCoverTabTooltip(ForgeDirection side) { + protected List getCoverTabTooltip(ForgeDirection side, ISerializableObject coverData) { final String[] SIDE_TOOLTIPS = new String[] { "GT5U.interface.coverTabs.down", "GT5U.interface.coverTabs.up", "GT5U.interface.coverTabs.north", "GT5U.interface.coverTabs.south", "GT5U.interface.coverTabs.west", "GT5U.interface.coverTabs.east" }; @@ -704,19 +723,18 @@ public abstract class CoverableTileEntity extends BaseTileEntity implements ICov final boolean coverHasGUI = coverInfo.hasCoverGUI(); final List tooltip = coverItem.getTooltip(Minecraft.getMinecraft().thePlayer, true); - for (int i = 0; i < tooltip.size(); i++) { - if (i == 0) { - tooltip.set( - 0, - (coverHasGUI ? EnumChatFormatting.UNDERLINE : EnumChatFormatting.DARK_GRAY) - + StatCollector.translateToLocal(SIDE_TOOLTIPS[side.ordinal()]) - + (coverHasGUI ? EnumChatFormatting.RESET + ": " : ": " + EnumChatFormatting.RESET) - + tooltip.get(0)); - } else { - tooltip.set(i, EnumChatFormatting.GRAY + tooltip.get(i)); - } - } - return tooltip; + final ImmutableList.Builder builder = ImmutableList.builder(); + builder.add( + (coverHasGUI ? EnumChatFormatting.UNDERLINE : EnumChatFormatting.DARK_GRAY) + + StatCollector.translateToLocal(SIDE_TOOLTIPS[side.ordinal()]) + + (coverHasGUI ? EnumChatFormatting.RESET + ": " : ": " + EnumChatFormatting.RESET) + + tooltip.get(0)); + builder.addAll(coverInfo.getAdditionalTooltip(coverData)); + builder.addAll( + IntStream.range(1, tooltip.size()) + .mapToObj(index -> EnumChatFormatting.GRAY + tooltip.get(index)) + .iterator()); + return builder.build(); } protected void onTabClicked(Widget.ClickData ignoredClickData, Widget widget, ForgeDirection side) { @@ -739,4 +757,51 @@ public abstract class CoverableTileEntity extends BaseTileEntity implements ICov .getPlayer()); } } + + @NotNull + private Map collectCoverData() { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (final ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) { + final CoverInfo coverInfo = getCoverInfoAtSide(direction); + if (coverInfo.isValid()) { + builder.put(direction, coverInfo.getCoverData()); + } + } + + return builder.build(); + } + + private void writeClientCoverData(@NotNull PacketBuffer buffer, + @NotNull Map dataMap) { + buffer.writeInt(dataMap.size()); + dataMap.forEach((direction, serializableObject) -> { + final ByteBuf individualBuffer = Unpooled.buffer(); + serializableObject.writeToByteBuf(individualBuffer); + + buffer.writeByte(direction.ordinal()); + buffer.writeInt(individualBuffer.array().length); + buffer.writeBytes(individualBuffer.array()); + }); + } + + @NotNull + private Map readClientCoverData(@NotNull PacketBuffer buffer) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + final int size = buffer.readInt(); + for (int i = 0; i < size; i++) { + final ForgeDirection direction = ForgeDirection.getOrientation(buffer.readByte()); + final int length = buffer.readInt(); + final byte[] object = buffer.readBytes(length) + .array(); + + // noinspection UnstableApiUsage + builder.put( + direction, + getCoverInfoAtSide(direction).getCoverBehavior() + .createDataObject() + .readFromPacket(ByteStreams.newDataInput(object), null)); + } + + return builder.build(); + } } diff --git a/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java b/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java index fdb4cbe7a4..3300ab43d6 100644 --- a/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java +++ b/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java @@ -2,6 +2,7 @@ package gregtech.api.util; import static gregtech.api.enums.GT_Values.E; +import java.util.List; import java.util.function.Supplier; import javax.annotation.Nullable; @@ -16,6 +17,9 @@ import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableList; import com.gtnewhorizons.modularui.api.ModularUITextures; import com.gtnewhorizons.modularui.api.drawable.ItemDrawable; import com.gtnewhorizons.modularui.api.screen.ModularWindow; @@ -385,6 +389,19 @@ public abstract class GT_CoverBehaviorBase { ICoverable aTileEntity) { return getDropImpl(side, aCoverID, forceCast(aCoverVariable), aTileEntity); } + + /** + * Called when the cover is initially attached to a machine. + * + * @param player The attaching player + * @param aCover An {@link ItemStack} containing the cover + * @param aTileEntity The machine receiving the cover + * @param side Which side the cover is attached to + */ + public void onPlayerAttach(EntityPlayer player, ItemStack aCover, ICoverable aTileEntity, ForgeDirection side) { + // Do nothing by default. + } + // endregion // region UI stuff @@ -815,5 +832,24 @@ public abstract class GT_CoverBehaviorBase { public String trans(String aNr, String aEnglish) { return GT_Utility.trans(aNr, aEnglish); } + + public boolean allowsCopyPasteTool() { + return true; + } + + @NotNull + public final List getAdditionalTooltip(ISerializableObject coverData) { + return getAdditionalTooltipImpl(forceCast(coverData)); + } + + /** + * Override to add to the tooltip generated when a user hovers over the cover on the left side of a machine's UI. + * + * @param coverData The cover data associated with the cover on a particular side. + * @return A list of new tooltip entries. Entries are inserted at the top, just after the name and direction line. + */ + protected List getAdditionalTooltipImpl(T coverData) { + return ImmutableList.of(); + } // endregion } diff --git a/src/main/java/gregtech/common/GT_Proxy.java b/src/main/java/gregtech/common/GT_Proxy.java index d853ea5f0a..2da2b883ca 100644 --- a/src/main/java/gregtech/common/GT_Proxy.java +++ b/src/main/java/gregtech/common/GT_Proxy.java @@ -166,6 +166,7 @@ import gregtech.common.entities.GT_Entity_Arrow; import gregtech.common.items.GT_MetaGenerated_Item_98; import gregtech.common.items.GT_MetaGenerated_Tool_01; import gregtech.common.misc.GlobalEnergyWorldSavedData; +import gregtech.common.misc.GlobalMetricsCoverDatabase; import gregtech.common.misc.spaceprojects.SpaceProjectWorldSavedData; public abstract class GT_Proxy implements IGT_Mod, IGuiHandler, IFuelHandler, IGlobalWirelessEnergy { @@ -1095,6 +1096,7 @@ public abstract class GT_Proxy implements IGT_Mod, IGuiHandler, IFuelHandler, IG MinecraftForge.EVENT_BUS.register(new GlobalEnergyWorldSavedData("")); MinecraftForge.EVENT_BUS.register(new SpaceProjectWorldSavedData()); MinecraftForge.EVENT_BUS.register(new GT_Worldgenerator.OregenPatternSavedData("")); + MinecraftForge.EVENT_BUS.register(new GlobalMetricsCoverDatabase()); FMLCommonHandler.instance() .bus() .register(new GT_Worldgenerator.OregenPatternSavedData("")); diff --git a/src/main/java/gregtech/common/covers/CoverInfo.java b/src/main/java/gregtech/common/covers/CoverInfo.java index e63fe6e176..0fac00218c 100644 --- a/src/main/java/gregtech/common/covers/CoverInfo.java +++ b/src/main/java/gregtech/common/covers/CoverInfo.java @@ -1,6 +1,7 @@ package gregtech.common.covers; import java.lang.ref.WeakReference; +import java.util.List; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; @@ -9,6 +10,8 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; +import org.jetbrains.annotations.NotNull; + import com.gtnewhorizons.modularui.api.screen.ModularWindow; import gregtech.api.GregTech_API; @@ -238,4 +241,9 @@ public final class CoverInfo { public int getFacadeMeta() { return getCoverBehavior().getFacadeMeta(coverSide, coverID, coverData, coveredTile.get()); } + + @NotNull + public List getAdditionalTooltip(ISerializableObject data) { + return getCoverBehavior().getAdditionalTooltip(data); + } } diff --git a/src/main/java/gregtech/common/covers/GT_Cover_Metrics_Transmitter.java b/src/main/java/gregtech/common/covers/GT_Cover_Metrics_Transmitter.java new file mode 100644 index 0000000000..6d3cf529d3 --- /dev/null +++ b/src/main/java/gregtech/common/covers/GT_Cover_Metrics_Transmitter.java @@ -0,0 +1,212 @@ +package gregtech.common.covers; + +import java.util.List; +import java.util.UUID; + +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.StatCollector; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.ForgeDirection; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteArrayDataInput; + +import gregtech.api.enums.ItemList; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetricsExporter; +import gregtech.api.interfaces.tileentity.ICoverable; +import gregtech.api.interfaces.tileentity.IGregTechDeviceInformation; +import gregtech.api.metatileentity.BaseMetaTileEntity; +import gregtech.api.util.GT_CoverBehaviorBase; +import gregtech.api.util.ISerializableObject; +import gregtech.common.events.MetricsCoverDataEvent; +import gregtech.common.events.MetricsCoverSelfDestructEvent; +import gregtech.common.misc.GlobalMetricsCoverDatabase; +import gregtech.common.misc.GlobalMetricsCoverDatabase.State; +import io.netty.buffer.ByteBuf; + +/** + * Used to transmit Nuclear Control information across dimensions. The only reason this is a cover is to artificially + * limit the number of total machines that transmit this data, for performance reasons. + *

+ * This cover will retrieve information, preferentially, using {@link IMetricsExporter#reportMetrics()}. Absent this + * method, it will resort to {@link BaseMetaTileEntity#getInfoData()} instead. + */ +public class GT_Cover_Metrics_Transmitter + extends GT_CoverBehaviorBase { + + @SuppressWarnings("SpellCheckingInspection") + public static final String FREQUENCY_MSB_KEY = "gt.metricscover.freq_msb"; + @SuppressWarnings("SpellCheckingInspection") + public static final String FREQUENCY_LSB_KEY = "gt.metricscover.freq_lsb"; + public static final String MACHINE_KEY = "machine_name"; + public static final String CARD_STATE_KEY = "card_state"; + + @SuppressWarnings("unused") + public GT_Cover_Metrics_Transmitter() { + this(null); + } + + public GT_Cover_Metrics_Transmitter(ITexture coverTexture) { + super(MetricsTransmitterData.class, coverTexture); + } + + @Override + public MetricsTransmitterData createDataObject(int aLegacyData) { + // As a new cover, this shouldn't fire. + return new MetricsTransmitterData(); + } + + @Override + public MetricsTransmitterData createDataObject() { + return new MetricsTransmitterData(); + } + + @Override + protected int getTickRateImpl(ForgeDirection side, int aCoverID, MetricsTransmitterData aCoverVariable, + ICoverable aTileEntity) { + return 20; + } + + @Override + public boolean isCoverPlaceable(ForgeDirection side, ItemStack aStack, ICoverable aTileEntity) { + return aTileEntity instanceof final IGregTechDeviceInformation device && device.isGivingInformation(); + } + + @Override + protected MetricsTransmitterData doCoverThingsImpl(ForgeDirection side, byte aInputRedstone, int aCoverID, + MetricsTransmitterData aCoverVariable, ICoverable aTileEntity, long aTimer) { + if (aTileEntity instanceof final BaseMetaTileEntity baseMTE && baseMTE.isGivingInformation()) { + final List payload; + + if (baseMTE.getMetaTileEntity() instanceof final IMetricsExporter metricsExporter) { + payload = metricsExporter.reportMetrics(); + } else { + payload = ImmutableList.copyOf(baseMTE.getInfoData()); + } + + MinecraftForge.EVENT_BUS.post(new MetricsCoverDataEvent( + aCoverVariable.frequency, + payload, + new GlobalMetricsCoverDatabase.Coordinates( + baseMTE.getWorld().provider.getDimensionName(), + baseMTE.getXCoord(), + baseMTE.getYCoord(), + baseMTE.getZCoord() + ) + )); + } + + return aCoverVariable; + } + + @Override + protected void onDroppedImpl(ForgeDirection side, int aCoverID, MetricsTransmitterData aCoverVariable, + ICoverable aTileEntity) { + MinecraftForge.EVENT_BUS.post(new MetricsCoverSelfDestructEvent(aCoverVariable.frequency)); + } + + @Override + public void onPlayerAttach(EntityPlayer player, ItemStack aCover, ICoverable aTileEntity, ForgeDirection side) { + final UUID newFrequency = UUID.randomUUID(); + final ItemStack cardStack = ItemList.NC_AdvancedSensorCard.get(1); + + if (cardStack == null) { + return; + } + + final NBTTagCompound tagCompound = new NBTTagCompound(); + tagCompound.setLong(FREQUENCY_MSB_KEY, newFrequency.getMostSignificantBits()); + tagCompound.setLong(FREQUENCY_LSB_KEY, newFrequency.getLeastSignificantBits()); + tagCompound.setInteger(CARD_STATE_KEY, State.OPERATIONAL.getType()); + + if (aTileEntity instanceof final BaseMetaTileEntity baseMTE) { + final ItemStack baseMTEStack = baseMTE.getStackForm(1); + if (baseMTEStack != null) { + tagCompound.setTag(MACHINE_KEY, baseMTEStack.writeToNBT(new NBTTagCompound())); + } + } + + cardStack.setTagCompound(tagCompound); + aTileEntity.getCoverInfoAtSide(side) + .setCoverData(new MetricsTransmitterData(newFrequency)); + + final EntityItem cardEntity = new EntityItem(player.worldObj, player.posX, player.posY, player.posZ, cardStack); + cardEntity.delayBeforeCanPickup = 0; + player.worldObj.spawnEntityInWorld(cardEntity); + } + + @Override + public boolean allowsCopyPasteTool() { + return false; + } + + @Override + public List getAdditionalTooltipImpl(MetricsTransmitterData data) { + return ImmutableList.of( + StatCollector.translateToLocalFormatted( + "gt.item.adv_sensor_card.tooltip.frequency", + EnumChatFormatting.UNDERLINE.toString() + EnumChatFormatting.YELLOW + data.frequency.toString())); + } + + public static class MetricsTransmitterData implements ISerializableObject { + + private UUID frequency; + + public MetricsTransmitterData() { + this.frequency = UUID.randomUUID(); + } + + public MetricsTransmitterData(@NotNull UUID frequency) { + this.frequency = frequency; + } + + @NotNull + public UUID getFrequency() { + return frequency; + } + + @NotNull + @Override + public ISerializableObject copy() { + return new MetricsTransmitterData(frequency); + } + + @NotNull + @Override + public NBTBase saveDataToNBT() { + NBTTagCompound tag = new NBTTagCompound(); + tag.setLong(FREQUENCY_MSB_KEY, frequency.getMostSignificantBits()); + tag.setLong(FREQUENCY_LSB_KEY, frequency.getLeastSignificantBits()); + return tag; + } + + @Override + public void loadDataFromNBT(NBTBase aNBT) { + if (aNBT instanceof final NBTTagCompound tag) { + frequency = new UUID(tag.getLong(FREQUENCY_MSB_KEY), tag.getLong(FREQUENCY_LSB_KEY)); + } + } + + @Override + public void writeToByteBuf(ByteBuf aBuf) { + aBuf.writeLong(frequency.getMostSignificantBits()); + aBuf.writeLong(frequency.getLeastSignificantBits()); + } + + @NotNull + @Override + public ISerializableObject readFromPacket(ByteArrayDataInput aBuf, @Nullable EntityPlayerMP aPlayer) { + return new MetricsTransmitterData(new UUID(aBuf.readLong(), aBuf.readLong())); + } + } +} diff --git a/src/main/java/gregtech/common/events/BaseMetricsCoverEvent.java b/src/main/java/gregtech/common/events/BaseMetricsCoverEvent.java new file mode 100644 index 0000000000..953d6fbfcf --- /dev/null +++ b/src/main/java/gregtech/common/events/BaseMetricsCoverEvent.java @@ -0,0 +1,21 @@ +package gregtech.common.events; + +import java.util.UUID; + +import org.jetbrains.annotations.NotNull; + +import cpw.mods.fml.common.eventhandler.Event; + +public abstract class BaseMetricsCoverEvent extends Event { + + protected final UUID frequency; + + public BaseMetricsCoverEvent(@NotNull UUID frequency) { + this.frequency = frequency; + } + + @NotNull + public UUID getFrequency() { + return frequency; + } +} diff --git a/src/main/java/gregtech/common/events/MetricsCoverDataEvent.java b/src/main/java/gregtech/common/events/MetricsCoverDataEvent.java new file mode 100644 index 0000000000..92a5aa663a --- /dev/null +++ b/src/main/java/gregtech/common/events/MetricsCoverDataEvent.java @@ -0,0 +1,37 @@ +package gregtech.common.events; + +import java.util.List; +import java.util.UUID; + +import org.jetbrains.annotations.NotNull; + +import gregtech.common.misc.GlobalMetricsCoverDatabase; + +/** + * Event fired when the Metrics Transmitter cover sends an information packet. + */ +public class MetricsCoverDataEvent extends BaseMetricsCoverEvent { + + @NotNull + private final List payload; + + @NotNull + private final GlobalMetricsCoverDatabase.Coordinates coordinates; + + public MetricsCoverDataEvent(@NotNull UUID frequency, @NotNull List payload, + @NotNull GlobalMetricsCoverDatabase.Coordinates coordinates) { + super(frequency); + this.payload = payload; + this.coordinates = coordinates; + } + + @NotNull + public List getPayload() { + return payload; + } + + @NotNull + public GlobalMetricsCoverDatabase.Coordinates getCoordinates() { + return coordinates; + } +} diff --git a/src/main/java/gregtech/common/events/MetricsCoverHostDeconstructedEvent.java b/src/main/java/gregtech/common/events/MetricsCoverHostDeconstructedEvent.java new file mode 100644 index 0000000000..490bd7d0b0 --- /dev/null +++ b/src/main/java/gregtech/common/events/MetricsCoverHostDeconstructedEvent.java @@ -0,0 +1,16 @@ +package gregtech.common.events; + +import java.util.UUID; + +import org.jetbrains.annotations.NotNull; + +/** + * Event fired when the machine housing a Metrics Transmitter cover is deconstructed, but the cover remains + * attached. + */ +public class MetricsCoverHostDeconstructedEvent extends BaseMetricsCoverEvent { + + public MetricsCoverHostDeconstructedEvent(@NotNull UUID frequency) { + super(frequency); + } +} diff --git a/src/main/java/gregtech/common/events/MetricsCoverSelfDestructEvent.java b/src/main/java/gregtech/common/events/MetricsCoverSelfDestructEvent.java new file mode 100644 index 0000000000..d554cbcc2b --- /dev/null +++ b/src/main/java/gregtech/common/events/MetricsCoverSelfDestructEvent.java @@ -0,0 +1,15 @@ +package gregtech.common.events; + +import java.util.UUID; + +import org.jetbrains.annotations.NotNull; + +/** + * Event fired when a Metrics Transmitter cover is detached from its machine with a crowbar. + */ +public class MetricsCoverSelfDestructEvent extends BaseMetricsCoverEvent { + + public MetricsCoverSelfDestructEvent(@NotNull UUID frequency) { + super(frequency); + } +} diff --git a/src/main/java/gregtech/common/items/GT_AdvancedSensorCard_Item.java b/src/main/java/gregtech/common/items/GT_AdvancedSensorCard_Item.java new file mode 100644 index 0000000000..820fe59952 --- /dev/null +++ b/src/main/java/gregtech/common/items/GT_AdvancedSensorCard_Item.java @@ -0,0 +1,323 @@ +package gregtech.common.items; + +import static gregtech.api.enums.Mods.GregTech; +import static gregtech.common.covers.GT_Cover_Metrics_Transmitter.CARD_STATE_KEY; +import static gregtech.common.covers.GT_Cover_Metrics_Transmitter.FREQUENCY_LSB_KEY; +import static gregtech.common.covers.GT_Cover_Metrics_Transmitter.FREQUENCY_MSB_KEY; +import static gregtech.common.covers.GT_Cover_Metrics_Transmitter.MACHINE_KEY; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IIcon; +import net.minecraft.util.StatCollector; +import net.minecraft.world.World; + +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableList; + +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import gregtech.common.misc.GlobalMetricsCoverDatabase; +import gregtech.common.misc.GlobalMetricsCoverDatabase.State; +import shedar.mods.ic2.nuclearcontrol.api.CardState; +import shedar.mods.ic2.nuclearcontrol.api.ICardWrapper; +import shedar.mods.ic2.nuclearcontrol.api.IPanelDataSource; +import shedar.mods.ic2.nuclearcontrol.api.PanelSetting; +import shedar.mods.ic2.nuclearcontrol.api.PanelString; + +@SuppressWarnings("unused") +public class GT_AdvancedSensorCard_Item extends Item implements IPanelDataSource { + + public static final UUID CARD_TYPE_ID = UUID.fromString("ff952e84-7608-4c4a-85af-dd6e1aa27fc7"); + + // This has obfuscated formatting, so no need to localize it. + private static final ImmutableList SELF_DESTRUCTED_OUTPUT = ImmutableList + .of(prebakePanelString(EnumChatFormatting.OBFUSCATED + "critical error" + EnumChatFormatting.RESET, true)); + + private static final ImmutableList DECONSTRUCTED_OUTPUT = ImmutableList.of( + prebakePanelString(StatCollector.translateToLocal("gt.item.adv_sensor_card.error.deconstructed.1"), true), + prebakePanelString(StatCollector.translateToLocal("gt.item.adv_sensor_card.error.deconstructed.2"), true)); + + private static final ImmutableList NO_DATA_FOUND = ImmutableList + .of(prebakePanelString(StatCollector.translateToLocal("gt.item.adv_sensor_card.error.no_data"), true)); + + private int payloadSize = 0; + + @SideOnly(Side.CLIENT) + private IIcon normalIcon; + @SideOnly(Side.CLIENT) + private IIcon selfDestructedIcon; + + @SuppressWarnings("unused") + public GT_AdvancedSensorCard_Item() { + super(); + + GameRegistry.registerItem(this, "gt.advancedsensorcard", GregTech.ID); + setUnlocalizedName("gt.advancedsensorcard"); + setMaxStackSize(1); + setNoRepair(); + } + + @Override + public void addInformation(final ItemStack itemStack, final EntityPlayer player, final List tooltip, + final boolean p_77624_4_) { + super.addInformation(itemStack, player, tooltip, p_77624_4_); + + final Optional cardState = getCardState(itemStack); + if (cardState.isPresent()) { + final State state = cardState.get(); + + if (state == State.SELF_DESTRUCTED) { + tooltip.add(StatCollector.translateToLocal("gt.item.adv_sensor_card.tooltip.fried.1")); + tooltip.add(StatCollector.translateToLocal("gt.item.adv_sensor_card.tooltip.fried.2")); + tooltip.add(StatCollector.translateToLocal("gt.item.adv_sensor_card.tooltip.fried.3")); + } else { + getMachineName(itemStack).ifPresent( + machineName -> tooltip.add( + StatCollector + .translateToLocalFormatted("gt.item.adv_sensor_card.tooltip.machine", machineName))); + getUUID(itemStack).ifPresent( + uuid -> tooltip.add( + StatCollector + .translateToLocalFormatted("gt.item.adv_sensor_card.tooltip.frequency", uuid.toString()))); + } + } else { + tooltip.add(StatCollector.translateToLocal("gt.item.adv_sensor_card.tooltip.recipe_hint")); + } + } + + @Override + @SideOnly(Side.CLIENT) + public void getSubItems(Item aItem, CreativeTabs aCreativeTab, List aOutputSubItems) {} + + @Override + @SideOnly(Side.CLIENT) + public void registerIcons(final IIconRegister aIconRegister) { + super.registerIcons(aIconRegister); + itemIcon = aIconRegister.registerIcon(GregTech.ID + ":gt.advancedsensorcard"); + normalIcon = itemIcon; + selfDestructedIcon = aIconRegister.registerIcon(GregTech.ID + ":gt.advancedsensorcardburned"); + } + + @Override + @SideOnly(Side.CLIENT) + public IIcon getIcon(final ItemStack stack, final int renderPass) { + return getIconIndex(stack); + } + + @Override + public IIcon getIconIndex(final ItemStack itemStack) { + return getCardState(itemStack).filter(Predicate.isEqual(State.SELF_DESTRUCTED)) + .map(ignored -> selfDestructedIcon) + .orElse(normalIcon); + } + + @Override + public CardState update(TileEntity tileEntity, ICardWrapper card, int maxRange) { + return update(tileEntity.getWorldObj(), card, maxRange); + } + + @Override + public CardState update(World world, ICardWrapper card, int maxRange) { + getDataFromDatabase(card).ifPresent(data -> { + reconcileSelfDestructedCard(card.getItemStack(), data.getState()); + card.setInt( + CARD_STATE_KEY, + data.getState() + .getType()); + payloadSize = switch (data.getState()) { + case SELF_DESTRUCTED -> SELF_DESTRUCTED_OUTPUT.size(); + case HOST_DECONSTRUCTED -> DECONSTRUCTED_OUTPUT.size() + getMachineName(card.getItemStack()).map(x -> 1) + .orElse(0); + case OPERATIONAL -> data.getPayload() + .map(List::size) + .orElse(0) + + getMachineName(card.getItemStack()).map(x -> 1) + .orElse(0) + + data.getCoordinates() + .map(x -> 2) + .orElse(0); + }; + }); + return CardState.OK; + } + + @Override + public List getStringData(final int displaySettings, final ICardWrapper card, + final boolean showLabels) { + // This method needs to return a mutable list, since the calling routine in NuclearCraft appends an item to the + // head of the list. Hence, all the array copying. + + return getCardState(card).map(state -> switch (state) { + case SELF_DESTRUCTED -> new ArrayList<>(SELF_DESTRUCTED_OUTPUT); + case HOST_DECONSTRUCTED -> { + final ArrayList list = new ArrayList<>(); + getMachineName(card.getItemStack()).ifPresent(name -> list.add(prebakePanelString(name, true))); + list.addAll(DECONSTRUCTED_OUTPUT); + yield list; + } + case OPERATIONAL -> getDataFromDatabase(card).map(data -> { + final ImmutableList.Builder builder = ImmutableList.builder(); + + getMachineName(card.getItemStack()).ifPresent(builder::add); + data.getCoordinates() + .ifPresent( + coordinates -> builder.add( + StatCollector.translateToLocalFormatted( + "gt.item.adv_sensor_card.dimension", + coordinates.getDimension()), + StatCollector.translateToLocalFormatted( + "gt.item.adv_sensor_card.coords", + coordinates.getLocalizedCoordinates()))); + + data.getPayload() + .ifPresent(builder::addAll); + + return builder.build(); + }) + .filter(payload -> !payload.isEmpty()) + .map( + payload -> IntStream.range(0, payload.size()) + .filter(i -> (displaySettings & (1 << i)) != 0) + .mapToObj(i -> prebakePanelString(payload.get(i), i == 0)) + .collect(Collectors.toCollection(ArrayList::new))) + .orElse(null); + }) + .orElse(new ArrayList<>(NO_DATA_FOUND)); + } + + @Override + public List getSettingsList() { + return payloadSize == 0 ? ImmutableList.of() + : ImmutableList.copyOf( + IntStream.range(0, Math.min(payloadSize, 31)) + .mapToObj(i -> new PanelSetting(String.valueOf(i + 1), 1 << i, getCardType())) + .iterator()); + } + + @Override + public UUID getCardType() { + return CARD_TYPE_ID; + } + + @Override + public void onUpdate(ItemStack stack, World worldIn, Entity entityIn, int slot, boolean isHeld) { + super.onUpdate(stack, worldIn, entityIn, slot, isHeld); + // At the time of this comment's writing, there are 52 matches of the regex: + // /% \d+0 \)?\s*== 0/ in the code base, indicating an over-reliance on events happening on either the 10th or + // 20th tick. Let's tick on something slightly off of that. A prime number will do nicely. + if ((worldIn.getWorldTime() % 20) == 13) { + getDataFromDatabase(stack).ifPresent(data -> { + reconcileSelfDestructedCard(stack, data.getState()); + if (!stack.hasTagCompound()) { + stack.setTagCompound(new NBTTagCompound()); + } + + stack.getTagCompound() + .setInteger( + CARD_STATE_KEY, + data.getState() + .getType()); + }); + } + } + + private void reconcileSelfDestructedCard(ItemStack stack, State newState) { + getUUID(stack).ifPresent(uuid -> getCardState(stack).ifPresent(oldState -> { + if (newState == State.SELF_DESTRUCTED && oldState != State.SELF_DESTRUCTED) { + GlobalMetricsCoverDatabase.clearSelfDestructedFrequency(uuid); + } + })); + + } + + @NotNull + private Optional getCardState(ICardWrapper card) { + return getCardState(card.getItemStack()); + } + + @NotNull + private Optional getCardState(ItemStack itemStack) { + if (itemStack.hasTagCompound() && itemStack.getTagCompound() + .hasKey(CARD_STATE_KEY)) { + return State.find( + itemStack.getTagCompound() + .getInteger(CARD_STATE_KEY)); + } + + return Optional.empty(); + } + + @NotNull + private Optional getUUID(ItemStack stack) { + if (stack.hasTagCompound()) { + NBTTagCompound nbt = stack.getTagCompound(); + if (nbt.hasKey(FREQUENCY_LSB_KEY) && nbt.hasKey(FREQUENCY_MSB_KEY)) { + return Optional.of(new UUID(nbt.getLong(FREQUENCY_MSB_KEY), nbt.getLong(FREQUENCY_LSB_KEY))); + } + } + + return Optional.empty(); + } + + @NotNull + private Optional getMachineName(ItemStack stack) { + if (stack.hasTagCompound() && stack.getTagCompound() + .hasKey(MACHINE_KEY)) { + try { + final ItemStack machine = ItemStack.loadItemStackFromNBT( + stack.getTagCompound() + .getCompoundTag(MACHINE_KEY)); + if (machine != null) { + return Optional.of(machine.getDisplayName()); + } + } catch (Exception ignored) {} + } + + return Optional.empty(); + } + + @NotNull + private Optional getDataFromDatabase(ICardWrapper card) { + return getDataFromDatabase(card.getItemStack()); + } + + @NotNull + private Optional getDataFromDatabase(ItemStack stack) { + return getUUID(stack).flatMap(GlobalMetricsCoverDatabase::getData); + } + + @NotNull + private static PanelString prebakePanelString(String info) { + return prebakePanelString(info, false); + } + + @NotNull + private static PanelString prebakePanelString(String info, boolean center) { + final PanelString panelString = new PanelString(); + if (center) { + panelString.textCenter = info; + } else { + panelString.textLeft = info; + } + return panelString; + + } +} diff --git a/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java b/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java index bede46adc9..f980c8031b 100644 --- a/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java +++ b/src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java @@ -1,5 +1,7 @@ package gregtech.common.items; +import static gregtech.api.enums.Textures.BlockIcons.MACHINE_CASINGS; +import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_METRICS_TRANSMITTER; import static gregtech.api.enums.Textures.BlockIcons.SOLARPANEL_UEV; import static gregtech.api.enums.Textures.BlockIcons.SOLARPANEL_UHV; import static gregtech.api.enums.Textures.BlockIcons.SOLARPANEL_UIV; @@ -21,6 +23,7 @@ import static gregtech.client.GT_TooltipHandler.Tier.ZPM; import static gregtech.client.GT_TooltipHandler.registerTieredTooltip; import gregtech.api.GregTech_API; +import gregtech.api.enums.GT_Values; import gregtech.api.enums.ItemList; import gregtech.api.enums.Materials; import gregtech.api.enums.OrePrefixes; @@ -29,6 +32,7 @@ import gregtech.api.enums.TC_Aspects; import gregtech.api.items.GT_MetaGenerated_Item_X32; import gregtech.api.render.TextureFactory; import gregtech.api.util.GT_OreDictUnificator; +import gregtech.common.covers.GT_Cover_Metrics_Transmitter; import gregtech.common.covers.GT_Cover_SolarPanel; public class GT_MetaGenerated_Item_03 extends GT_MetaGenerated_Item_X32 { @@ -911,6 +915,22 @@ public class GT_MetaGenerated_Item_03 extends GT_MetaGenerated_Item_X32 { "By the powers of Greg, I command this star to be really hot.", SubTag.NO_UNIFICATION)); + ItemList.Cover_Metrics_Transmitter.set( + addItem( + 232, + "Metrics Transmitter Cover", + String.join( + "/n ", + "Taking Information Panels to the next level!", + "Creates a GregTech Advanced Sensor Card when attached", + "Works across dimensions or if machine is dismantled", + "Removing this cover will destroy the linked card", + GT_Values.AuthorQuerns))); + GregTech_API.registerCover( + ItemList.Cover_Metrics_Transmitter.get(1L), + TextureFactory.of(MACHINE_CASINGS[2][0], TextureFactory.of(OVERLAY_METRICS_TRANSMITTER)), + new GT_Cover_Metrics_Transmitter(TextureFactory.of(OVERLAY_METRICS_TRANSMITTER))); + ItemList.Optical_Cpu_Containment_Housing.set(addItem(727, "Optical CPU Containment Housing", "CPU Housing", o)); ItemList.Optically_Perfected_CPU.set(addItem(726, "Optically Perfected CPU", "Perfected CPU!", o)); ItemList.Optically_Compatible_Memory.set(addItem(725, "Optically Compatible Memory", "Its in the name!", o)); diff --git a/src/main/java/gregtech/common/items/GT_SensorCard_Item.java b/src/main/java/gregtech/common/items/GT_SensorCard_Item.java index 024b089812..67e5b24a70 100644 --- a/src/main/java/gregtech/common/items/GT_SensorCard_Item.java +++ b/src/main/java/gregtech/common/items/GT_SensorCard_Item.java @@ -43,7 +43,7 @@ public class GT_SensorCard_Item extends GT_Generic_Item implements IRemoteSensor if (aStack != null) { NBTTagCompound tNBT = aStack.getTagCompound(); if (tNBT == null) { - aList.add(transItem("014", "Missing Coodinates!")); + aList.add(transItem("014", "Missing Coordinates!")); } else { aList.add(transItem("015", "Device at:")); aList.add( diff --git a/src/main/java/gregtech/common/items/behaviors/Behaviour_Cover_Tool.java b/src/main/java/gregtech/common/items/behaviors/Behaviour_Cover_Tool.java index bb37e1e933..5688872796 100644 --- a/src/main/java/gregtech/common/items/behaviors/Behaviour_Cover_Tool.java +++ b/src/main/java/gregtech/common/items/behaviors/Behaviour_Cover_Tool.java @@ -112,14 +112,22 @@ public class Behaviour_Cover_Tool extends Behaviour_None { ? GT_Utility.determineWrenchingSide(side, hitX, hitY, hitZ) : ForgeDirection.UNKNOWN; if (tSide != ForgeDirection.UNKNOWN) { - mStoredData = tCoverable.getComplexCoverDataAtSide(tSide); - mCoverType = tCoverable.getCoverIDAtSide(tSide); - aList.add("Block Side: " + EnumChatFormatting.AQUA + tSide.name() + EnumChatFormatting.RESET); - aList.add( - "Cover Type: " + EnumChatFormatting.GREEN - + tCoverable.getCoverItemAtSide(tSide) - .getDisplayName() - + EnumChatFormatting.RESET); + if (tCoverable.getCoverBehaviorAtSideNew(tSide) + .allowsCopyPasteTool()) { + mStoredData = tCoverable.getComplexCoverDataAtSide(tSide); + mCoverType = tCoverable.getCoverIDAtSide(tSide); + + aList.add("Block Side: " + EnumChatFormatting.AQUA + tSide.name() + EnumChatFormatting.RESET); + aList.add( + "Cover Type: " + EnumChatFormatting.GREEN + + tCoverable.getCoverItemAtSide(tSide) + .getDisplayName() + + EnumChatFormatting.RESET); + } else { + mStoredData = GregTech_API.sNoBehavior.createDataObject(); + mCoverType = 0; + aList.add("Copy unavailable for this cover type"); + } } else { mStoredData = GregTech_API.sNoBehavior.createDataObject(); mCoverType = 0; diff --git a/src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java b/src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java new file mode 100644 index 0000000000..33e8198bd6 --- /dev/null +++ b/src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java @@ -0,0 +1,492 @@ +package gregtech.common.misc; + +import static net.minecraftforge.common.util.Constants.NBT.TAG_BYTE_ARRAY; +import static net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import net.minecraft.entity.item.EntityItem; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagByteArray; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.StatCollector; +import net.minecraft.world.WorldSavedData; +import net.minecraft.world.storage.MapStorage; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.item.ItemExpireEvent; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.event.world.ExplosionEvent.Detonate; +import net.minecraftforge.event.world.WorldEvent; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import gregtech.api.enums.GT_Values; +import gregtech.api.util.GT_Utility; +import gregtech.common.covers.CoverInfo; +import gregtech.common.covers.GT_Cover_Metrics_Transmitter; +import gregtech.common.events.MetricsCoverDataEvent; +import gregtech.common.events.MetricsCoverHostDeconstructedEvent; +import gregtech.common.events.MetricsCoverSelfDestructEvent; + +/** + * Catches and provides data transmitted from deployed Metrics Transmitter covers. Only stores one result per frequency + * at a time. Metrics covers are intended to overwrite an old result every time they emit a new event. + *
+ *
+ * This information is only partially persisted; frequencies that are in a non-operational state will be written to + * disk, while operational frequencies are volatile. The assumption is that any frequency with a broadcasting card will, + * fairly quickly, re-assert its presence. Conversely, one-time events like deconstruction or self-destruction can occur + * while the card is in a container, rotting on the ground, etc. + */ +public class GlobalMetricsCoverDatabase extends WorldSavedData { + + private static GlobalMetricsCoverDatabase INSTANCE; + + /** Holds received metrics. */ + private static final Map DATABASE = new ConcurrentHashMap<>(); + /** Used to speed up event handlers dealing with block breaking and explosions. Not persisted. */ + private static final Map> REVERSE_LOOKUP = new ConcurrentHashMap<>(); + + private static final String DATA_NAME = "GregTech_MetricsCoverDatabase"; + private static final String DECONSTRUCTED_KEY = "GregTech_MetricsCoverDatabase_Deconstructed"; + private static final String SELF_DESTRUCTED_KEY = "GregTech_MetricsCoverDatabase_SelfDestructed"; + + public GlobalMetricsCoverDatabase() { + this(DATA_NAME); + } + + public GlobalMetricsCoverDatabase(String name) { + super(name); + } + + @SuppressWarnings("unused") + @SubscribeEvent + public void receiveMetricsData(MetricsCoverDataEvent event) { + final Coordinates coordinates = event.getCoordinates(); + store(event.getFrequency(), State.OPERATIONAL, event.getPayload(), coordinates); + + if (!REVERSE_LOOKUP.containsKey(coordinates)) { + REVERSE_LOOKUP.put(coordinates, new HashSet<>()); + } + REVERSE_LOOKUP.get(coordinates) + .add(event.getFrequency()); + } + + @SuppressWarnings("unused") + @SubscribeEvent + public void receiveHostDeconstructed(MetricsCoverHostDeconstructedEvent event) { + cullReverseLookupEntry(event.getFrequency()); + store(event.getFrequency(), State.HOST_DECONSTRUCTED); + } + + @SuppressWarnings("unused") + @SubscribeEvent + public void receiveSelfDestruct(MetricsCoverSelfDestructEvent event) { + cullReverseLookupEntry(event.getFrequency()); + store(event.getFrequency(), State.SELF_DESTRUCTED); + } + + @SuppressWarnings("unused") + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load event) { + if (event.world.isRemote || event.world.provider.dimensionId != 0) { + return; + } + + DATABASE.clear(); + + final MapStorage storage = event.world.mapStorage; + INSTANCE = (GlobalMetricsCoverDatabase) storage.loadData(GlobalMetricsCoverDatabase.class, DATA_NAME); + if (INSTANCE == null) { + INSTANCE = new GlobalMetricsCoverDatabase(); + storage.setData(DATA_NAME, INSTANCE); + } + + INSTANCE.markDirty(); + } + + @SuppressWarnings("unused") + @SubscribeEvent + public void onBlockBreak(BlockEvent.BreakEvent event) { + final Coordinates coords = new Coordinates(event.world.provider.getDimensionName(), event.x, event.y, event.z); + // In case someone else wants to listen to these, go the roundabout way. + final Set uuids = REVERSE_LOOKUP.get(coords); + if (uuids != null) { + uuids.forEach( + uuid -> MinecraftForge.EVENT_BUS.post( + ForgeHooks.canHarvestBlock(event.block, event.getPlayer(), event.blockMetadata) + && !event.getPlayer().capabilities.isCreativeMode ? new MetricsCoverHostDeconstructedEvent(uuid) + : new MetricsCoverSelfDestructEvent(uuid))); + } + } + + @SuppressWarnings("unused") + @SubscribeEvent + public void onExplosion(Detonate event) { + final String dimensionName = event.world.provider.getDimensionName(); + + event.getAffectedBlocks() + .forEach(chunkPosition -> { + final Set uuids = REVERSE_LOOKUP.get( + new Coordinates( + dimensionName, + chunkPosition.chunkPosX, + chunkPosition.chunkPosY, + chunkPosition.chunkPosZ)); + + if (uuids != null) { + uuids.forEach(uuid -> MinecraftForge.EVENT_BUS.post(new MetricsCoverSelfDestructEvent(uuid))); + } + }); + + event.getAffectedEntities().forEach(entity -> { + if (entity instanceof final EntityItem entityItem) { + getCoverUUIDsFromItemStack(entityItem.getEntityItem()) + .forEach(uuid -> MinecraftForge.EVENT_BUS.post(new MetricsCoverSelfDestructEvent(uuid))); + } + }); + } + + @SuppressWarnings("unused") + @SubscribeEvent + public void onItemExpiration(ItemExpireEvent event) { + getCoverUUIDsFromItemStack(event.entityItem.getEntityItem()) + .forEach(uuid -> MinecraftForge.EVENT_BUS.post(new MetricsCoverSelfDestructEvent(uuid))); + } + + /** + * Get the data for a frequency, if it exists. + * + * @param frequency The UUID corresponding to the frequency to retrieve. + * @return An Optional with the frequency's data, or an empty Optional if it doesn't exist. + */ + @NotNull + public static Optional getData(UUID frequency) { + return Optional.ofNullable(DATABASE.get(frequency)); + } + + /** + * Once a card has received the fact that it has self-destructed, this method can be called to free up its spot + * in the database. Does nothing if the frequency is missing or is not in a self-destructed state. + * + * @param frequency The UUID corresponding to the frequency to possibly cull. + */ + public static void clearSelfDestructedFrequency(UUID frequency) { + getData(frequency).ifPresent(data -> { + if (data.getState() == State.SELF_DESTRUCTED) { + DATABASE.remove(frequency); + tryMarkDirty(); + } + }); + } + + @Override + public void readFromNBT(NBTTagCompound nbtTagCompound) { + final NBTTagList deconstructed = nbtTagCompound.getTagList(DECONSTRUCTED_KEY, TAG_BYTE_ARRAY); + final NBTTagList selfDestructed = nbtTagCompound.getTagList(SELF_DESTRUCTED_KEY, TAG_BYTE_ARRAY); + + for (int i = 0; i < deconstructed.tagCount(); i++) { + final NBTTagByteArray byteArray = (NBTTagByteArray) deconstructed.removeTag(0); + reconstituteUUID(byteArray.func_150292_c()) + .ifPresent(uuid -> DATABASE.put(uuid, new Data(State.HOST_DECONSTRUCTED))); + } + + for (int i = 0; i < selfDestructed.tagCount(); i++) { + final NBTTagByteArray byteArray = (NBTTagByteArray) selfDestructed.removeTag(0); + reconstituteUUID(byteArray.func_150292_c()) + .ifPresent(uuid -> DATABASE.put(uuid, new Data(State.SELF_DESTRUCTED))); + } + } + + @Override + public void writeToNBT(NBTTagCompound nbtTagCompound) { + // We only care about persisting frequencies that aren't operational. + final NBTTagList deconstructed = new NBTTagList(); + final NBTTagList selfDestructed = new NBTTagList(); + DATABASE.forEach((uuid, data) -> { + switch (data.getState()) { + case HOST_DECONSTRUCTED -> deconstructed.appendTag(new NBTTagByteArray(dumpUUID(uuid))); + case SELF_DESTRUCTED -> selfDestructed.appendTag(new NBTTagByteArray(dumpUUID(uuid))); + } + }); + + if (deconstructed.tagCount() > 0) { + nbtTagCompound.setTag(DECONSTRUCTED_KEY, deconstructed); + } + if (selfDestructed.tagCount() > 0) { + nbtTagCompound.setTag(SELF_DESTRUCTED_KEY, selfDestructed); + } + } + + /** + * Stores the new result and flag the static {@link MapStorage} instance as dirty if the information updated. Will + * not flag dirty for any data in the {@link State#OPERATIONAL OPERATIONAL} state since they aren't stored. + * + * @param frequency Maps to a unique deployed cover. + * @param state The new cover state. + */ + private static void store(@NotNull UUID frequency, @NotNull State state) { + store(frequency, state, null, null); + } + + /** + * Stores the new result and flag the static {@link MapStorage} instance as dirty if the information updated. Will + * not flag dirty for any data in the {@link State#OPERATIONAL OPERATIONAL} state since they aren't stored. + * + * @param frequency Maps to a unique deployed cover. + * @param state The new cover state. + * @param payload A list of strings to display on the information panel, if the card is slotted properly. + * @param coordinates Coordinates of the active machine (including dimension.) + */ + private static void store(@NotNull UUID frequency, @NotNull State state, @Nullable List payload, + @Nullable Coordinates coordinates) { + final Data newData = new Data(state, payload, coordinates); + final Data oldData = DATABASE.put(frequency, newData); + + if (state != State.OPERATIONAL && (oldData == null || oldData != newData)) { + tryMarkDirty(); + } + } + + private static void tryMarkDirty() { + if (INSTANCE != null) { + INSTANCE.markDirty(); + } + } + + private static byte[] dumpUUID(UUID uuid) { + ByteBuffer buffer = ByteBuffer.wrap(new byte[16]); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + return buffer.array(); + } + + @NotNull + private static Optional reconstituteUUID(byte[] bytes) throws IllegalArgumentException { + if (bytes.length != 16) { + return Optional.empty(); + } + + ByteBuffer buffer = ByteBuffer.wrap(bytes); + return Optional.of(new UUID(buffer.getLong(), buffer.getLong())); + } + + private static void cullReverseLookupEntry(UUID frequency) { + getData(frequency).ifPresent(data -> { + if (data.state == State.OPERATIONAL && REVERSE_LOOKUP.containsKey(data.coordinates)) { + final Set set = REVERSE_LOOKUP.get(data.coordinates); + set.remove(frequency); + if (set.isEmpty()) { + REVERSE_LOOKUP.remove(data.coordinates); + } + } + }); + } + + private static Stream getCoverUUIDsFromItemStack(final ItemStack stack) { + if (stack.hasTagCompound() && stack.getTagCompound() + .hasKey(GT_Values.NBT.COVERS, TAG_COMPOUND)) { + final NBTTagList tagList = stack.getTagCompound() + .getTagList(GT_Values.NBT.COVERS, TAG_COMPOUND); + return IntStream.range(0, tagList.tagCount()) + .mapToObj(tagList::getCompoundTagAt) + .map(nbt -> new CoverInfo(null, nbt).getCoverData()) + .filter( + serializableObject -> serializableObject instanceof GT_Cover_Metrics_Transmitter.MetricsTransmitterData) + .map(data -> ((GT_Cover_Metrics_Transmitter.MetricsTransmitterData) data).getFrequency()); + } + return Stream.empty(); + } + + /** + * Data transmitted by a Metrics Transmitter cover. + *

+ * Since only negative states ({@link State#HOST_DECONSTRUCTED HOST_DECONSTRUCTED} and + * {@link State#SELF_DESTRUCTED SELF DESTRUCTED}) are persisted, additional fields can be added to this data with + * little consequence. Ensure that any new fields are nullable, and make any getter for these fields return an + * {@link Optional}. + */ + public static class Data { + + @NotNull + private final State state; + @Nullable + private final List payload; + @Nullable + private final Coordinates coordinates; + + public Data(@NotNull State state) { + this.state = state; + this.payload = null; + this.coordinates = null; + } + + public Data(@NotNull State state, @Nullable List payload) { + this.state = state; + this.payload = payload; + this.coordinates = null; + } + + public Data(@NotNull State state, @Nullable List payload, @Nullable Coordinates coordinates) { + this.state = state; + this.payload = payload; + this.coordinates = coordinates; + + } + + /** + * Retrieves the payload for this data. Only present if the frequency is in an + * {@link State#OPERATIONAL operational} state. Will be cleared if the frequency goes into a + * {@link State#HOST_DECONSTRUCTED host-deconstructed} or {@link State#SELF_DESTRUCTED self-destructed} state. + * + * @return The data if present, or an empty Optional otherwise. + */ + @NotNull + public Optional> getPayload() { + return Optional.ofNullable(payload); + } + + /** + * Gets the state of the frequency. + * + * @return The state + */ + @NotNull + public State getState() { + return state; + } + + /** + * Gets the last known coordinates for the machine broadcasting metrics. Will only be present in an + * {@link State#OPERATIONAL operational} state. + * + * @return The coordinates + */ + @NotNull + public Optional getCoordinates() { + return Optional.ofNullable(coordinates); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Data data = (Data) o; + return state == data.state && Objects.equals(payload, data.payload) + && Objects.equals(coordinates, data.coordinates); + } + + @Override + public int hashCode() { + return Objects.hash(state, payload, coordinates); + } + } + + @SuppressWarnings("ClassCanBeRecord") + public static class Coordinates { + + private final String dimension; + private final int x; + private final int y; + private final int z; + + public Coordinates(final String dimension, final int x, final int y, final int z) { + this.dimension = dimension; + this.x = x; + this.y = y; + this.z = z; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public String getDimension() { + return dimension; + } + + public String getLocalizedCoordinates() { + return StatCollector.translateToLocalFormatted( + "gt.db.metrics_cover.coords", + GT_Utility.formatNumbers(x), + GT_Utility.formatNumbers(y), + GT_Utility.formatNumbers(z)); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Coordinates that = (Coordinates) o; + return x == that.x && y == that.y && z == that.z && Objects.equals(dimension, that.dimension); + } + + @Override + public int hashCode() { + return Objects.hash(dimension, x, y, z); + } + } + + public enum State { + // NOTE: type cannot be 0, as NuclearControl returns a 0 when querying for an integer from an item stack's NBT + // data when it really means null. + + /** The machine is online and broadcasting metrics. */ + OPERATIONAL(1), + /** + * The machine was picked up, but the cover is still attached. Will transition to operational state if the + * machine is placed back down and started up again. + */ + HOST_DECONSTRUCTED(2), + /** + * Cover was removed from its host machine, or machine was destroyed (in the limited number of ways we can + * detect.) Any frequency in this state will no longer get updates nor leave this state. + */ + SELF_DESTRUCTED(3); + + private static final Map VALID_TYPE_INTEGERS = Arrays.stream(State.values()) + .collect(Collectors.toMap(State::getType, Function.identity())); + private final int type; + + State(final int type) { + if (type <= 0) { + throw new IllegalArgumentException("A state must have a positive, nonzero type parameter."); + } + this.type = type; + } + + @NotNull + public static Optional find(int candidate) { + return Optional.ofNullable(VALID_TYPE_INTEGERS.get(candidate)); + } + + public int getType() { + return type; + } + } +} diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java index f40c70d2a4..41e0712627 100644 --- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java +++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; @@ -161,6 +162,7 @@ public abstract class GT_MetaTileEntity_DrillerBase /** Allows inheritors to supply custom runtime failure messages. */ private CheckRecipeResult runtimeFailure = null; + private CheckRecipeResult lastRuntimeFailure = null; /** Allows inheritors to supply custom shutdown failure messages. */ private @NotNull String shutdownReason = ""; @@ -563,12 +565,16 @@ public abstract class GT_MetaTileEntity_DrillerBase } if (runtimeFailure == null) { + if (wasSuccessful) { + lastRuntimeFailure = null; + } + return resultRegistry.getOrDefault( new ResultRegistryKey(workState, wasSuccessful), SimpleCheckRecipeResult.ofFailure("no_mining_pipe")); } else { final CheckRecipeResult result; - result = runtimeFailure; + result = lastRuntimeFailure = runtimeFailure; runtimeFailure = null; return result; } @@ -584,6 +590,25 @@ public abstract class GT_MetaTileEntity_DrillerBase runtimeFailure = newFailureReason; } + /** + * Gets a reason for why the drill turned off, for use in UIs and such. + * + * @return A reason, or empty if the machine is active or there is no message set yet. + */ + @NotNull + protected Optional getFailureReason() { + if (getBaseMetaTileEntity().isActive()) { + return Optional.empty(); + } + + if (!shutdownReason.isEmpty()) { + return Optional.of(shutdownReason); + } + + return Optional.ofNullable(lastRuntimeFailure) + .map(CheckRecipeResult::getDisplayString); + } + /** * Sets a line in the UI to explain why the drill shut down. E.g.: operation finished. * Should be used when the machine has been turned off due to an operating issue or completion. diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java index 9a6f2b5e80..0a47bdb268 100644 --- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java +++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java @@ -45,6 +45,7 @@ import com.gtnewhorizons.modularui.common.widget.TextWidget; import gregtech.api.enums.SoundResource; import gregtech.api.interfaces.IHatchElement; import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.metatileentity.IMetricsExporter; import gregtech.api.interfaces.tileentity.IGregTechTileEntity; import gregtech.api.objects.GT_ChunkManager; import gregtech.api.recipe.check.CheckRecipeResultRegistry; @@ -55,7 +56,7 @@ import gregtech.api.util.GT_Utility; import gregtech.api.util.ValidationResult; import gregtech.api.util.ValidationType; -public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_DrillerBase { +public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_DrillerBase implements IMetricsExporter { private final ArrayList mOilFieldChunks = new ArrayList<>(); private int mOilId = 0; @@ -385,6 +386,31 @@ public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_D return l.toArray(new String[0]); } + @Override + public @NotNull List reportMetrics() { + if (getBaseMetaTileEntity().isActive()) { + return switch (workState) { + case STATE_AT_BOTTOM -> ImmutableList.of( + StatCollector.translateToLocalFormatted("GT5U.gui.text.pump_fluid_type", getFluidName()), + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.pump_rate.1", + EnumChatFormatting.AQUA + getFlowRatePerTick()) + + StatCollector.translateToLocal("GT5U.gui.text.pump_rate.2"), + getReservoirContents() + StatCollector.translateToLocal("GT5U.gui.text.pump_recovery.2")); + case STATE_DOWNWARD -> ImmutableList.of(StatCollector.translateToLocal("GT5U.gui.text.deploying_pipe")); + case STATE_UPWARD, STATE_ABORT -> ImmutableList + .of(StatCollector.translateToLocal("GT5U.gui.text.retracting_pipe")); + + default -> ImmutableList.of(); + }; + } + + return ImmutableList.of( + getFailureReason() + .map(reason -> StatCollector.translateToLocalFormatted("GT5U.gui.text.drill_offline_reason", reason)) + .orElseGet(() -> StatCollector.translateToLocalFormatted("GT5U.gui.text.drill_offline_generic"))); + } + @NotNull protected String getFlowRatePerTick() { return GT_Utility.formatNumbers(this.mMaxProgresstime > 0 ? (mOilFlow / this.mMaxProgresstime) : 0); @@ -413,7 +439,8 @@ public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_D } } - return StatCollector.translateToLocalFormatted("GT5U.gui.text.pump_recovery", GT_Utility.formatNumbers(amount)); + return StatCollector + .translateToLocalFormatted("GT5U.gui.text.pump_recovery.1", GT_Utility.formatNumbers(amount)); } @Override @@ -423,19 +450,29 @@ public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_D .widget( TextWidget .dynamicString( - () -> StatCollector.translateToLocalFormatted("GT5U.gui.text.pump_fluid_type", clientFluidType)) + () -> EnumChatFormatting.GRAY + + StatCollector.translateToLocalFormatted("GT5U.gui.text.pump_fluid_type", clientFluidType)) .setSynced(false) .setTextAlignment(Alignment.CenterLeft) .setEnabled(widget -> getBaseMetaTileEntity().isActive() && workState == STATE_AT_BOTTOM)) .widget( - TextWidget.dynamicString( - () -> StatCollector - .translateToLocalFormatted("GT5U.gui.text.pump_rate", EnumChatFormatting.AQUA + clientPumpRate)) + TextWidget + .dynamicString( + () -> EnumChatFormatting.GRAY + + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.pump_rate.1", + EnumChatFormatting.AQUA + clientPumpRate) + + EnumChatFormatting.GRAY + + StatCollector.translateToLocal("GT5U.gui.text.pump_rate.2")) .setSynced(false) .setTextAlignment(Alignment.CenterLeft) .setEnabled(widget -> getBaseMetaTileEntity().isActive() && workState == STATE_AT_BOTTOM)) .widget( - TextWidget.dynamicString(() -> clientReservoirContents) + TextWidget + .dynamicString( + () -> EnumChatFormatting.GRAY + clientReservoirContents + + EnumChatFormatting.GRAY + + StatCollector.translateToLocal("GT5U.gui.text.pump_recovery.2")) .setSynced(false) .setTextAlignment(Alignment.CenterLeft) .setEnabled(widget -> getBaseMetaTileEntity().isActive() && workState == STATE_AT_BOTTOM)) diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java index 7986e52208..a48bb1dee8 100644 --- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java +++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java @@ -5,6 +5,7 @@ import static gregtech.api.enums.GT_HatchElement.InputBus; import static gregtech.api.enums.GT_HatchElement.InputHatch; import static gregtech.api.enums.GT_HatchElement.Maintenance; import static gregtech.api.enums.GT_HatchElement.OutputBus; +import static gregtech.api.enums.GT_Values.TIER_COLORS; import static gregtech.api.enums.GT_Values.VN; import static gregtech.api.metatileentity.BaseTileEntity.TOOLTIP_DELAY; @@ -46,6 +47,7 @@ import gregtech.api.enums.SoundResource; import gregtech.api.gui.modularui.GT_UITextures; import gregtech.api.gui.widgets.GT_LockedWhileActiveButton; import gregtech.api.interfaces.IHatchElement; +import gregtech.api.interfaces.metatileentity.IMetricsExporter; import gregtech.api.objects.GT_ChunkManager; import gregtech.api.objects.ItemData; import gregtech.api.recipe.check.CheckRecipeResultRegistry; @@ -56,7 +58,8 @@ import gregtech.api.util.GT_Utility; import gregtech.common.blocks.GT_Block_Ores_Abstract; import gregtech.common.blocks.GT_TileEntity_Ores; -public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTileEntity_DrillerBase { +public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTileEntity_DrillerBase + implements IMetricsExporter { private final List oreBlockPositions = new ArrayList<>(); protected int mTier = 1; @@ -444,9 +447,13 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile this.mEfficiencyIncrease = 10000; int tier = Math.max(1, GT_Utility.getTier(getMaxInputVoltage())); this.mEUt = -3 * (1 << (tier << 1)); - this.mMaxProgresstime = ((workState == STATE_DOWNWARD || workState == STATE_AT_BOTTOM) ? getBaseProgressTime() - : 80) / (1 << tier); - this.mMaxProgresstime = Math.max(1, this.mMaxProgresstime); + this.mMaxProgresstime = calculateMaxProgressTime(tier); + } + + private int calculateMaxProgressTime(int tier) { + return Math.max( + 1, + ((workState == STATE_DOWNWARD || workState == STATE_AT_BOTTOM) ? getBaseProgressTime() : 80) / (1 << tier)); } private ItemStack[] getOutputByDrops(Collection oreBlockDrops) { @@ -551,15 +558,20 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile .getDisplayName(); final GT_Multiblock_Tooltip_Builder tt = new GT_Multiblock_Tooltip_Builder(); + final int baseCycleTime = calculateMaxProgressTime(getMinTier()); tt.addMachineType("Miner") .addInfo("Controller Block for the Ore Drilling Plant " + (tierSuffix != null ? tierSuffix : "")) .addInfo("Use a Screwdriver to configure block radius") - .addInfo("Maximum radius is " + (getRadiusInChunks() << 4) + " blocks") + .addInfo("Maximum radius is " + GT_Utility.formatNumbers((long) getRadiusInChunks() << 4) + " blocks") .addInfo("Use Soldering iron to turn off chunk mode") .addInfo("Use Wire Cutter to toggle replacing mined blocks with cobblestone") .addInfo("In chunk mode, working area center is the chunk corner nearest to the drill") .addInfo("Gives ~3x as much crushed ore vs normal processing") - .addInfo("Fortune bonus of " + (mTier + 3) + ". Only works on small ores") + .addInfo("Fortune bonus of " + GT_Utility.formatNumbers(mTier + 3) + ". Only works on small ores") + .addInfo("Minimum energy hatch tier: " + TIER_COLORS[getMinTier()] + VN[getMinTier()]) + .addInfo( + "Base cycle time: " + (baseCycleTime < 20 ? GT_Utility.formatNumbers(baseCycleTime) + " ticks" + : GT_Utility.formatNumbers(baseCycleTime / 20) + " seconds")) .addSeparator() .beginStructureBlock(3, 7, 3, false) .addController("Front bottom") @@ -582,7 +594,7 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile .widget( TextWidget .dynamicString( - () -> StatCollector.translateToLocalFormatted( + () -> EnumChatFormatting.GRAY + StatCollector.translateToLocalFormatted( "GT5U.gui.text.drill_ores_left_chunk", GT_Utility.formatNumbers(clientOreListSize))) .setSynced(false) @@ -593,7 +605,7 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile .widget( TextWidget .dynamicString( - () -> StatCollector.translateToLocalFormatted( + () -> EnumChatFormatting.GRAY + StatCollector.translateToLocalFormatted( "GT5U.gui.text.drill_ores_left_layer", GT_Utility.formatNumbers(clientYHead), GT_Utility.formatNumbers(clientOreListSize))) @@ -604,7 +616,7 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile .widget( TextWidget .dynamicString( - () -> StatCollector.translateToLocalFormatted( + () -> EnumChatFormatting.GRAY + StatCollector.translateToLocalFormatted( "GT5U.gui.text.drill_chunks_left", GT_Utility.formatNumbers(clientCurrentChunk), GT_Utility.formatNumbers(clientTotalChunks))) @@ -686,6 +698,35 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile } @Override + public @NotNull List reportMetrics() { + if (getBaseMetaTileEntity().isActive()) { + return switch (workState) { + case STATE_AT_BOTTOM -> ImmutableList.of( + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.drill_ores_left_chunk", + GT_Utility.formatNumbers(oreBlockPositions.size())), + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.drill_chunks_left", + GT_Utility.formatNumbers(getChunkNumber()), + GT_Utility.formatNumbers(getTotalChunkCount()))); + case STATE_DOWNWARD -> ImmutableList.of( + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.drill_ores_left_layer", + getYHead(), + GT_Utility.formatNumbers(oreBlockPositions.size()))); + case STATE_UPWARD, STATE_ABORT -> ImmutableList + .of(StatCollector.translateToLocal("GT5U.gui.text.retracting_pipe")); + + default -> ImmutableList.of(); + }; + } + + return ImmutableList.of( + getFailureReason() + .map(reason -> StatCollector.translateToLocalFormatted("GT5U.gui.text.drill_offline_reason", reason)) + .orElseGet(() -> StatCollector.translateToLocalFormatted("GT5U.gui.text.drill_offline_generic"))); + } + public boolean supportsVoidProtection() { return true; } diff --git a/src/main/java/gregtech/loaders/postload/recipes/AssemblerRecipes.java b/src/main/java/gregtech/loaders/postload/recipes/AssemblerRecipes.java index 09f1ac9e91..897ea7f57d 100644 --- a/src/main/java/gregtech/loaders/postload/recipes/AssemblerRecipes.java +++ b/src/main/java/gregtech/loaders/postload/recipes/AssemblerRecipes.java @@ -2956,6 +2956,19 @@ public class AssemblerRecipes implements Runnable { .eut(TierEU.RECIPE_EV) .addTo(sAssemblerRecipes); + GT_Values.RA.stdBuilder() + .itemInputs( + GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Titanium, 1), + ItemList.NC_SensorKit.get(1), + ItemList.Emitter_EV.get(1), + getModItem(AppliedEnergistics2.ID, "item.ItemMultiMaterial", 1, 28), + GT_Utility.getIntegratedCircuit(2)) + .itemOutputs(ItemList.Cover_Metrics_Transmitter.get(1)) + .fluidInputs(Materials.SolderingAlloy.getMolten(INGOTS)) + .duration(5 * SECONDS) + .eut(TierEU.RECIPE_EV) + .addTo(sAssemblerRecipes); + ItemStack[] plates = new ItemStack[] { GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Iron, 1L), GT_OreDictUnificator.get(OrePrefixes.plate, Materials.WroughtIron, 1L), GT_OreDictUnificator.get(OrePrefixes.plate, Materials.Aluminium, 1L) }; diff --git a/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java b/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java index fdfe2365c4..056ebadca9 100644 --- a/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java +++ b/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java @@ -169,6 +169,17 @@ public class GT_Loader_Item_Block_And_Fluid implements Runnable { ? new GT_Generic_Item("sensorcard", "GregTech Sensor Card", "Nuclear Control not installed", false) : tItem); + Item advSensorCard = (Item) GT_Utility + .callConstructor("gregtech.common.items.GT_AdvancedSensorCard_Item", 0, null, false); + ItemList.NC_AdvancedSensorCard.set( + advSensorCard == null + ? new GT_Generic_Item( + "advancedsensorcard", + "GregTech Advanced Sensor Card", + "Nuclear Control not installed", + false) + : advSensorCard); + ItemList.Neutron_Reflector .set(new GT_NeutronReflector_Item("neutronreflector", "Iridium Neutron Reflector", 0)); ItemList.Reactor_Coolant_He_1 -- cgit