aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/gregtech/api/enums/GT_Values.java2
-rw-r--r--src/main/java/gregtech/api/enums/ItemList.java4
-rw-r--r--src/main/java/gregtech/api/enums/Textures.java1
-rw-r--r--src/main/java/gregtech/api/interfaces/metatileentity/IMetricsExporter.java28
-rw-r--r--src/main/java/gregtech/api/metatileentity/BaseMetaPipeEntity.java8
-rw-r--r--src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java9
-rw-r--r--src/main/java/gregtech/api/metatileentity/CoverableTileEntity.java101
-rw-r--r--src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java36
-rw-r--r--src/main/java/gregtech/common/GT_Proxy.java2
-rw-r--r--src/main/java/gregtech/common/covers/CoverInfo.java8
-rw-r--r--src/main/java/gregtech/common/covers/GT_Cover_Metrics_Transmitter.java212
-rw-r--r--src/main/java/gregtech/common/events/BaseMetricsCoverEvent.java21
-rw-r--r--src/main/java/gregtech/common/events/MetricsCoverDataEvent.java37
-rw-r--r--src/main/java/gregtech/common/events/MetricsCoverHostDeconstructedEvent.java16
-rw-r--r--src/main/java/gregtech/common/events/MetricsCoverSelfDestructEvent.java15
-rw-r--r--src/main/java/gregtech/common/items/GT_AdvancedSensorCard_Item.java323
-rw-r--r--src/main/java/gregtech/common/items/GT_MetaGenerated_Item_03.java20
-rw-r--r--src/main/java/gregtech/common/items/GT_SensorCard_Item.java2
-rw-r--r--src/main/java/gregtech/common/items/behaviors/Behaviour_Cover_Tool.java24
-rw-r--r--src/main/java/gregtech/common/misc/GlobalMetricsCoverDatabase.java492
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java27
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java51
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java59
-rw-r--r--src/main/java/gregtech/loaders/postload/recipes/AssemblerRecipes.java13
-rw-r--r--src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java11
-rw-r--r--src/main/resources/assets/gregtech/lang/en_US.lang32
-rw-r--r--src/main/resources/assets/gregtech/textures/blocks/iconsets/OVERLAY_METRICS_TRANSMITTER.pngbin0 -> 1810 bytes
-rw-r--r--src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcard.pngbin0 -> 7624 bytes
-rw-r--r--src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcardburned.pngbin0 -> 6106 bytes
-rw-r--r--src/main/resources/assets/gregtech/textures/items/gt.metaitem.03/232.pngbin0 -> 2008 bytes
30 files changed, 1499 insertions, 55 deletions
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.
+ * <p>
+ * 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<String> 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<ForgeDirection, ISerializableObject> 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<String> getCoverTabTooltip(ForgeDirection side) {
+ protected List<String> 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<String> 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<String> 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<ForgeDirection, ISerializableObject> collectCoverData() {
+ final ImmutableMap.Builder<ForgeDirection, ISerializableObject> 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<ForgeDirection, ISerializableObject> 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<ForgeDirection, ISerializableObject> readClientCoverData(@NotNull PacketBuffer buffer) {
+ ImmutableMap.Builder<ForgeDirection, ISerializableObject> 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<T extends ISerializableObject> {
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<T extends ISerializableObject> {
public String trans(String aNr, String aEnglish) {
return GT_Utility.trans(aNr, aEnglish);
}
+
+ public boolean allowsCopyPasteTool() {
+ return true;
+ }
+
+ @NotNull
+ public final List<String> 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<String> 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<String> 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.
+ * <p>
+ * 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<GT_Cover_Metrics_Transmitter.MetricsTransmitterData> {
+
+ @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<String> 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<String> 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<String> payload;
+
+ @NotNull
+ private final GlobalMetricsCoverDatabase.Coordinates coordinates;
+
+ public MetricsCoverDataEvent(@NotNull UUID frequency, @NotNull List<String> payload,
+ @NotNull GlobalMetricsCoverDatabase.Coordinates coordinates) {
+ super(frequency);
+ this.payload = payload;
+ this.coordinates = coordinates;
+ }
+
+ @NotNull
+ public List<String> 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<PanelString> SELF_DESTRUCTED_OUTPUT = ImmutableList
+ .of(prebakePanelString(EnumChatFormatting.OBFUSCATED + "critical error" + EnumChatFormatting.RESET, true));
+
+ private static final ImmutableList<PanelString> 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<PanelString> 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<String> tooltip,
+ final boolean p_77624_4_) {
+ super.addInformation(itemStack, player, tooltip, p_77624_4_);
+
+ final Optional<State> 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<ItemStack> 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<PanelString> 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<PanelString> 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<String> 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<PanelSetting> 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<State> getCardState(ICardWrapper card) {
+ return getCardState(card.getItemStack());
+ }
+
+ @NotNull
+ private Optional<State> 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<UUID> 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<String> 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<GlobalMetricsCoverDatabase.Data> getDataFromDatabase(ICardWrapper card) {
+ return getDataFromDatabase(card.getItemStack());
+ }
+
+ @NotNull
+ private Optional<GlobalMetricsCoverDatabase.Data> 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.
+ * <br />
+ * <br />
+ * 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<UUID, Data> DATABASE = new ConcurrentHashMap<>();
+ /** Used to speed up event handlers dealing with block breaking and explosions. Not persisted. */
+ private static final Map<Coordinates, Set<UUID>> 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<UUID> 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<UUID> 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<Data> 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<String> 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<UUID> 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<UUID> set = REVERSE_LOOKUP.get(data.coordinates);
+ set.remove(frequency);
+ if (set.isEmpty()) {
+ REVERSE_LOOKUP.remove(data.coordinates);
+ }
+ }
+ });
+ }
+
+ private static Stream<UUID> 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.
+ * <p>
+ * 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<String> 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<String> payload) {
+ this.state = state;
+ this.payload = payload;
+ this.coordinates = null;
+ }
+
+ public Data(@NotNull State state, @Nullable List<String> 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<List<String>> 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<Coordinates> 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<Integer, State> 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<State> 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;
}
@@ -585,6 +591,25 @@ public abstract class GT_MetaTileEntity_DrillerBase
}
/**
+ * 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<String> 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<Chunk> 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<String> 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<ChunkPosition> 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<ItemStack> 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<String> 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
diff --git a/src/main/resources/assets/gregtech/lang/en_US.lang b/src/main/resources/assets/gregtech/lang/en_US.lang
index ab69f948da..810145245c 100644
--- a/src/main/resources/assets/gregtech/lang/en_US.lang
+++ b/src/main/resources/assets/gregtech/lang/en_US.lang
@@ -9,6 +9,8 @@ itemGroup.GregTech.Main=Main
itemGroup.GregTech.Materials=Materials
itemGroup.GregTech.Ores=Ores
+item.gt.advancedsensorcard.name=GregTech Advanced Sensor Card
+
GT5U.autoplace.error.no_hatch=§cSuggested to place hatch §4%s§c but none was found
GT5U.autoplace.error.no_mte.id=§cSuggested to place machine with meta ID §4%s§c but none was found
GT5U.autoplace.error.no_mte.class_name=§cSuggested to place machine with class name §4%d§c but none was found
@@ -390,9 +392,11 @@ GT5U.gui.text.backfiller_no_concrete=§7No liquid concrete
GT5U.gui.text.backfiller_finished=§aWork complete
GT5U.gui.text.backfiller_working=§aPouring concrete
GT5U.gui.text.backfiller_current_area=§7Filling at y-level: §a%s
-GT5U.gui.text.pump_fluid_type=§7Fluid: §a%s
-GT5U.gui.text.pump_rate=§7Pumping rate: %s§7 L/t
-GT5U.gui.text.pump_recovery=§7Recovering §b%s§7 L per operation
+GT5U.gui.text.pump_fluid_type=Fluid: §a%s
+GT5U.gui.text.pump_rate.1=Pumping rate: %s§r
+GT5U.gui.text.pump_rate.2= L/t
+GT5U.gui.text.pump_recovery.1=Recovering §b%s§r
+GT5U.gui.text.pump_recovery.2= L per operation
GT5U.gui.text.not_enough_energy=§7Not enough energy
GT5U.gui.text.power_overflow=§7Power overflowed
GT5U.gui.text.duration_overflow=§7Processing time overflowed
@@ -401,9 +405,11 @@ GT5U.gui.text.insufficient_heat=§7Recipe needs more heat to start. Required: %s
GT5U.gui.text.insufficient_machine_tier=§7Recipe needs higher structure tier. Required: %s
GT5U.gui.text.insufficient_startup_power=§7Recipe needs higher startup power. Required: %s
GT5U.gui.text.internal_error=§4Recipe was found, but had internal error
-GT5U.gui.text.drill_ores_left_chunk=§7Ores left in current chunk: §a%s
-GT5U.gui.text.drill_ores_left_layer=§7Ores left at y-level %s: §a%s
-GT5U.gui.text.drill_chunks_left=§7Drilling chunk: §a%s / %s
+GT5U.gui.text.drill_ores_left_chunk=Ores left in current chunk: §a%s
+GT5U.gui.text.drill_ores_left_layer=Ores left at y-level %s: §a%s
+GT5U.gui.text.drill_chunks_left=Drilling chunk: §a%s / %s
+GT5U.gui.text.drill_offline_reason=Drill Offline: %s
+GT5U.gui.text.drill_offline_generic=Drill Offline
GT5U.item.programmed_circuit.select.header=Reprogram Circuit
@@ -1355,3 +1361,17 @@ gt.multiBlock.controller.cokeOven=Coke Oven
gt.locker.waila_armor_slot_none=Slot %s: §7None
gt.locker.waila_armor_slot_generic=Slot %s: §e%s
gt.locker.waila_armor_slot_charged=Slot %s: §e%s§r (%s%s%%§r)
+
+gt.db.metrics_cover.coords=§a%s§r, §a%s§r, §a%s§r
+
+gt.item.adv_sensor_card.dimension=Dimension: §a%s
+gt.item.adv_sensor_card.coords=Coordinates: %s
+gt.item.adv_sensor_card.tooltip.recipe_hint=Created by attaching a Metrics Transmitter cover, no standard recipe
+gt.item.adv_sensor_card.tooltip.fried.1=§o§dThis thing looks completely fried...
+gt.item.adv_sensor_card.tooltip.fried.2=§cDestroyed due to metrics transmitter being removed
+gt.item.adv_sensor_card.tooltip.fried.3=§cor the machine being destroyed
+gt.item.adv_sensor_card.tooltip.machine=Machine: §b%s
+gt.item.adv_sensor_card.tooltip.frequency=Frequency: §b%s
+gt.item.adv_sensor_card.error.deconstructed.1=-ERROR- Machine Deconstructed
+gt.item.adv_sensor_card.error.deconstructed.2=Place machine again to re-enable
+gt.item.adv_sensor_card.error.no_data=No data found
diff --git a/src/main/resources/assets/gregtech/textures/blocks/iconsets/OVERLAY_METRICS_TRANSMITTER.png b/src/main/resources/assets/gregtech/textures/blocks/iconsets/OVERLAY_METRICS_TRANSMITTER.png
new file mode 100644
index 0000000000..5be635f133
--- /dev/null
+++ b/src/main/resources/assets/gregtech/textures/blocks/iconsets/OVERLAY_METRICS_TRANSMITTER.png
Binary files differ
diff --git a/src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcard.png b/src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcard.png
new file mode 100644
index 0000000000..f9b6d81f02
--- /dev/null
+++ b/src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcard.png
Binary files differ
diff --git a/src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcardburned.png b/src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcardburned.png
new file mode 100644
index 0000000000..7ed3a05488
--- /dev/null
+++ b/src/main/resources/assets/gregtech/textures/items/gt.advancedsensorcardburned.png
Binary files differ
diff --git a/src/main/resources/assets/gregtech/textures/items/gt.metaitem.03/232.png b/src/main/resources/assets/gregtech/textures/items/gt.metaitem.03/232.png
new file mode 100644
index 0000000000..fac1800649
--- /dev/null
+++ b/src/main/resources/assets/gregtech/textures/items/gt.metaitem.03/232.png
Binary files differ