aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/common')
-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
15 files changed, 1283 insertions, 26 deletions
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;
}