From 199ec48f853f55a78124a5ccbcd00f521de2d3aa Mon Sep 17 00:00:00 2001
From: Glease <4586901+Glease@users.noreply.github.com>
Date: Tue, 16 Nov 2021 02:00:00 +0800
Subject: underground oil and pollution persistence form rework
Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
---
src/main/java/gregtech/GT_Mod.java | 1 +
src/main/java/gregtech/api/enums/GT_Values.java | 4 +
.../java/gregtech/api/objects/GT_UO_Dimension.java | 7 +
.../gregtech/api/util/GT_ChunkAssociatedData.java | 435 +++++++++++++++++++++
src/main/java/gregtech/api/util/GT_Utility.java | 79 +++-
src/main/java/gregtech/common/GT_Pollution.java | 178 ++++++---
src/main/java/gregtech/common/GT_Proxy.java | 75 +---
.../java/gregtech/common/GT_UndergroundOil.java | 267 ++++++++++---
src/main/java/gregtech/common/misc/GT_Command.java | 2 +-
9 files changed, 847 insertions(+), 201 deletions(-)
create mode 100644 src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java
(limited to 'src/main/java')
diff --git a/src/main/java/gregtech/GT_Mod.java b/src/main/java/gregtech/GT_Mod.java
index 864ef31c8c..44ee615f41 100644
--- a/src/main/java/gregtech/GT_Mod.java
+++ b/src/main/java/gregtech/GT_Mod.java
@@ -286,6 +286,7 @@ public class GT_Mod implements IGT_Mod {
GT_Values.debugBlockMiner = tMainConfig.get(aTextGeneral, "debugBlockMiner", false).getBoolean(false);
GT_Values.debugBlockPump = tMainConfig.get(aTextGeneral, "debugBlockPump", false).getBoolean(false);
GT_Values.debugEntityCramming = tMainConfig.get(aTextGeneral, "debugEntityCramming", false).getBoolean(false);
+ GT_Values.debugWorldData = tMainConfig.get(aTextGeneral, "debugWorldData", false).getBoolean(false);
GT_Values.oreveinPercentage = tMainConfig.get(aTextGeneral, "oreveinPercentage_100", 100).getInt(100);
GT_Values.oreveinAttempts = tMainConfig.get(aTextGeneral, "oreveinAttempts_64", 64).getInt(64);
GT_Values.oreveinMaxPlacementAttempts = tMainConfig.get(aTextGeneral, "oreveinMaxPlacementAttempts_8", 8).getInt(8);
diff --git a/src/main/java/gregtech/api/enums/GT_Values.java b/src/main/java/gregtech/api/enums/GT_Values.java
index f30360ca58..6505c34423 100644
--- a/src/main/java/gregtech/api/enums/GT_Values.java
+++ b/src/main/java/gregtech/api/enums/GT_Values.java
@@ -271,6 +271,10 @@ public class GT_Values {
* Debug parameter for entity cramming reduction
*/
public static boolean debugEntityCramming = false;
+ /**
+ * Debug parameter for {@link gregtech.api.util.GT_ChunkAssociatedData}
+ */
+ public static boolean debugWorldData = false;
/**
* Number of ticks between sending sound packets to clients for electric machines. Default is 1.5 seconds. Trying to mitigate lag and FPS drops.
*/
diff --git a/src/main/java/gregtech/api/objects/GT_UO_Dimension.java b/src/main/java/gregtech/api/objects/GT_UO_Dimension.java
index 1c090eb9b5..0d05e6d229 100644
--- a/src/main/java/gregtech/api/objects/GT_UO_Dimension.java
+++ b/src/main/java/gregtech/api/objects/GT_UO_Dimension.java
@@ -39,4 +39,11 @@ public class GT_UO_Dimension {
return null;
}
+ public String getUOFluidKey(GT_UO_Fluid uoFluid) {
+ return fFluids.inverse().get(uoFluid);
+ }
+
+ public GT_UO_Fluid getUOFluid(String key) {
+ return fFluids.get(key);
+ }
}
diff --git a/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java b/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java
new file mode 100644
index 0000000000..b28cb429b4
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java
@@ -0,0 +1,435 @@
+package gregtech.api.util;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import net.minecraft.world.ChunkCoordIntPair;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.world.WorldEvent;
+import org.apache.commons.io.FileUtils;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * A utility to save all kinds of data that is a function of any chunk.
+ *
+ * GregTech takes care of saving and loading the data from disk, and an efficient mechanism to locate it.
+ * Subclass only need to define the exact scheme of each element data (by overriding the three protected abstract method)
+ *
+ * Oh, there is no limit on how large your data is, though you'd not have the familiar NBT interface, but DataOutput
+ * should be reasonably common anyway.
+ *
+ * It should be noted this class is NOT thread safe.
+ *
+ * Element cannot be null.
+ *
+ * TODO: Implement automatic region unloading.
+ *
+ * @param data element type
+ * @author glease
+ */
+@ParametersAreNonnullByDefault
+public abstract class GT_ChunkAssociatedData {
+ private static final Map> instances = new ConcurrentHashMap<>();
+ private static final int IO_PARALLELISM = Math.min(8, Math.max(1, Runtime.getRuntime().availableProcessors()) * 2 / 3);
+ private static final ExecutorService IO_WORKERS = Executors.newWorkStealingPool(IO_PARALLELISM);
+ private static final Pattern FILE_PATTERN = Pattern.compile("(.+)\\.(-?\\d+)\\.(-?\\d+)\\.dat");
+
+ static {
+ // register event handler
+ new EventHandler();
+ }
+
+ protected final String mId;
+ protected final Class elementtype;
+ private final int regionLength;
+ private final int version;
+ private final boolean saveDefaults;
+ /**
+ * Data is stored as a `(world id -> (super region id -> super region data))` hash map.
+ * where super region's size is determined by regionSize.
+ * Here it is called super region, to not confuse with vanilla's regions.
+ */
+ private final Map> masterMap = new ConcurrentHashMap<>();
+
+ /**
+ * Initialize this instance.
+ *
+ * @param aId An arbitrary, but globally unique identifier for what this data is
+ * @param elementType The class of this element type. Used to create arrays.
+ * @param regionLength The length of one super region. Each super region will contain {@code regionLength * regionLength} chunks
+ * @param version An integer marking the version of this data. Useful later when the data's serialized form changed.
+ */
+ protected GT_ChunkAssociatedData(String aId, Class elementType, int regionLength, byte version, boolean saveDefaults) {
+ if (regionLength * regionLength > Short.MAX_VALUE || regionLength <= 0)
+ throw new IllegalArgumentException("Region invalid: " + regionLength);
+ if (!IData.class.isAssignableFrom(elementType))
+ throw new IllegalArgumentException("Data type invalid");
+ if (aId.contains("."))
+ throw new IllegalArgumentException("ID cannot contains dot");
+ this.mId = aId;
+ this.elementtype = elementType;
+ this.regionLength = regionLength;
+ this.version = version;
+ this.saveDefaults = saveDefaults;
+ if (instances.putIfAbsent(aId, this) != null)
+ throw new IllegalArgumentException("Duplicate GT_ChunkAssociatedData: " + aId);
+ }
+
+ private ChunkCoordIntPair getRegionID(int aChunkX, int aChunkZ) {
+ return new ChunkCoordIntPair(aChunkX / regionLength, aChunkZ / regionLength);
+ }
+
+ /**
+ * Get a reference to data of the chunk that tile entity is in.
+ * The returned reference should be mutable.
+ */
+ public final T get(IGregTechTileEntity tileEntity) {
+ return get(tileEntity.getWorld(), tileEntity.getXCoord() >> 4, tileEntity.getZCoord() >> 4);
+ }
+
+ public final T get(Chunk chunk) {
+ return get(chunk.worldObj, chunk.xPosition, chunk.zPosition);
+ }
+
+ public final T get(World world, ChunkCoordIntPair coord) {
+ return get(world, coord.chunkXPos, coord.chunkZPos);
+ }
+
+ public final T get(World world, int chunkX, int chunkZ) {
+ SuperRegion region = masterMap.computeIfAbsent(world.provider.dimensionId, ignored -> new ConcurrentHashMap<>())
+ .computeIfAbsent(getRegionID(chunkX, chunkZ), c -> new SuperRegion(world, c));
+ return region.get(chunkX % regionLength, chunkZ % regionLength);
+ }
+
+ protected final void set(World world, int chunkX, int chunkZ, T data) {
+ SuperRegion region = masterMap.computeIfAbsent(world.provider.dimensionId, ignored -> new ConcurrentHashMap<>())
+ .computeIfAbsent(getRegionID(chunkX, chunkZ), c -> new SuperRegion(world, c));
+ region.set(chunkX % regionLength, chunkZ % regionLength, data);
+ }
+
+ protected final boolean isCreated(int dimId, int chunkX, int chunkZ) {
+ Map dimData = masterMap.getOrDefault(dimId, null);
+ if (dimData == null) return false;
+
+ SuperRegion region = dimData.getOrDefault(getRegionID(chunkX, chunkZ), null);
+ if (region == null) return false;
+
+ return region.isCreated(chunkX % regionLength, chunkZ % regionLength);
+ }
+
+ public void clear() {
+ if (GT_Values.debugWorldData) {
+ long dirtyRegionCount = masterMap.values().stream()
+ .flatMap(m -> m.values().stream())
+ .filter(SuperRegion::isDirty)
+ .count();
+ if (dirtyRegionCount > 0)
+ GT_Log.out.println("Clearing ChunkAssociatedData with " + dirtyRegionCount + " regions dirty. Data might have been lost!");
+ }
+ masterMap.clear();
+ }
+
+ public void save() {
+ saveRegions(masterMap.values().stream().flatMap(m -> m.values().stream()));
+ }
+
+ public void save(World world) {
+ saveRegions(masterMap.get(world.provider.dimensionId).values().stream());
+ }
+
+ private void saveRegions(Stream stream) {
+ stream.filter(r -> !r.isDirty())
+ .map(c -> (Runnable) c::save)
+ .map(r -> CompletableFuture.runAsync(r, IO_WORKERS))
+ .reduce(CompletableFuture::allOf)
+ .ifPresent(f -> {
+ try {
+ f.get();
+ } catch (Exception e) {
+ GT_Log.err.println("Data save error: " + mId);
+ e.printStackTrace(GT_Log.err);
+ }
+ });
+ }
+
+ protected abstract void writeElement(DataOutput output, T element, World world, int chunkX, int chunkZ) throws IOException;
+
+ protected abstract T readElement(DataInput input, int version, World world, int chunkX, int chunkZ) throws IOException;
+
+ protected abstract T createElement(World world, int chunkX, int chunkZ);
+
+ /**
+ * Clear all mappings, regardless of whether they are dirty
+ */
+ public static void clearAll() {
+ for (GT_ChunkAssociatedData> d : instances.values()) d.clear();
+ }
+
+ /**
+ * Save all mappings
+ */
+ public static void saveAll() {
+ for (GT_ChunkAssociatedData> d : instances.values()) d.save();
+ }
+
+ /**
+ * Load data for all chunks for a given world.
+ * Current data for that world will be discarded. If this is what you intended, call {@link #save(World)} beforehand.
+ *
+ * Be aware of the memory consumption though.
+ */
+ protected void loadAll(World w) {
+ if (GT_Values.debugWorldData && masterMap.containsKey(w.provider.dimensionId))
+ GT_Log.err.println("Reloading ChunkAssociatedData " + mId + " for world " + w.provider.dimensionId + " discards old data!");
+ if (!getSaveDirectory(w).isDirectory())
+ // nothing to load...
+ return;
+ try {
+ Map worldData = Files.list(getSaveDirectory(w).toPath())
+ .map(f -> {
+ Matcher matcher = FILE_PATTERN.matcher(f.getFileName().toString());
+ return matcher.matches() ? matcher : null;
+ })
+ .filter(Objects::nonNull)
+ .filter(m -> mId.equals(m.group(1)))
+ .map(m -> CompletableFuture.supplyAsync(() -> new SuperRegion(w, Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3))), IO_WORKERS))
+ .map(f -> {
+ try {
+ return f.get();
+ } catch (Exception e) {
+ GT_Log.err.println("Error loading region");
+ e.printStackTrace(GT_Log.err);
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(SuperRegion::getCoord, Function.identity()));
+ masterMap.put(w.provider.dimensionId, worldData);
+ } catch (IOException e) {
+ GT_Log.err.println("Error loading all region");
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ protected File getSaveDirectory(World w) {
+ return new File(w.getSaveHandler().getWorldDirectory(), "gregtech");
+ }
+
+ public interface IData {
+ /**
+ * @return Whether the data is different from chunk default
+ */
+ boolean isSameAsDefault();
+ }
+
+ protected final class SuperRegion {
+ private final T[] data = createData();
+ private final File backingStorage;
+ private final WeakReference world;
+ private final ChunkCoordIntPair coord;
+
+ private SuperRegion(World world, int chunkX, int chunkZ) {
+ this.world = new WeakReference<>(world);
+ this.coord = new ChunkCoordIntPair(chunkX, chunkZ);
+ backingStorage = new File(getSaveDirectory(world), String.format("%s.%d.%d.dat", mId, chunkX, chunkZ));
+ if (backingStorage.isFile())
+ load();
+ }
+
+ private SuperRegion(World world, ChunkCoordIntPair coord) {
+ this.world = new WeakReference<>(world);
+ this.coord = coord;
+ backingStorage = new File(getSaveDirectory(world), String.format("%s.%d.%d.dat", mId, coord.chunkXPos, coord.chunkZPos));
+ if (backingStorage.isFile())
+ load();
+ }
+
+ @SuppressWarnings("unchecked")
+ private T[] createData() {
+ return (T[]) Array.newInstance(elementtype, regionLength * regionLength);
+ }
+
+ public T get(int subRegionX, int subRegionZ) {
+ int index = getIndex(subRegionX, subRegionZ);
+ T datum = data[index];
+ if (datum == null) {
+ World world = Objects.requireNonNull(this.world.get());
+ T newElem = createElement(world, coord.chunkXPos * regionLength + subRegionX, coord.chunkZPos * regionLength + subRegionZ);
+ data[index] = newElem;
+ return newElem;
+ }
+ return datum;
+ }
+
+ public void set(int subRegionX, int subRegionZ, T data) {
+ this.data[getIndex(subRegionX, subRegionZ)] = data;
+ }
+
+ public boolean isCreated(int subRegionX, int subRegionZ) {
+ return this.data[getIndex(subRegionX, subRegionZ)] == null;
+ }
+
+ public ChunkCoordIntPair getCoord() {
+ return coord;
+ }
+
+ private int getIndex(int subRegionX, int subRegionY) {
+ return subRegionX * regionLength + subRegionY;
+ }
+
+ private int getChunkX(int index) {
+ return index / regionLength + coord.chunkXPos;
+ }
+
+ private int getChunkZ(int index) {
+ return index % regionLength + coord.chunkZPos;
+ }
+
+ public boolean isDirty() {
+ for (T datum : data) {
+ if (datum != null && datum.isSameAsDefault())
+ return true;
+ }
+ return false;
+ }
+
+ public void save() {
+ try {
+ save0();
+ } catch (IOException e) {
+ GT_Log.err.println("Error saving data " + backingStorage.getPath());
+ e.printStackTrace(GT_Log.err);
+ }
+ }
+
+ private void save0() throws IOException {
+ if (!isDirty())
+ return;
+ //noinspection ResultOfMethodCallIgnored
+ backingStorage.getParentFile().mkdirs();
+ File tmpFile = getTmpFile();
+ World world = Objects.requireNonNull(this.world.get(), "Attempting to save region of another world!");
+ try (DataOutputStream output = new DataOutputStream(new FileOutputStream(tmpFile))) {
+ int ptr = 0;
+ boolean nullRange = data[0] == null;
+ // write a magic byte as storage format version
+ output.writeByte(0);
+ // write a magic byte as data format version
+ output.writeByte(version);
+ output.writeBoolean(nullRange);
+ while (ptr < data.length) {
+ // work out how long is this range
+ int rangeStart = ptr;
+ while (ptr < data.length && (data[ptr] == null || (!saveDefaults && data[ptr].isSameAsDefault())) == nullRange)
+ ptr++;
+ // write range length
+ output.writeShort(ptr - rangeStart);
+ if (!nullRange)
+ // write element data
+ for (int i = rangeStart; i < ptr; i++)
+ writeElement(output, data[i], world, getChunkX(ptr), getChunkZ(ptr));
+ // or not
+ nullRange = !nullRange;
+ }
+ }
+ // first try to replace the destination file
+ // since atomic operation, no need to keep the backup in place
+ try {
+ Files.move(tmpFile.toPath(), backingStorage.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ } catch (AtomicMoveNotSupportedException ignored) {
+ // in case some dumb system/jre combination would cause this
+ // or if **somehow** two file inside the same directory belongs two separate filesystem
+ FileUtils.copyFile(tmpFile, backingStorage);
+ }
+ }
+
+ public void load() {
+ try {
+ loadFromFile(backingStorage);
+ } catch (IOException | RuntimeException e) {
+ GT_Log.err.println("Primary storage file broken in " + backingStorage.getPath());
+ e.printStackTrace(GT_Log.err);
+ // in case the primary storage is broken
+ try {
+ loadFromFile(getTmpFile());
+ } catch (IOException | RuntimeException e2) {
+ GT_Log.err.println("Backup storage file broken in " + backingStorage.getPath());
+ e2.printStackTrace(GT_Log.err);
+ }
+ }
+ }
+
+ private void loadFromFile(File file) throws IOException {
+ World world = Objects.requireNonNull(this.world.get(), "Attempting to load region of another world!");
+ try (DataInputStream input = new DataInputStream(new FileInputStream(file))) {
+ boolean nullRange = input.readBoolean();
+ int ptr = 0;
+ while (ptr != data.length) {
+ int rangeEnd = ptr + input.readUnsignedShort();
+ if (!nullRange) {
+ for (; ptr < rangeEnd; ptr++) {
+ data[ptr] = readElement(input, version, world, getChunkX(ptr), getChunkZ(ptr));
+ }
+ } else {
+ Arrays.fill(data, ptr, rangeEnd, null);
+ ptr = rangeEnd;
+ }
+ }
+ }
+ }
+
+ private File getTmpFile() {
+ return new File(backingStorage.getParentFile(), backingStorage.getName() + ".tmp");
+ }
+ }
+
+ public static class EventHandler {
+ private EventHandler() {
+ MinecraftForge.EVENT_BUS.register(this);
+ }
+
+ @SubscribeEvent
+ public void onWorldSave(WorldEvent.Save e) {
+ for (GT_ChunkAssociatedData> d : instances.values()) {
+ d.save(e.world);
+ }
+ }
+
+ @SubscribeEvent
+ public void onWorldUnload(WorldEvent.Unload e) {
+ for (GT_ChunkAssociatedData> d : instances.values()) {
+ // there is no need to explicitly do a save here
+ // forge will send a WorldEvent.Save on server thread before this event is distributed
+ d.masterMap.remove(e.world.provider.dimensionId);
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_Utility.java b/src/main/java/gregtech/api/util/GT_Utility.java
index 16d22be9ef..2ae09b6adf 100644
--- a/src/main/java/gregtech/api/util/GT_Utility.java
+++ b/src/main/java/gregtech/api/util/GT_Utility.java
@@ -12,13 +12,24 @@ import gregtech.api.GregTech_API;
import gregtech.api.damagesources.GT_DamageSources;
import gregtech.api.damagesources.GT_DamageSources.DamageSourceHotItem;
import gregtech.api.enchants.Enchantment_Radioactivity;
-import gregtech.api.enums.*;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.SubTag;
+import gregtech.api.enums.Textures;
+import gregtech.api.enums.ToolDictNames;
import gregtech.api.events.BlockScanningEvent;
import gregtech.api.interfaces.IBlockContainer;
import gregtech.api.interfaces.IDebugableBlock;
import gregtech.api.interfaces.IProjectileItem;
import gregtech.api.interfaces.ITexture;
-import gregtech.api.interfaces.tileentity.*;
+import gregtech.api.interfaces.tileentity.IBasicEnergyContainer;
+import gregtech.api.interfaces.tileentity.ICoverable;
+import gregtech.api.interfaces.tileentity.IGregTechDeviceInformation;
+import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.interfaces.tileentity.IMachineProgress;
+import gregtech.api.interfaces.tileentity.IUpgradableMachine;
import gregtech.api.items.GT_EnergyArmor_Item;
import gregtech.api.items.GT_Generic_Item;
import gregtech.api.items.GT_MetaGenerated_Tool;
@@ -28,7 +39,7 @@ import gregtech.api.objects.GT_ItemStack;
import gregtech.api.objects.ItemData;
import gregtech.api.threads.GT_Runnable_Sound;
import gregtech.api.util.extensions.ArrayExt;
-import gregtech.common.GT_Proxy;
+import gregtech.common.GT_Pollution;
import gregtech.common.blocks.GT_Block_Ores_Abstract;
import ic2.api.recipe.IRecipeInput;
import ic2.api.recipe.RecipeInputItemStack;
@@ -37,7 +48,11 @@ import ic2.api.recipe.RecipeOutput;
import net.minecraft.block.Block;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
-import net.minecraft.entity.*;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityList;
+import net.minecraft.entity.EntityLiving;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.EnumCreatureAttribute;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
@@ -59,9 +74,14 @@ import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
-import net.minecraft.util.*;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.DamageSource;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
+import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.BlockSnapshot;
@@ -70,8 +90,13 @@ import net.minecraftforge.common.util.FakePlayerFactory;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.world.BlockEvent;
-import net.minecraftforge.fluids.*;
+import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry.FluidContainerData;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+import net.minecraftforge.fluids.IFluidContainerItem;
+import net.minecraftforge.fluids.IFluidHandler;
import net.minecraftforge.oredict.OreDictionary;
import javax.annotation.Nullable;
@@ -79,16 +104,34 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.NumberFormat;
-import java.text.DecimalFormatSymbols;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import static gregtech.GT_Mod.GT_FML_LOGGER;
-import static gregtech.api.enums.GT_Values.*;
-import static gregtech.common.GT_Proxy.GTPOLLUTION;
+import static gregtech.api.enums.GT_Values.D1;
+import static gregtech.api.enums.GT_Values.DW;
+import static gregtech.api.enums.GT_Values.E;
+import static gregtech.api.enums.GT_Values.GT;
+import static gregtech.api.enums.GT_Values.L;
+import static gregtech.api.enums.GT_Values.M;
+import static gregtech.api.enums.GT_Values.NW;
+import static gregtech.api.enums.GT_Values.V;
+import static gregtech.api.enums.GT_Values.W;
import static gregtech.common.GT_UndergroundOil.undergroundOilReadInformation;
/**
@@ -2186,23 +2229,19 @@ public class GT_Utility {
}
}
+ Chunk currentChunk = aWorld.getChunkFromBlockCoords(aX, aZ);
if (aPlayer.capabilities.isCreativeMode) {
- FluidStack tFluid = undergroundOilReadInformation(aWorld.getChunkFromBlockCoords(aX,aZ));//-# to only read
+ FluidStack tFluid = undergroundOilReadInformation(currentChunk);//-# to only read
if (tFluid!=null)
tList.add(EnumChatFormatting.GOLD + tFluid.getLocalizedName() + EnumChatFormatting.RESET + ": " + EnumChatFormatting.YELLOW + formatNumbers(tFluid.amount) + EnumChatFormatting.RESET + " L");
else
tList.add(EnumChatFormatting.GOLD + trans("201","Nothing") + EnumChatFormatting.RESET + ": " + EnumChatFormatting.YELLOW + '0' + EnumChatFormatting.RESET + " L");
}
// if(aPlayer.capabilities.isCreativeMode){
- int[] chunkData = GT_Proxy.dimensionWiseChunkData.get(aWorld.provider.dimensionId).get(aWorld.getChunkFromBlockCoords(aX,aZ).getChunkCoordIntPair());
- if(chunkData != null){
- if(chunkData[GTPOLLUTION]>0){
- tList.add(trans("202","Pollution in Chunk: ") + EnumChatFormatting.RED + formatNumbers(chunkData[GTPOLLUTION]) + EnumChatFormatting.RESET + trans("203"," gibbl"));
- }else{
- tList.add(EnumChatFormatting.GREEN+trans("204","No Pollution in Chunk! HAYO!")+EnumChatFormatting.RESET);
- }
- }else{
- tList.add(EnumChatFormatting.GREEN+trans("204","No Pollution in Chunk! HAYO!")+EnumChatFormatting.RESET);
+ if (GT_Pollution.hasPollution(currentChunk)) {
+ tList.add(trans("202", "Pollution in Chunk: ") + EnumChatFormatting.RED + formatNumbers(GT_Pollution.getPollution(currentChunk)) + EnumChatFormatting.RESET + trans("203", " gibbl"));
+ } else {
+ tList.add(EnumChatFormatting.GREEN + trans("204", "No Pollution in Chunk! HAYO!") + EnumChatFormatting.RESET);
}
try {
diff --git a/src/main/java/gregtech/common/GT_Pollution.java b/src/main/java/gregtech/common/GT_Pollution.java
index 7b1ad90d52..2a13d692b3 100644
--- a/src/main/java/gregtech/common/GT_Pollution.java
+++ b/src/main/java/gregtech/common/GT_Pollution.java
@@ -7,6 +7,7 @@ import gregtech.GT_Mod;
import gregtech.api.enums.GT_Values;
import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
import gregtech.api.net.GT_Packet_Pollution;
+import gregtech.api.util.GT_ChunkAssociatedData;
import gregtech.api.util.GT_Utility;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
@@ -20,30 +21,34 @@ import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.ChunkPosition;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
+import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.world.ChunkDataEvent;
import net.minecraftforge.event.world.ChunkWatchEvent;
+import net.minecraftforge.event.world.WorldEvent;
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import static gregtech.api.objects.XSTR.XSTR_INSTANCE;
-import static gregtech.common.GT_Proxy.GTPOLLUTION;
-import static gregtech.common.GT_Proxy.dimensionWiseChunkData;
import static gregtech.common.GT_Proxy.dimensionWisePollution;
-import static gregtech.common.GT_Proxy.getDefaultChunkDataOnCreation;
public class GT_Pollution {
+ private static final Storage STORAGE = new Storage();
/**
* Pollution dispersion until effects start:
* Calculation: ((Limit * 0.01) + 2000) * (4 <- spreading rate)
- *
+ *
* SMOG(500k) 466.7 pollution/sec
* Poison(750k) 633,3 pollution/sec
* Dying Plants(1mio) 800 pollution/sec
* Sour Rain(1.5mio) 1133.3 pollution/sec
- *
+ *
* Pollution producers (pollution/sec)
* Bronze Boiler(20)
* Lava Boiler(20)
@@ -69,10 +74,10 @@ public class GT_Pollution {
* LV (0%), MV (30%), HV (52%), EV (66%), IV (76%), LuV (84%), ZPM (89%), UV (92%), MAX (95%)
*/
private List pollutionList = new ArrayList<>();//chunks left to process
- private HashMap chunkData;//link to chunk data that is saved/loaded
+ private final List chunkData = new ArrayList<>();//link to chunk data that is saved/loaded
private int operationsPerTick=0;//how much chunks should be processed in each cycle
private static final short cycleLen=1200;
- private final World aWorld;
+ private final World world;
public static int mPlayerPollution;
private static int POLLUTIONPACKET_MINVALUE = 1000;
@@ -80,13 +85,7 @@ public class GT_Pollution {
private static GT_PollutionEventHandler EVENT_HANDLER;
public GT_Pollution(World world){
- aWorld=world;
- chunkData=dimensionWiseChunkData.get(aWorld.provider.dimensionId);
- if(chunkData==null){
- chunkData=new HashMap<>(1024);
- dimensionWiseChunkData.put(world.provider.dimensionId,chunkData);
- }
- dimensionWisePollution.put(aWorld.provider.dimensionId,this);
+ this.world = world;
if (EVENT_HANDLER == null) {
EVENT_HANDLER = new GT_PollutionEventHandler();
@@ -105,7 +104,9 @@ public class GT_Pollution {
private void tickPollutionInWorld(int aTickID){//called from method above
//gen data set
if(aTickID==0){
- pollutionList = new ArrayList<>(chunkData.keySet());
+ // make a snapshot of what to work on
+ // counterintuitive as it seems, but this is the fastest way java collections framework offers us.
+ pollutionList = new ArrayList<>(chunkData);
//set operations per tick
if(pollutionList.size()>0) operationsPerTick =(pollutionList.size()/cycleLen);
else operationsPerTick=0;//SANity
@@ -114,10 +115,9 @@ public class GT_Pollution {
for(int chunksProcessed=0;chunksProcessed<=operationsPerTick;chunksProcessed++){
if(pollutionList.size()==0)break;//no more stuff to do
ChunkCoordIntPair actualPos=pollutionList.remove(pollutionList.size()-1);//faster
- //add default data if missing
- if(!chunkData.containsKey(actualPos)) chunkData.put(actualPos,getDefaultChunkDataOnCreation());
//get pollution
- int tPollution = chunkData.get(actualPos)[GTPOLLUTION];
+ ChunkData currentData = STORAGE.get(world, actualPos);
+ int tPollution = currentData.getAmount();
//remove some
tPollution = (int)(0.9945f*tPollution);
//tPollution -= 2000;//This does not really matter...
@@ -131,15 +131,14 @@ public class GT_Pollution {
tNeighbors[2]=(new ChunkCoordIntPair(actualPos.chunkXPos,actualPos.chunkZPos+1));
tNeighbors[3]=(new ChunkCoordIntPair(actualPos.chunkXPos,actualPos.chunkZPos-1));
for(ChunkCoordIntPair neighborPosition : tNeighbors){
- if(!chunkData.containsKey(neighborPosition)) chunkData.put(neighborPosition,getDefaultChunkDataOnCreation());
-
- int neighborPollution = chunkData.get(neighborPosition)[GTPOLLUTION];
- if(neighborPollution*6 < tPollution*5){//METHEMATICS...
+ ChunkData neighbor = STORAGE.get(world, neighborPosition);
+ int neighborPollution = neighbor.getAmount();
+ if(neighborPollution*6 < tPollution*5){//MATHEMATICS...
int tDiff = tPollution - neighborPollution;
tDiff = tDiff/20;
neighborPollution = GT_Utility.safeInt((long)neighborPollution+tDiff);//tNPol += tDiff;
tPollution -= tDiff;
- chunkData.get(neighborPosition)[GTPOLLUTION] = neighborPollution;
+ neighbor.setAmount(neighborPollution);
}
}
@@ -148,7 +147,7 @@ public class GT_Pollution {
//Smog filter TODO
if(tPollution > GT_Mod.gregtechproxy.mPollutionSmogLimit) {
AxisAlignedBB chunk = AxisAlignedBB.getBoundingBox(actualPos.chunkXPos << 4, 0, actualPos.chunkZPos << 4, (actualPos.chunkXPos << 4) + 16, 256, (actualPos.chunkZPos << 4) + 16);
- List tEntitys = aWorld.getEntitiesWithinAABB(EntityLivingBase.class, chunk);
+ List tEntitys = world.getEntitiesWithinAABB(EntityLivingBase.class, chunk);
for (EntityLivingBase tEnt : tEntitys) {
if (tEnt instanceof EntityPlayerMP && ((EntityPlayerMP) tEnt).capabilities.isCreativeMode)
continue;
@@ -194,18 +193,18 @@ public class GT_Pollution {
int x = (actualPos.chunkXPos << 4) + XSTR_INSTANCE.nextInt(16);
int y = 60 + (-f + XSTR_INSTANCE.nextInt(f * 2 + 1));
int z = (actualPos.chunkZPos << 4) + XSTR_INSTANCE.nextInt(16);
- damageBlock(aWorld, x, y, z, tPollution > GT_Mod.gregtechproxy.mPollutionSourRainLimit);
+ damageBlock(world, x, y, z, tPollution > GT_Mod.gregtechproxy.mPollutionSourRainLimit);
}
}
}
}
}
//Write new pollution to Hashmap !!!
- chunkData.get(actualPos)[GTPOLLUTION] = tPollution;
+ currentData.setAmount(tPollution);
//Send new value to players nearby
if (tPollution > POLLUTIONPACKET_MINVALUE) {
- NetworkRegistry.TargetPoint point = new NetworkRegistry.TargetPoint(aWorld.provider.dimensionId, (actualPos.chunkXPos << 4), 64, (actualPos.chunkZPos << 4), 256);
+ NetworkRegistry.TargetPoint point = new NetworkRegistry.TargetPoint(world.provider.dimensionId, (actualPos.chunkXPos << 4), 64, (actualPos.chunkZPos << 4), 256);
GT_Values.NW.sendToAllAround(new GT_Packet_Pollution(actualPos, tPollution), point);
}
}
@@ -266,19 +265,8 @@ public class GT_Pollution {
}
public static void addPollution(Chunk ch, int aPollution){
- if(!GT_Mod.gregtechproxy.mPollution)return;
- HashMap dataMap=dimensionWiseChunkData.get(ch.worldObj.provider.dimensionId);
- if(dataMap==null){
- dataMap=new HashMap<>(1024);
- dimensionWiseChunkData.put(ch.worldObj.provider.dimensionId,dataMap);
- }
- int[] dataArr=dataMap.get(ch.getChunkCoordIntPair());
- if(dataArr==null){
- dataArr=getDefaultChunkDataOnCreation();
- dataMap.put(ch.getChunkCoordIntPair(),dataArr);
- }
- dataArr[GTPOLLUTION]+=aPollution;
- if(dataArr[GTPOLLUTION]<0)dataArr[GTPOLLUTION]=0;
+ if(!GT_Mod.gregtechproxy.mPollution || aPollution == 0)return;
+ STORAGE.get(ch).changeAmount(aPollution);
}
public static int getPollution(IGregTechTileEntity te){
@@ -288,18 +276,19 @@ public class GT_Pollution {
public static int getPollution(Chunk ch){
if(!GT_Mod.gregtechproxy.mPollution)
return 0;
- HashMap dataMap=dimensionWiseChunkData.get(ch.worldObj.provider.dimensionId);
- if(dataMap==null || dataMap.get(ch.getChunkCoordIntPair())==null) return 0;
- return dataMap.get(ch.getChunkCoordIntPair())[GTPOLLUTION];
+ return STORAGE.get(ch).getAmount();
+ }
+
+ public static boolean hasPollution(Chunk ch) {
+ if (!GT_Mod.gregtechproxy.mPollution)
+ return false;
+ return STORAGE.isCreated(ch.worldObj, ch.getChunkCoordIntPair()) && STORAGE.get(ch).getAmount() > 0;
}
public static int getPollution(ChunkCoordIntPair aCh, int aDim) {
if (!GT_Mod.gregtechproxy.mPollution)
return 0;
- HashMap dataMap = dimensionWiseChunkData.get(aDim);
- if (dataMap == null || dataMap.get(aCh) == null)
- return 0;
- return dataMap.get(aCh)[GTPOLLUTION];
+ return STORAGE.get(DimensionManager.getWorld(aDim), aCh.chunkXPos, aCh.chunkZPos).getAmount();
}
//Add compatibility with old code
@@ -310,15 +299,100 @@ public class GT_Pollution {
addPollution(aWorld.getChunkFromBlockCoords(aPos.chunkPosX,aPos.chunkPosZ),aPollution);
}
- public class GT_PollutionEventHandler {
+ static void migrate(ChunkDataEvent.Load e) {
+ addPollution(e.getChunk(), e.getData().getInteger("GTPOLLUTION"));
+ }
+
+ public static class GT_PollutionEventHandler {
@SubscribeEvent
public void chunkWatch(ChunkWatchEvent.Watch event) {
- if(!GT_Mod.gregtechproxy.mPollution) return;
- if (chunkData.containsKey(event.chunk)) {
- int pollution = chunkData.get(event.chunk)[GTPOLLUTION];
+ if (!GT_Mod.gregtechproxy.mPollution) return;
+ World world = event.player.worldObj;
+ if (STORAGE.isCreated(world, event.chunk)) {
+ int pollution = STORAGE.get(world, event.chunk).getAmount();
if (pollution > POLLUTIONPACKET_MINVALUE)
GT_Values.NW.sendToPlayer(new GT_Packet_Pollution(event.chunk, pollution), event.player);
}
}
+
+ @SubscribeEvent
+ public void onWorldLoad(WorldEvent.Load e) {
+ // super class loads everything lazily. We force it to load them all.
+ STORAGE.loadAll(e.world);
+ }
+ }
+
+ @ParametersAreNonnullByDefault
+ private static final class Storage extends GT_ChunkAssociatedData {
+ private Storage() {
+ super("Pollution", ChunkData.class, 64, (byte) 0, false);
+ }
+
+ @Override
+ protected void writeElement(DataOutput output, ChunkData element, World world, int chunkX, int chunkZ) throws IOException {
+ output.writeInt(element.getAmount());
+ }
+
+ @Override
+ protected ChunkData readElement(DataInput input, int version, World world, int chunkX, int chunkZ) throws IOException {
+ ChunkData data = new ChunkData(input.readInt());
+ getChunkData(world).add(new ChunkCoordIntPair(chunkX, chunkZ));
+ return data;
+ }
+
+ private List getChunkData(World world) {
+ return dimensionWisePollution.computeIfAbsent(world.provider.dimensionId, i -> new GT_Pollution(world)).chunkData;
+ }
+
+ @Override
+ protected ChunkData createElement(World world, int chunkX, int chunkZ) {
+ return new ChunkData();
+ }
+
+ @Override
+ public void loadAll(World w) {
+ super.loadAll(w);
+ }
+
+ public void set(World world, ChunkCoordIntPair coord, ChunkData data) {
+ set(world, coord.chunkXPos, coord.chunkZPos, data);
+ getChunkData(world).add(coord);
+ }
+
+ public boolean isCreated(World world, ChunkCoordIntPair coord) {
+ return isCreated(world.provider.dimensionId, coord.chunkXPos, coord.chunkZPos);
+ }
+ }
+
+ private static final class ChunkData implements GT_ChunkAssociatedData.IData {
+ public int amount;
+
+ private ChunkData() {
+ this(0);
+ }
+
+ private ChunkData(int amount) {
+ this.amount = amount;
+ }
+
+ /**
+ * Current pollution amount.
+ */
+ public int getAmount() {
+ return amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = Math.max(amount, 0);
+ }
+
+ public void changeAmount(int delta) {
+ this.amount = Math.max(amount + delta, 0);
+ }
+
+ @Override
+ public boolean isSameAsDefault() {
+ return amount == 0;
+ }
}
}
diff --git a/src/main/java/gregtech/common/GT_Proxy.java b/src/main/java/gregtech/common/GT_Proxy.java
index ed99c1e3c0..a1706ee6e9 100644
--- a/src/main/java/gregtech/common/GT_Proxy.java
+++ b/src/main/java/gregtech/common/GT_Proxy.java
@@ -709,8 +709,8 @@ public abstract class GT_Proxy implements IGT_Mod, IGuiHandler, IFuelHandler {
}
public void onServerAboutToStart(){
- dimensionWiseChunkData.clear();//!!! IMPORTANT for map switching...
dimensionWisePollution.clear();//!!! IMPORTANT for map switching...
+ GT_ChunkAssociatedData.clearAll();
}
public void onServerStarting() {
@@ -776,6 +776,7 @@ public abstract class GT_Proxy implements IGT_Mod, IGuiHandler, IFuelHandler {
} catch (Throwable e) {e.printStackTrace(GT_Log.err);}
}
this.mUniverse = null;
+ //GT_ChunkAssociatedData.saveAll(); todo: figure out if this is needed
}
@SubscribeEvent
@@ -1981,87 +1982,25 @@ public abstract class GT_Proxy implements IGT_Mod, IGuiHandler, IFuelHandler {
}
+ @Deprecated
public static final HashMap> dimensionWiseChunkData = new HashMap<>(16);//stores chunk data that is loaded/saved
public static final HashMap dimensionWisePollution = new HashMap<>(16);//stores GT_Polluttors objects
public static final byte GTOIL=3,GTOILFLUID=2,GTPOLLUTION=1,GTMETADATA=0,NOT_LOADED=0,LOADED=1;//consts
- //@Deprecated
- //public static final HashMap chunkData = new HashMap<>(0);
-
- private static final byte oilVer=(byte)20;//non zero plz
//TO get default's fast
+ @Deprecated
public static int[] getDefaultChunkDataOnCreation(){
return new int[]{NOT_LOADED,0,-1,-1};
}
+ @Deprecated
public static int[] getDefaultChunkDataOnLoad(){
return new int[]{LOADED,0,-1,-1};
}
- @SubscribeEvent
- public void handleChunkSaveEvent(ChunkDataEvent.Save event) {//ALWAYS SAVE FROM THE HASH MAP DATA
- HashMap chunkData=dimensionWiseChunkData.get(event.world.provider.dimensionId);
- if(chunkData==null) return;//no dim info stored
-
- int[] tInts = chunkData.get(event.getChunk().getChunkCoordIntPair());
- if(tInts==null) return;//no chunk data stored
- //assuming len of this array 4
- if(tInts[3]>=0)event.getData().setInteger("GTOIL", tInts[GTOIL]);
- else event.getData().removeTag("GTOIL");
- if(tInts[2]>=0)event.getData().setInteger("GTOILFLUID", tInts[GTOILFLUID]);
- else event.getData().removeTag("GTOILFLUID");
- if(tInts[1]>0)event.getData().setInteger("GTPOLLUTION", tInts[GTPOLLUTION]);
- else event.getData().removeTag("GTPOLLUTION");
- event.getData().setByte("GTOILVER", oilVer);//version mark
- }
-
@SubscribeEvent
public void handleChunkLoadEvent(ChunkDataEvent.Load event) {
- final int worldID=event.world.provider.dimensionId;
- HashMap chunkData = dimensionWiseChunkData.computeIfAbsent(worldID, k -> new HashMap<>(1024));
- if (dimensionWisePollution.get(worldID) == null)
- dimensionWisePollution.put(worldID, new GT_Pollution(event.world));
-
- int[] tInts = chunkData.get(event.getChunk().getChunkCoordIntPair());
- if (tInts == null) {
- //NOT LOADED and NOT PROCESSED by pollution algorithms
- //regular load
- tInts = getDefaultChunkDataOnLoad();
-
- if (event.getData().getByte("GTOILVER") == oilVer) {
- if (event.getData().hasKey("GTOIL"))
- tInts[GTOIL] = event.getData().getInteger("GTOIL");
- if (event.getData().hasKey("GTOILFLUID"))
- tInts[GTOILFLUID] = event.getData().getInteger("GTOILFLUID");
- }
-
- tInts[GTPOLLUTION] = event.getData().getInteger("GTPOLLUTION");//Defaults to 0
-
- //store in HASH MAP if has useful data
- if (tInts[GTPOLLUTION] > 0 || tInts[GTOIL] >= 0 || tInts[GTOILFLUID] >= 0)
- chunkData.put(event.getChunk().getChunkCoordIntPair(), tInts);
- } else if (tInts[GTMETADATA] == NOT_LOADED) {//was NOT loaded from chunk save game data
- //NOT LOADED but generated
- //append load
- if (event.getData().getByte("GTOILVER") == oilVer) {
- if (tInts[GTOIL] < 0 && event.getData().hasKey("GTOIL"))//if was not yet initialized
- tInts[GTOIL] = event.getData().getInteger("GTOIL");
-
- if (tInts[GTOILFLUID] < 0 && event.getData().hasKey("GTOILFLUID"))//if was not yet initialized
- tInts[GTOILFLUID] = event.getData().getInteger("GTOILFLUID");
- } else {
- tInts[GTOIL] = -1;
- tInts[GTOILFLUID] = -1;
- }
-
- tInts[GTPOLLUTION] += event.getData().getInteger("GTPOLLUTION");//Defaults to 0, add stored pollution to data
- tInts[GTMETADATA] = LOADED;//mark as = loaded
- //store in HASHMAP
-
- chunkData.put(event.getChunk().getChunkCoordIntPair(), tInts);
- }//else if(tInts[0]==1){
- ////Already loaded chunk data
- ////DO NOTHING - this chunk data was already loaded and stored in hash map
- //}
+ GT_UndergroundOil.migrate(event);
+ GT_Pollution.migrate(event);
}
@SubscribeEvent
diff --git a/src/main/java/gregtech/common/GT_UndergroundOil.java b/src/main/java/gregtech/common/GT_UndergroundOil.java
index 1f45b5fca9..cc6771dc90 100644
--- a/src/main/java/gregtech/common/GT_UndergroundOil.java
+++ b/src/main/java/gregtech/common/GT_UndergroundOil.java
@@ -5,22 +5,29 @@ 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 net.minecraft.world.ChunkCoordIntPair;
+import gregtech.api.util.GT_ChunkAssociatedData;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
-import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.event.world.ChunkDataEvent;
+import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
-import java.util.HashMap;
+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;
-import static gregtech.common.GT_Proxy.*;
/**
* 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);
@@ -37,81 +44,221 @@ public class GT_UndergroundOil {
//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) {
- World aWorld = chunk.worldObj;
- int dimensionId=aWorld.provider.dimensionId;
- GT_UO_Dimension dimension=GT_Mod.gregtechproxy.mUndergroundOil.GetDimension(dimensionId);
- if(dimension==null) return null;
-
- //Read hash map
- HashMap chunkData = dimensionWiseChunkData.get(dimensionId);
- if(chunkData==null){
- chunkData=new HashMap<>(1024);
- dimensionWiseChunkData.put(dimensionId,chunkData);
- }
-
- int[] tInts = chunkData.get(chunk.getChunkCoordIntPair());
-
- if(tInts==null) tInts=getDefaultChunkDataOnCreation();//init if null
- else if(tInts[GTOIL]==0){//FAST stop
- //can return 0 amount stack for info :D
- return readOrDrainCoefficient>=0 ? null : new FluidStack(FluidRegistry.getFluid(tInts[GTOILFLUID]),0);
- }
-
- //GEN IT TO GET OBJECT...
- final XSTR tRandom = new XSTR(aWorld.getSeed() + dimensionId * 2 +
- (chunk.getChunkCoordIntPair().chunkXPos>>3) +
- 8267 * (chunk.getChunkCoordIntPair().chunkZPos>>3));
-
- GT_UO_Fluid uoFluid = dimension.getRandomFluid(tRandom);
-
- //Fluid stack holder
- FluidStack fluidInChunk;
-
- //Set fluid stack from uoFluid
- if (uoFluid == null || uoFluid.getFluid()==null){
- tInts[GTOILFLUID]=Integer.MAX_VALUE;//null fluid pointer... kind of
- tInts[GTOIL]=0;
- chunkData.put(chunk.getChunkCoordIntPair(),tInts);//update hash map
+ ChunkData chunkData = STORAGE.get(chunk);
+ if (chunkData.getVein() == null || chunkData.getFluid() == null) // nothing here...
return null;
- } else {
- if(tInts[GTOILFLUID]== uoFluid.getFluid().getID()){//if stored fluid matches uoFluid
- fluidInChunk = new FluidStack(uoFluid.getFluid(),tInts[GTOIL]);
- }else{
- fluidInChunk = new FluidStack(uoFluid.getFluid(), uoFluid.getRandomAmount(tRandom));
- fluidInChunk.amount=(int)((float)fluidInChunk.amount*(0.75f+(XSTR_INSTANCE.nextFloat()/2f)));//Randomly change amounts by +/- 25%
- }
- tInts[GTOIL]=fluidInChunk.amount;
- tInts[GTOILFLUID]=fluidInChunk.getFluidID();
- }
-
//do stuff on it if needed
+ FluidStack fluidInChunk = new FluidStack(chunkData.getFluid(), 0);
if(readOrDrainCoefficient>=0){
- int fluidExtracted=(int)Math.floor(fluidInChunk.amount * (double) readOrDrainCoefficient / DIVIDER);
- double averageDecrease=uoFluid.DecreasePerOperationAmount * (double)readOrDrainCoefficient;
+ 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 || fluidInChunk.amount<=decrease){//decrease - here it is max value of extraction for easy check
- fluidInChunk=null;
- tInts[GTOIL]=0;//so in next access it will stop way above
+ 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"
+ 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
- tInts[GTOIL]-=decrease;//diminish amount, "randomly" adjusted to double value (averageDecrease)
+ 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
- tInts[GTOIL]=0;//so in next access it will stop way above
+ chunkData.setAmount(0);
}else{
fluidInChunk.amount=fluidInChunk.amount/DIVIDER;//give moderate extraction speed
}
}
-
- chunkData.put(chunk.getChunkCoordIntPair(),tInts);//update hash map
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;
+ }
+ }
}
diff --git a/src/main/java/gregtech/common/misc/GT_Command.java b/src/main/java/gregtech/common/misc/GT_Command.java
index 13e26353d8..4f6f77ea70 100644
--- a/src/main/java/gregtech/common/misc/GT_Command.java
+++ b/src/main/java/gregtech/common/misc/GT_Command.java
@@ -61,7 +61,7 @@ public final class GT_Command extends CommandBase {
} else if (test.equals("toggle")) {
String test1 = ss[1].trim();
Stream.of("D1", "D2", "debugCleanroom", "debugDriller", "debugBlockPump", "debugBlockMiner", "debugWorldGen", "debugEntityCramming",
- "debugOrevein", "debugSmallOres", "debugStones", "debugChunkloaders", "debugMulti")
+ "debugOrevein", "debugSmallOres", "debugStones", "debugChunkloaders", "debugMulti", "debugWorldData")
.filter(s -> test1.isEmpty() || s.startsWith(test1))
.forEach(l::add);
--
cgit