package gregtech.common;
import gregtech.GT_Mod;
import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
import gregtech.api.objects.GT_UO_Dimension;
import gregtech.api.objects.GT_UO_Fluid;
import gregtech.api.objects.XSTR;
import gregtech.api.util.GT_ChunkAssociatedData;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.event.world.ChunkDataEvent;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;
import java.util.WeakHashMap;
import static gregtech.api.objects.XSTR.XSTR_INSTANCE;
/**
* Created by Tec on 29.04.2017.
*/
public class GT_UndergroundOil {
public static final short DIVIDER=5000;
private static final GT_UndergroundOilStore STORAGE = new GT_UndergroundOilStore();
public static FluidStack undergroundOilReadInformation(IGregTechTileEntity te){
return undergroundOil(te.getWorld().getChunkFromBlockCoords(te.getXCoord(),te.getZCoord()),-1);
}
public static FluidStack undergroundOilReadInformation(Chunk chunk) {
return undergroundOil(chunk,-1);
}
public static FluidStack undergroundOil(IGregTechTileEntity te, float readOrDrainCoefficient){
return undergroundOil(te.getWorld().getChunkFromBlockCoords(te.getXCoord(),te.getZCoord()),readOrDrainCoefficient);
}
//Returns whole content for information purposes -> when drainSpeedCoefficient < 0
//Else returns extracted fluidStack if amount > 0, or null otherwise
public static FluidStack undergroundOil(Chunk chunk, float readOrDrainCoefficient) {
ChunkData chunkData = STORAGE.get(chunk);
if (chunkData.getVein() == null || chunkData.getFluid() == null) // nothing here...
return null;
//do stuff on it if needed
FluidStack fluidInChunk = new FluidStack(chunkData.getFluid(), 0);
if(readOrDrainCoefficient>=0){
int fluidExtracted = (int) Math.floor(chunkData.getAmount() * (double) readOrDrainCoefficient / DIVIDER);
double averageDecrease = chunkData.getVein().DecreasePerOperationAmount * (double) readOrDrainCoefficient;
int decrease=(int)Math.ceil(averageDecrease);
if (fluidExtracted <= 0 || chunkData.amount <= decrease) {//decrease - here it is max value of extraction for easy check
chunkData.setAmount(0);
}else{
fluidInChunk.amount = fluidExtracted;//give appropriate amount
if (XSTR_INSTANCE.nextFloat() < (decrease - averageDecrease))
decrease--;//use XSTR_INSTANCE to "subtract double from int"
//ex.
// averageDecrease=3.9
// decrease= ceil from 3.9 = 4
// decrease-averageDecrease=0.1 -> chance to subtract 1
// if XSTR_INSTANCE is < chance then subtract 1
chunkData.changeAmount(-decrease);//diminish amount, "randomly" adjusted to double value (averageDecrease)
}
}else{//just get info
if(fluidInChunk.amount<=DIVIDER){
fluidInChunk.amount=0;//return informative stack
chunkData.setAmount(0);
}else{
fluidInChunk.amount=fluidInChunk.amount/DIVIDER;//give moderate extraction speed
}
}
return fluidInChunk;
}
static void migrate(ChunkDataEvent.Load e) {
if (e.getData().hasKey("GTOIL") && e.getData().hasKey("GTOILFLUID")) {
ChunkData chunkData = STORAGE.get(e.getChunk());
Fluid fluid = chunkData.getFluid();
if (fluid != null && fluid.getID() == e.getData().getInteger("GTOIL"))
chunkData.setAmount(Math.min(chunkData.getAmount(), e.getData().getInteger("GTOILFLUID")));
}
}
/**
* Revamped UO store.
*
* Primary functionality:
*
*
* - Decouple data storage with chunk, making it possible to pump oil from unloaded chunks
* - Regen detection. If fluid generation config is changed, chunk fluid will be regenerated.
*
*
* Serialized form
*
* Since the exact file layout is controlled by the super class, here we only concern how each chunk's data is written.
*
Form A: Empty Chunk
*
* - 4 bytes of 0
*
*
* Form B: Normal Chunk
*
* - 4 bytes unsigned integer. Vein Hash.
* - UTF string. Vein Key.
* - 4 bytes signed integer. Fluid amount.
*
*
* @author glease
*/
@ParametersAreNonnullByDefault
private static class GT_UndergroundOilStore extends GT_ChunkAssociatedData {
private static final GT_UndergroundOil.ChunkData NIL_FLUID_STACK = new GT_UndergroundOil.ChunkData(-1, null, null, false);
private static final WeakHashMap hashes = new WeakHashMap<>();
private GT_UndergroundOilStore() {
super("UO", GT_UndergroundOil.ChunkData.class, 64, (byte) 0, false);
}
@Override
protected void writeElement(DataOutput output, ChunkData element, World world, int chunkX, int chunkZ) throws IOException {
/* see class javadoc for explanation */
output.writeInt(element.getVeinHash());
if (element.getVeinKey() == null) return;
output.writeUTF(element.getVeinKey());
if (element.getAmount() > 0 && element.getFluid() != null) {
output.writeInt(element.getAmount());
} else {
output.writeInt(-1);
}
}
@Override
protected GT_UndergroundOil.ChunkData readElement(DataInput input, int version, World world, int chunkX, int chunkZ) throws IOException {
/* see class javadoc for explanation */
if (version != 0)
throw new IOException("Region file corrupted");
GT_UndergroundOil.ChunkData pristine = createElement(world, chunkX, chunkZ);
int hash = input.readInt();
String veinKey = hash != 0 ? input.readUTF() : null;
int amount = hash != 0 ? input.readInt() : -1;
if (hash != pristine.veinHash || !Objects.equals(veinKey, pristine.getVeinKey())) {
// vein config changed. use regen-ed data.
return pristine;
}
if (hash == 0)
return NIL_FLUID_STACK;
return new GT_UndergroundOil.ChunkData(amount, GT_Mod.gregtechproxy.mUndergroundOil.GetDimension(world.provider.dimensionId).getUOFluid(veinKey), veinKey);
}
@Override
protected GT_UndergroundOil.ChunkData createElement(World world, int chunkX, int chunkZ) {
int dimensionId = world.provider.dimensionId;
GT_UO_Dimension dimension = GT_Mod.gregtechproxy.mUndergroundOil.GetDimension(dimensionId);
if (dimension == null) return NIL_FLUID_STACK;
// prepare RNG 🙏 🙏 🙏
final XSTR tRandom = new XSTR(world.getSeed() + dimensionId * 2L + (chunkX >> 3) + 8267L * (chunkZ >> 3));
GT_UO_Fluid uoFluid = dimension.getRandomFluid(tRandom);
// nothing here :(
if (uoFluid == null || uoFluid.getFluid() == null) return NIL_FLUID_STACK;
// offset each chunk's fluid amount by +-25%
int amount = (int) ((float) uoFluid.getRandomAmount(tRandom) * (0.75f + (XSTR_INSTANCE.nextFloat() / 2f)));
return new GT_UndergroundOil.ChunkData(amount, uoFluid, dimension.getUOFluidKey(uoFluid), false);
}
private static int hash(@Nullable GT_UO_Fluid fluid) {
if (fluid == null)
return 0;
int result = fluid.Registry.hashCode();
result = 31 * result + fluid.MaxAmount;
result = 31 * result + fluid.MinAmount;
result = 31 * result + fluid.Chance;
result = 31 * result + fluid.DecreasePerOperationAmount;
return result == 0 ? 1 : result;
}
}
/**
* Represent the amount of fluid in a given chunk.
*/
private static final class ChunkData implements GT_ChunkAssociatedData.IData {
private final Fluid fluid;
@Nullable
private final GT_UO_Fluid vein;
private final String veinKey;
private final int veinHash;
private int amount;
private boolean dirty;
private ChunkData(int amount, GT_UO_Fluid veinKey, String veinID) {
this(amount, veinKey, veinID, true);
}
private ChunkData(int amount, @Nullable GT_UO_Fluid vein, @Nullable String veinKey, boolean dirty) {
this.amount = amount;
this.vein = vein;
this.dirty = dirty;
if (vein == null) {
fluid = null;
this.veinKey = null;
veinHash = 0;
} else {
fluid = vein.getFluid();
this.veinKey = veinKey;
veinHash = GT_UndergroundOilStore.hashes.computeIfAbsent(vein, GT_UndergroundOilStore::hash);
}
}
/**
* The current fluid type. {@code null} if vein is generated to be empty.
*/
@Nullable
public Fluid getFluid() {
return fluid;
}
/**
* Current fluid amount. Might be 0 if empty. Cannot be negative
*/
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
if (this.amount != amount)
dirty = true;
this.amount = Math.max(0, amount);
}
public void changeAmount(int delta) {
if (delta != 0)
dirty = true;
this.amount = Math.max(0, amount - delta);
}
@Nullable
public GT_UO_Fluid getVein() {
return vein;
}
/**
* The vein ID. Might be null if generated to be empty.
*/
@Nullable
public String getVeinKey() {
return veinKey;
}
public int getVeinHash() {
return veinHash;
}
@Override
public boolean isSameAsDefault() {
return dirty;
}
}
}