From eb03b825cf6dff659614abc91be2f29be7301997 Mon Sep 17 00:00:00 2001 From: DStrand1 Date: Thu, 12 Aug 2021 21:57:28 -0500 Subject: [PATCH] port Native EU to FE --- .../api/capability/GregtechCapabilities.java | 14 + .../api/capability/impl/EUToFEProvider.java | 67 ++++ .../api/capability/impl/GTEnergyWrapper.java | 338 ++++++++++++++++++ .../java/gregtech/common/ConfigHolder.java | 14 + 4 files changed, 433 insertions(+) create mode 100644 src/main/java/gregtech/api/capability/impl/EUToFEProvider.java create mode 100644 src/main/java/gregtech/api/capability/impl/GTEnergyWrapper.java diff --git a/src/main/java/gregtech/api/capability/GregtechCapabilities.java b/src/main/java/gregtech/api/capability/GregtechCapabilities.java index a12a0f5d04a..dd765489b68 100644 --- a/src/main/java/gregtech/api/capability/GregtechCapabilities.java +++ b/src/main/java/gregtech/api/capability/GregtechCapabilities.java @@ -1,12 +1,20 @@ package gregtech.api.capability; +import gregtech.api.GTValues; +import gregtech.api.capability.impl.EUToFEProvider; import gregtech.api.capability.tool.ICutterItem; import gregtech.api.capability.tool.IScrewdriverItem; import gregtech.api.capability.tool.ISoftHammerItem; import gregtech.api.capability.tool.IWrenchItem; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +@Mod.EventBusSubscriber(modid = GTValues.MODID) public class GregtechCapabilities { @CapabilityInject(IEnergyContainer.class) @@ -33,4 +41,10 @@ public class GregtechCapabilities { @CapabilityInject(IMultiblockController.class) public static Capability CAPABILITY_MULTIBLOCK_CONTROLLER = null; + private static final ResourceLocation CAPABILITY_EU_TO_FE = new ResourceLocation(GTValues.MODID, "fe_capability"); + + @SubscribeEvent + public static void attachTileCapability(AttachCapabilitiesEvent event) { + event.addCapability(CAPABILITY_EU_TO_FE, new EUToFEProvider(event.getObject())); + } } diff --git a/src/main/java/gregtech/api/capability/impl/EUToFEProvider.java b/src/main/java/gregtech/api/capability/impl/EUToFEProvider.java new file mode 100644 index 00000000000..70a62ed3287 --- /dev/null +++ b/src/main/java/gregtech/api/capability/impl/EUToFEProvider.java @@ -0,0 +1,67 @@ +package gregtech.api.capability.impl; + +import gregtech.api.capability.GregtechCapabilities; +import gregtech.common.ConfigHolder; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.energy.CapabilityEnergy; + +import javax.annotation.Nonnull; +import java.util.concurrent.locks.ReentrantLock; + +public class EUToFEProvider implements ICapabilityProvider { + + private final TileEntity tileEntity; + private GTEnergyWrapper wrapper; + + /** + * Lock used for concurrency protection between hasCapability and getCapability. + */ + ReentrantLock lock = new ReentrantLock(); + + public EUToFEProvider(TileEntity tileEntity) { + this.tileEntity = tileEntity; + } + + @Override + public boolean hasCapability(@Nonnull Capability capability, EnumFacing facing) { + + if (!ConfigHolder.U.energyOptions.nativeEUToFE) + return false; + + if (lock.isLocked() || (capability != CapabilityEnergy.ENERGY && capability != GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER)) + return false; + + // Wrap FE Machines with a GTEU EnergyContainer + if (wrapper == null) wrapper = new GTEnergyWrapper(tileEntity); + + lock.lock(); + try { + return wrapper.isValid(facing); + } finally { + lock.unlock(); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getCapability(@Nonnull Capability capability, EnumFacing facing) { + + if (!ConfigHolder.U.energyOptions.nativeEUToFE) + return null; + + if (lock.isLocked() || !hasCapability(capability, facing)) + return null; + + if (wrapper == null) wrapper = new GTEnergyWrapper(tileEntity); + + lock.lock(); + try { + return wrapper.isValid(facing) ? (T) wrapper : null; + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/gregtech/api/capability/impl/GTEnergyWrapper.java b/src/main/java/gregtech/api/capability/impl/GTEnergyWrapper.java new file mode 100644 index 00000000000..e758843f3ce --- /dev/null +++ b/src/main/java/gregtech/api/capability/impl/GTEnergyWrapper.java @@ -0,0 +1,338 @@ +package gregtech.api.capability.impl; + +import gregtech.api.GTValues; +import gregtech.api.capability.IEnergyContainer; +import gregtech.api.util.GTUtility; +import gregtech.common.ConfigHolder; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.energy.CapabilityEnergy; +import net.minecraftforge.energy.IEnergyStorage; + +import javax.annotation.Nullable; + +public class GTEnergyWrapper implements IEnergyContainer { + + /** + * Capability Provider of the FE TileEntity for the EU-capability. + */ + private final ICapabilityProvider upvalue; + + /** + * Capability holder for the FE-capability. + */ + private final IEnergyStorage[] facesFE = new IEnergyStorage[7]; + + /** + * Internally used FE Buffer so that a very large packet of EU is not partially destroyed + * on the conversion to FE. This is hidden from the player, but ensures that no energy + * is ever lost on conversion, no matter the voltage tier or FE storage abilities. + */ + private long feBuffer = 0; + + protected GTEnergyWrapper(ICapabilityProvider upvalue) { + this.upvalue = upvalue; + } + + /** + * Test this EnergyContainer for sided Capabilities. + * + * @param side The side of the TileEntity to test for the Capability + * @return True if side has Capability, false otherwise + */ + protected boolean isValid(EnumFacing side) { + + if (upvalue.hasCapability(CapabilityEnergy.ENERGY, side)) + return true; + + if (side == null) { + for (EnumFacing face2 : EnumFacing.VALUES) { + if (upvalue.hasCapability(CapabilityEnergy.ENERGY, face2)) { + return true; + } + } + } + return false; + } + + private IEnergyStorage getStorageCap() { + + IEnergyStorage container = def(); + + if (container != null && container.getMaxEnergyStored() > 0) + return container; + + for (EnumFacing face : EnumFacing.VALUES) { + container = facesFE[face.getIndex()]; + + if (container == null) { + container = upvalue.getCapability(CapabilityEnergy.ENERGY, face); + facesFE[face.getIndex()] = container; + } + + if (container != null && container.getMaxEnergyStored() > 0) + return container; + } + + return container; + } + + private IEnergyStorage getAcceptingCap() { + + IEnergyStorage container = def(); + + if (container != null && container.canReceive()) + return container; + + for (EnumFacing face : EnumFacing.VALUES) { + container = facesFE[face.getIndex()]; + + if (container == null) { + container = upvalue.getCapability(CapabilityEnergy.ENERGY, face); + facesFE[face.getIndex()] = container; + } + + if (container != null && container.canReceive()) + return container; + } + + return container; + } + + @Override + public long acceptEnergyFromNetwork(EnumFacing facing, long voltage, long amperage) { + + int faceID = facing == null ? 6 : facing.getIndex(); + IEnergyStorage container = facesFE[faceID]; + + if (container == null) { + container = upvalue.getCapability(CapabilityEnergy.ENERGY, facing); + facesFE[faceID] = container; + } + + if (container == null) + return 0L; + + int receive = 0; + + // Try to use the internal buffer before consuming a new packet + if (feBuffer > 0) { + + receive = container.receiveEnergy(safeCastLongToInt(feBuffer), true); + + if (receive == 0) + return 0; + + // Internal Buffer could provide the max RF the consumer could consume + if (feBuffer > receive) { + feBuffer -= receive; + container.receiveEnergy(receive, false); + return 0; + + // Buffer could not provide max value, save the remainder and continue processing + } else { + receive = safeCastLongToInt(feBuffer); + feBuffer = 0; + } + } + + long maxPacket = (long) (voltage * ConfigHolder.U.energyOptions.rfRatio); + long maximalValue = maxPacket * amperage; + + // Try to consume our remainder buffer plus a fresh packet + if (receive != 0) { + + int consumable = container.receiveEnergy(safeCastLongToInt(maximalValue + receive), true); + + // Machine unable to consume any power + // Probably unnecessary in this case, but just to be safe + if (consumable == 0) + return 0; + + // Only able to consume our buffered amount + if (consumable == receive) { + container.receiveEnergy(consumable, false); + return 0; + } + + // Able to consume our full packet as well as our remainder buffer + if (consumable == maximalValue + receive) { + container.receiveEnergy(consumable, false); + return amperage; + } + + int newPower = consumable - receive; + + // Able to consume buffered amount plus an even amount of packets (no buffer needed) + if (newPower % maxPacket == 0) { + return container.receiveEnergy(consumable, false) / maxPacket; + } + + // Able to consume buffered amount plus some amount of power with a packet remainder + int ampsToConsume = safeCastLongToInt((newPower / maxPacket) + 1); + feBuffer = safeCastLongToInt((maxPacket * ampsToConsume) - consumable); + container.receiveEnergy(consumable, false); + return ampsToConsume; + + // Else try to draw 1 full packet + } else { + + int consumable = container.receiveEnergy(safeCastLongToInt(maximalValue), true); + + // Machine unable to consume any power + if (consumable == 0) + return 0; + + // Able to accept the full amount of power + if (consumable == maximalValue) { + container.receiveEnergy(consumable, false); + return amperage; + } + + // Able to consume an even amount of packets + if (consumable % maxPacket == 0) { + return container.receiveEnergy(consumable, false) / maxPacket; + } + + // Able to consume power with some amount of power remainder in the packet + int ampsToConsume = safeCastLongToInt((consumable / maxPacket) + 1); + feBuffer = safeCastLongToInt((maxPacket * ampsToConsume) - consumable); + container.receiveEnergy(consumable, false); + return ampsToConsume; + } + } + + @Override + public long changeEnergy(long delta) { + + IEnergyStorage container = getStorageCap(); + + if (container == null || delta == 0) + return 0; + + long energyValue = (long) (delta * ConfigHolder.U.energyOptions.rfRatio); + if (energyValue > Integer.MAX_VALUE) + energyValue = Integer.MAX_VALUE; + + if (delta < 0L) { + + int extract = container.extractEnergy(safeCastLongToInt(energyValue), true); + + if (extract != ConfigHolder.U.energyOptions.rfRatio) + extract -= extract % ConfigHolder.U.energyOptions.rfRatio; + + return (long) (container.extractEnergy(extract, false) / ConfigHolder.U.energyOptions.rfRatio); + + } else { + + int receive = container.receiveEnergy((int) energyValue, true); + + if (receive != ConfigHolder.U.energyOptions.rfRatio) + receive -= receive % ConfigHolder.U.energyOptions.rfRatio; + + return (long) (container.receiveEnergy(receive, false) / ConfigHolder.U.energyOptions.rfRatio); + } + } + + @Nullable + private IEnergyStorage def() { + + if (facesFE[6] == null) + facesFE[6] = upvalue.getCapability(CapabilityEnergy.ENERGY, null); + + return facesFE[6]; + } + + @Override + public long getEnergyCapacity() { + IEnergyStorage cap = getStorageCap(); + + if (cap == null) + return 0L; + + return (long) (cap.getMaxEnergyStored() / ConfigHolder.U.energyOptions.rfRatio); + } + + @Override + public long getEnergyStored() { + IEnergyStorage cap = getStorageCap(); + + if (cap == null) + return 0L; + + return (long) (cap.getEnergyStored() / ConfigHolder.U.energyOptions.rfRatio); + } + + @Override + public long getInputAmperage() { + IEnergyStorage container = getAcceptingCap(); + + if (container == null) + return 0; + + long voltage = getInputVoltage(); + + return voltage == 0 ? 0 : 2; + } + + @Override + public long getInputVoltage() { + IEnergyStorage container = getStorageCap(); + + if (container == null) + return 0; + + long maxInput = container.receiveEnergy(Integer.MAX_VALUE, true); + + if (maxInput == 0) + return 0; + + maxInput = (long) (maxInput / ConfigHolder.U.energyOptions.rfRatio); + return GTValues.V[GTUtility.getTierByVoltage(maxInput)]; + } + + @Override + public boolean inputsEnergy(EnumFacing facing) { + + int faceID = facing == null ? 6 : facing.getIndex(); + IEnergyStorage container = facesFE[faceID]; + + if (container == null) { + container = upvalue.getCapability(CapabilityEnergy.ENERGY, facing); + facesFE[faceID] = container; + } + + if (container == null) + return false; + + return container.canReceive(); + } + + /** + * Wrapped FE-consumers should not be able to output EU. + */ + @Override + public boolean outputsEnergy(EnumFacing facing) { + return false; + } + + /** + * Hide this TileEntity EU-capability in TOP. Allows FE-machines to + * "silently" accept EU without showing their charge in EU in TOP. + * Let the machine display it in FE instead, however it chooses to. + */ + @Override + public boolean isOneProbeHidden() { + return true; + } + + /** + * Safely cast a Long to an Int without overflow. + * + * @param v The Long value to cast to an Int. + * @return v, casted to Int, or Integer.MAX_VALUE if it would overflow. + */ + public static int safeCastLongToInt(long v) { + return v > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) v; + } +} diff --git a/src/main/java/gregtech/common/ConfigHolder.java b/src/main/java/gregtech/common/ConfigHolder.java index 957f7840c77..be694223727 100644 --- a/src/main/java/gregtech/common/ConfigHolder.java +++ b/src/main/java/gregtech/common/ConfigHolder.java @@ -175,6 +175,10 @@ public static class UnofficialOptions { @Config.Name("GregTech 6 Options") public GT6 GT6 = new GT6(); + @Config.Comment("Config category for energy compatibility features") + @Config.Name("Energy Compatibility Options") + public EnergyCompatibility energyOptions = new EnergyCompatibility(); + @Config.Comment("Should recipes for EV and IV Drills be enabled, which may cause large amounts of lag when used on some low-end devices? Default: true") @Config.RequiresMcRestart public boolean registerRecipesForHighTierDrills = true; @@ -256,5 +260,15 @@ public static class GT6 { @Config.RequiresMcRestart public boolean plateWrenches = false; } + + public static class EnergyCompatibility { + + @Config.Comment("Enable Native GTEU to Forge Energy (RF and alike) on GT Cables and Wires. Default: true") + public boolean nativeEUToFE = true; + + @Config.Comment("GTEU to Forge Energy (RF and alike) ratio. Default: 4 FE to 1 EU") + @Config.RangeDouble() // to ensure positive number + public double rfRatio = 4; + } } }