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) { +