package gregtech.common.covers;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidHandler;
import com.google.common.io.ByteArrayDataInput;
import com.gtnewhorizons.modularui.api.NumberFormatMUI;
import com.gtnewhorizons.modularui.api.drawable.Text;
import com.gtnewhorizons.modularui.api.screen.ModularWindow;
import com.gtnewhorizons.modularui.common.widget.TextWidget;
import gregtech.api.gui.modularui.CoverUIBuildContext;
import gregtech.api.gui.modularui.GTUITextures;
import gregtech.api.interfaces.ITexture;
import gregtech.api.interfaces.tileentity.ICoverable;
import gregtech.api.interfaces.tileentity.IMachineProgress;
import gregtech.api.util.CoverBehaviorBase;
import gregtech.api.util.GTUtility;
import gregtech.api.util.ISerializableObject;
import gregtech.common.gui.modularui.widget.CoverDataControllerWidget;
import gregtech.common.gui.modularui.widget.CoverDataFollowerNumericWidget;
import gregtech.common.gui.modularui.widget.CoverDataFollowerToggleButtonWidget;
import io.netty.buffer.ByteBuf;
/**
* Cover variable
*
*
* 1111 1111 1111 1111 1111 1111 1111 1111
* |- interval-| |- flow rate 2 compl. -|
* ^ export?
*
*
* Concat export and flow rate 2 compl. together to get actual flow rate. A positive actual flow rate is export, and
* vice versa.
*
* Interval is an unsigned 11 bit integer minus 1, so the range is 1~2048. The stored bits will be flipped bitwise if
* speed is negative. This way, `0` means 1tick interval, while `-1` means 1 tick interval as well, preserving the
* legacy behavior.
*/
public class CoverFluidRegulator extends CoverBehaviorBase {
private static final int SPEED_LENGTH = 20;
private static final int TICK_RATE_LENGTH = Integer.SIZE - SPEED_LENGTH - 1;
private static final int TICK_RATE_MIN = 1;
private static final int TICK_RATE_MAX = (-1 >>> (Integer.SIZE - TICK_RATE_LENGTH)) + TICK_RATE_MIN;
private static final int TICK_RATE_BITMASK = (TICK_RATE_MAX - TICK_RATE_MIN) << SPEED_LENGTH;
public final int mTransferRate;
private boolean allowFluid = false;
public CoverFluidRegulator(int aTransferRate, ITexture coverTexture) {
super(FluidRegulatorData.class, coverTexture);
this.mTransferRate = aTransferRate;
}
@Override
public FluidRegulatorData createDataObject(int aLegacyData) {
return new FluidRegulatorData(aLegacyData);
}
@Override
public FluidRegulatorData createDataObject() {
return new FluidRegulatorData();
}
@Override
protected boolean isRedstoneSensitiveImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable,
ICoverable aTileEntity, long aTimer) {
return aCoverVariable.condition.isRedstoneSensitive();
}
@Override
protected FluidRegulatorData doCoverThingsImpl(ForgeDirection side, byte aInputRedstone, int aCoverID,
FluidRegulatorData aCoverVariable, ICoverable aTileEntity, long aTimer) {
if (aCoverVariable.speed == 0 || !aCoverVariable.condition.isAllowedToWork(side, aCoverID, aTileEntity)) {
return aCoverVariable;
}
if ((aTileEntity instanceof IFluidHandler)) {
final IFluidHandler tTank1;
final IFluidHandler tTank2;
final ForgeDirection directionFrom;
final ForgeDirection directionTo;
if (aCoverVariable.speed > 0) {
tTank2 = aTileEntity.getITankContainerAtSide(side);
tTank1 = (IFluidHandler) aTileEntity;
directionFrom = side;
directionTo = side.getOpposite();
} else {
tTank1 = aTileEntity.getITankContainerAtSide(side);
tTank2 = (IFluidHandler) aTileEntity;
directionFrom = side.getOpposite();
directionTo = side;
}
if (tTank1 != null && tTank2 != null) {
allowFluid = true;
GTUtility
.moveFluid(tTank1, tTank2, directionFrom, Math.abs(aCoverVariable.speed), this::canTransferFluid);
allowFluid = false;
}
}
return aCoverVariable;
}
private void adjustSpeed(EntityPlayer aPlayer, FluidRegulatorData aCoverVariable, int scale) {
int tSpeed = aCoverVariable.speed;
tSpeed += scale;
int tTickRate = aCoverVariable.tickRate;
if (Math.abs(tSpeed) > mTransferRate * tTickRate) {
tSpeed = mTransferRate * tTickRate * (tSpeed > 0 ? 1 : -1);
GTUtility.sendChatToPlayer(aPlayer, GTUtility.trans("316", "Pump speed limit reached!"));
}
if (tTickRate == 1) {
GTUtility.sendChatToPlayer(
aPlayer,
GTUtility.trans("048", "Pump speed: ") + tSpeed
+ GTUtility.trans("049", "L/tick ")
+ tSpeed * 20
+ GTUtility.trans("050", "L/sec"));
} else {
GTUtility.sendChatToPlayer(
aPlayer,
String.format(
GTUtility.trans("207", "Pump speed: %dL every %d ticks, %.2f L/sec on average"),
tSpeed,
tTickRate,
tSpeed * 20d / tTickRate));
}
}
@Override
public FluidRegulatorData onCoverScrewdriverClickImpl(ForgeDirection side, int aCoverID,
FluidRegulatorData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) {
if (GTUtility.getClickedFacingCoords(side, aX, aY, aZ)[0] >= 0.5F) {
adjustSpeed(aPlayer, aCoverVariable, aPlayer.isSneaking() ? 256 : 16);
} else {
adjustSpeed(aPlayer, aCoverVariable, aPlayer.isSneaking() ? -256 : -16);
}
return aCoverVariable;
}
protected boolean canTransferFluid(FluidStack fluid) {
return true;
}
@Override
public boolean letsRedstoneGoInImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable,
ICoverable aTileEntity) {
return true;
}
@Override
public boolean letsRedstoneGoOutImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable,
ICoverable aTileEntity) {
return true;
}
@Override
public boolean letsEnergyInImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable,
ICoverable aTileEntity) {
return true;
}
@Override
public boolean letsEnergyOutImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable,
ICoverable aTileEntity) {
return true;
}
@Override
public boolean letsItemsInImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable, int aSlot,
ICoverable aTileEntity) {
return true;
}
@Override
public boolean letsItemsOutImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable, int aSlot,
ICoverable aTileEntity) {
return true;
}
@Override
public boolean letsFluidInImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable, Fluid aFluid,
ICoverable aTileEntity) {
return allowFluid;
}
@Override
public boolean letsFluidOutImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable, Fluid aFluid,
ICoverable aTileEntity) {
return allowFluid;
}
@Override
protected boolean alwaysLookConnectedImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable,
ICoverable aTileEntity) {
return true;
}
@Override
protected int getTickRateImpl(ForgeDirection side, int aCoverID, FluidRegulatorData aCoverVariable,
ICoverable aTileEntity) {
return aCoverVariable.tickRate;
}
// GUI stuff
@Override
public boolean hasCoverGUI() {
return true;
}
@Override
public ModularWindow createWindow(CoverUIBuildContext buildContext) {
return new FluidRegulatorUIFactory(buildContext).createWindow();
}
private class FluidRegulatorUIFactory extends UIFactory {
private static final int startX = 10;
private static final int startY = 25;
private static final int spaceX = 18;
private static final int spaceY = 18;
private static final NumberFormatMUI numberFormat;
static {
numberFormat = new NumberFormatMUI();
numberFormat.setMaximumFractionDigits(2);
}
public FluidRegulatorUIFactory(CoverUIBuildContext buildContext) {
super(buildContext);
}
@SuppressWarnings("PointlessArithmeticExpression")
@Override
protected void addUIWidgets(ModularWindow.Builder builder) {
AtomicBoolean warn = new AtomicBoolean(false);
builder.widget(
new CoverDataControllerWidget<>(this::getCoverData, this::setCoverData, CoverFluidRegulator.this)
.addFollower(
CoverDataFollowerToggleButtonWidget.ofDisableable(),
coverData -> coverData.speed >= 0,
(coverData, state) -> {
coverData.speed = Math.abs(coverData.speed);
return coverData;
},
widget -> widget.setStaticTexture(GTUITextures.OVERLAY_BUTTON_EXPORT)
.addTooltip(GTUtility.trans("006", "Export"))
.setPos(spaceX * 0, spaceY * 0))
.addFollower(
CoverDataFollowerToggleButtonWidget.ofDisableable(),
coverData -> coverData.speed <= 0,
(coverData, state) -> {
coverData.speed = -Math.abs(coverData.speed);
return coverData;
},
widget -> widget.setStaticTexture(GTUITextures.OVERLAY_BUTTON_IMPORT)
.addTooltip(GTUtility.trans("007", "Import"))
.setPos(spaceX * 1, spaceY * 0))
.addFollower(
CoverDataFollowerToggleButtonWidget.ofDisableable(),
coverData -> coverData.condition == Conditional.Always,
(coverData, state) -> {
coverData.condition = Conditional.Always;
return coverData;
},
widget -> widget.setStaticTexture(GTUITextures.OVERLAY_BUTTON_CHECKMARK)
.addTooltip(GTUtility.trans("224", "Always On"))
.setPos(spaceX * 0, spaceY * 1))
.addFollower(
CoverDataFollowerToggleButtonWidget.ofDisableable(),
coverData -> coverData.condition == Conditional.Conditional,
(coverData, state) -> {
coverData.condition = Conditional.Conditional;
return coverData;
},
widget -> widget.setStaticTexture(GTUITextures.OVERLAY_BUTTON_USE_PROCESSING_STATE)
.addTooltip(GTUtility.trans("343", "Use Machine Processing State"))
.setPos(spaceX * 1, spaceY * 1))
.addFollower(
CoverDataFollowerToggleButtonWidget.ofDisableable(),
coverData -> coverData.condition == Conditional.Inverted,
(coverData, state) -> {
coverData.condition = Conditional.Inverted;
return coverData;
},
widget -> widget.setStaticTexture(GTUITextures.OVERLAY_BUTTON_USE_INVERTED_PROCESSING_STATE)
.addTooltip(GTUtility.trans("343.1", "Use Inverted Machine Processing State"))
.setPos(spaceX * 2, spaceY * 1))
.addFollower(
new CoverDataFollowerNumericWidget<>(),
coverData -> (double) coverData.speed,
(coverData, state) -> {
coverData.speed = state.intValue();
return coverData;
},
widget -> widget.setBounds(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)
.setValidator(val -> {
final int tickRate = getCoverData() != null ? getCoverData().tickRate : 0;
final long maxFlow = (long) mTransferRate
* GTUtility.clamp(tickRate, TICK_RATE_MIN, TICK_RATE_MAX);
warn.set(false);
if (val > maxFlow) {
val = maxFlow;
warn.set(true);
} else if (val < -maxFlow) {
val = -maxFlow;
warn.set(true);
}
return val;
})
.setScrollValues(1, 144, 1000)
.setFocusOnGuiOpen(true)
.setPos(spaceX * 0, spaceY * 2 + 2)
.setSize(spaceX * 4 - 3, 12))
.addFollower(
new CoverDataFollowerNumericWidget<>(),
coverData -> (double) coverData.tickRate,
(coverData, state) -> {
coverData.tickRate = state.intValue();
return coverData;
},
widget -> widget.setBounds(0, TICK_RATE_MAX)
.setValidator(val -> {
final int speed = getCoverData() != null ? getCoverData().speed : 0;
warn.set(false);
if (val > TICK_RATE_MAX) {
val = (long) TICK_RATE_MAX;
warn.set(true);
} else if (Math.abs(speed) > mTransferRate * val) {
val = (long) Math
.min(TICK_RATE_MAX, (Math.abs(speed) + mTransferRate - 1) / mTransferRate);
warn.set(true);
} else if (val < TICK_RATE_MIN) {
val = 1L;
}
return val;
})
.setPos(spaceX * 5, spaceY * 2 + 2)
.setSize(spaceX * 2 - 3, 12))
.setPos(startX, startY))
.widget(
new TextWidget(GTUtility.trans("229", "Export/Import")).setDefaultColor(COLOR_TEXT_GRAY.get())
.setPos(3 + startX + spaceX * 4, 4 + startY + spaceY * 0))
.widget(
new TextWidget(GTUtility.trans("230", "Conditional")).setDefaultColor(COLOR_TEXT_GRAY.get())
.setPos(3 + startX + spaceX * 4, 4 + startY + spaceY * 1))
.widget(
new TextWidget(GTUtility.trans("208", " L")).setDefaultColor(COLOR_TEXT_GRAY.get())
.setPos(startX + spaceX * 4, 4 + startY + spaceY * 2))
.widget(
new TextWidget(GTUtility.trans("209", " ticks")).setDefaultColor(COLOR_TEXT_GRAY.get())
.setPos(startX + spaceX * 7, 4 + startY + spaceY * 2))
.widget(new TextWidget().setTextSupplier(() -> {
FluidRegulatorData coverVariable = getCoverData();
if (coverVariable == null) return new Text("");
return new Text(
GTUtility.trans("210.1", "Average:") + " "
+ numberFormat.format(
coverVariable.tickRate == 0 ? 0 : coverVariable.speed * 20d / coverVariable.tickRate)
+ " "
+ GTUtility.trans("210.2", "L/sec"))
.color(warn.get() ? COLOR_TEXT_WARN.get() : COLOR_TEXT_GRAY.get());
})
.setPos(startX + spaceX * 0, 4 + startY + spaceY * 3));
}
}
@Override
public boolean allowsTickRateAddition() {
return false;
}
public enum Conditional {
Always(false) {
@Override
boolean isAllowedToWork(ForgeDirection side, int aCoverID, ICoverable aTileEntity) {
return true;
}
},
Conditional(true) {
@Override
boolean isAllowedToWork(ForgeDirection side, int aCoverID, ICoverable aTileEntity) {
return !(aTileEntity instanceof IMachineProgress) || ((IMachineProgress) aTileEntity).isAllowedToWork();
}
},
Inverted(true) {
@Override
boolean isAllowedToWork(ForgeDirection side, int aCoverID, ICoverable aTileEntity) {
return !(aTileEntity instanceof IMachineProgress)
|| !((IMachineProgress) aTileEntity).isAllowedToWork();
}
};
static final Conditional[] VALUES = values();
private final boolean redstoneSensitive;
Conditional(boolean redstoneSensitive) {
this.redstoneSensitive = redstoneSensitive;
}
abstract boolean isAllowedToWork(ForgeDirection side, int aCoverID, ICoverable aTileEntity);
boolean isRedstoneSensitive() {
return redstoneSensitive;
}
}
public static class FluidRegulatorData implements ISerializableObject {
private int tickRate;
private int speed;
private Conditional condition;
private static int getSpeed(int aCoverVariable) {
// positive or 0 -> interval bits need to be set to zero
// negative -> interval bits need to be set to one
return aCoverVariable >= 0 ? aCoverVariable & ~TICK_RATE_BITMASK : aCoverVariable | TICK_RATE_BITMASK;
}
private static int getTickRate(int aCoverVariable) {
// range: TICK_RATE_MIN ~ TICK_RATE_MAX
return ((Math.abs(aCoverVariable) & TICK_RATE_BITMASK) >>> SPEED_LENGTH) + TICK_RATE_MIN;
}
public FluidRegulatorData() {
this(0);
}
public FluidRegulatorData(int legacy) {
this(getTickRate(legacy), getSpeed(legacy), Conditional.Always);
}
public FluidRegulatorData(int tickRate, int speed, Conditional condition) {
this.tickRate = tickRate;
this.speed = speed;
this.condition = condition;
}
@Nonnull
@Override
public ISerializableObject copy() {
return new FluidRegulatorData(tickRate, speed, condition);
}
@Nonnull
@Override
public NBTBase saveDataToNBT() {
NBTTagCompound tag = new NBTTagCompound();
tag.setInteger("mSpeed", speed);
tag.setInteger("mTickRate", tickRate);
tag.setByte("mCondition", (byte) condition.ordinal());
return tag;
}
@Override
public void writeToByteBuf(ByteBuf aBuf) {
aBuf.writeInt(tickRate)
.writeInt(speed)
.writeByte(condition.ordinal());
}
@Override
public void loadDataFromNBT(NBTBase aNBT) {
if (!(aNBT instanceof NBTTagCompound tag)) return; // not very good...
speed = tag.getInteger("mSpeed");
tickRate = tag.getInteger("mTickRate");
condition = Conditional.VALUES[tag.getByte("mCondition")];
}
@Nonnull
@Override
public ISerializableObject readFromPacket(ByteArrayDataInput aBuf, @Nullable EntityPlayerMP aPlayer) {
return new FluidRegulatorData(aBuf.readInt(), aBuf.readInt(), Conditional.VALUES[aBuf.readUnsignedByte()]);
}
public int getTickRate() {
return tickRate;
}
public void setTickRate(int tickRate) {
this.tickRate = tickRate;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public Conditional getCondition() {
return condition;
}
public void setCondition(Conditional condition) {
this.condition = condition;
}
}
}