diff options
Diffstat (limited to 'src/main')
| -rw-r--r-- | src/main/java/gregtech/GT_Mod.java | 1 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/enums/GT_Values.java | 4 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/objects/GT_UO_Dimension.java | 7 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java | 435 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/util/GT_Utility.java | 79 | ||||
| -rw-r--r-- | src/main/java/gregtech/common/GT_Pollution.java | 341 | ||||
| -rw-r--r-- | src/main/java/gregtech/common/GT_Proxy.java | 75 | ||||
| -rw-r--r-- | src/main/java/gregtech/common/GT_UndergroundOil.java | 267 | ||||
| -rw-r--r-- | src/main/java/gregtech/common/misc/GT_Command.java | 2 | 
9 files changed, 931 insertions, 280 deletions
| 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 @@ -272,6 +272,10 @@ public class GT_Values {       */      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.       */      public static int ticksBetweenSounds = 30; 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. + * <p> + * 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) + * <p> + * 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. + * <p> + * It should be noted this class is NOT thread safe. + * <p> + * Element cannot be null. + * <p> + * TODO: Implement automatic region unloading. + * + * @param <T> data element type + * @author glease + */ +@ParametersAreNonnullByDefault +public abstract class GT_ChunkAssociatedData<T extends GT_ChunkAssociatedData.IData> { +	private static final Map<String, GT_ChunkAssociatedData<?>> 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<T> 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<Integer, Map<ChunkCoordIntPair, SuperRegion>> 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<T> 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<ChunkCoordIntPair, SuperRegion> 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<SuperRegion> 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. +	 * <p> +	 * 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<ChunkCoordIntPair, SuperRegion> 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> 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..865fa83f7e 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) -	 *  +	 * <p>  	 * 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 -	 *  +	 * <p>  	 * Pollution producers (pollution/sec)  	 * Bronze Boiler(20)  	 * Lava Boiler(20) @@ -52,7 +57,7 @@ public class GT_Pollution {  	 * Diesel Generator(40/80/160)  	 * Gas Turbine(20/40/80)  	 * Charcoal Pile(100) -	 *  +	 * <p>  	 * Large Diesel Engine(320)  	 * Electric Blast Furnace(100)  	 * Implosion Compressor(2000) @@ -60,33 +65,27 @@ public class GT_Pollution {  	 * Large Gas Turbine(160)  	 * Multi Smelter(100)  	 * Pyrolyse Oven(400) -	 *  +	 * <p>  	 * Machine Explosion(100,000) -	 *  +	 * <p>  	 * Other Random Shit: lots and lots -	 *  +	 * <p>  	 * Muffler Hatch Pollution reduction:  ** inaccurate **  	 * LV (0%), MV (30%), HV (52%), EV (66%), IV (76%), LuV (84%), ZPM (89%), UV (92%), MAX (95%)  	 */  	private List<ChunkCoordIntPair> pollutionList = new ArrayList<>();//chunks left to process -	private HashMap<ChunkCoordIntPair,int[]> chunkData;//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 List<ChunkCoordIntPair> 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 world;  	public static int mPlayerPollution;  	private static int POLLUTIONPACKET_MINVALUE = 1000;  	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); +	public GT_Pollution(World world) { +		this.world = world;  		if (EVENT_HANDLER == null) {  			EVENT_HANDLER = new GT_PollutionEventHandler(); @@ -94,61 +93,61 @@ public class GT_Pollution {  		}  	} -	public static void onWorldTick(TickEvent.WorldTickEvent aEvent){//called from proxy +	public static void onWorldTick(TickEvent.WorldTickEvent aEvent) {//called from proxy  		//return if pollution disabled -		if(!GT_Mod.gregtechproxy.mPollution) return; +		if (!GT_Mod.gregtechproxy.mPollution) return;  		final GT_Pollution pollutionInstance = dimensionWisePollution.get(aEvent.world.provider.dimensionId); -		if(pollutionInstance==null)return; -		pollutionInstance.tickPollutionInWorld((int)(aEvent.world.getTotalWorldTime()%cycleLen)); +		if (pollutionInstance == null) return; +		pollutionInstance.tickPollutionInWorld((int) (aEvent.world.getTotalWorldTime() % cycleLen));  	} -	private void tickPollutionInWorld(int aTickID){//called from method above +	private void tickPollutionInWorld(int aTickID) {//called from method above  		//gen data set -		if(aTickID==0){ -			pollutionList = new ArrayList<>(chunkData.keySet()); +		if (aTickID == 0) { +			// 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 +			if (pollutionList.size() > 0) operationsPerTick = (pollutionList.size() / cycleLen); +			else operationsPerTick = 0;//SANity  		} -		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()); +		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  			//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 = (int) (0.9945f * tPollution);  			//tPollution -= 2000;//This does not really matter... -			if(tPollution<=0) tPollution = 0;//SANity check -			else if(tPollution>400000){//Spread Pollution +			if (tPollution <= 0) tPollution = 0;//SANity check +			else if (tPollution > 400000) {//Spread Pollution  				ChunkCoordIntPair[] tNeighbors = new ChunkCoordIntPair[4];//array is faster -				tNeighbors[0]=(new ChunkCoordIntPair(actualPos.chunkXPos+1,actualPos.chunkZPos)); -				tNeighbors[1]=(new ChunkCoordIntPair(actualPos.chunkXPos-1,actualPos.chunkZPos)); -				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... +				tNeighbors[0] = (new ChunkCoordIntPair(actualPos.chunkXPos + 1, actualPos.chunkZPos)); +				tNeighbors[1] = (new ChunkCoordIntPair(actualPos.chunkXPos - 1, actualPos.chunkZPos)); +				tNeighbors[2] = (new ChunkCoordIntPair(actualPos.chunkXPos, actualPos.chunkZPos + 1)); +				tNeighbors[3] = (new ChunkCoordIntPair(actualPos.chunkXPos, actualPos.chunkZPos - 1)); +				for (ChunkCoordIntPair neighborPosition : tNeighbors) { +					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; +						tDiff = tDiff / 20; +						neighborPollution = GT_Utility.safeInt((long) neighborPollution + tDiff);//tNPol += tDiff;  						tPollution -= tDiff; -						chunkData.get(neighborPosition)[GTPOLLUTION] = neighborPollution; +						neighbor.setAmount(neighborPollution);  					}  				}  				//Create Pollution effects  				//Smog filter TODO -				if(tPollution > GT_Mod.gregtechproxy.mPollutionSmogLimit) { +				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<EntityLivingBase> tEntitys = aWorld.getEntitiesWithinAABB(EntityLivingBase.class, chunk); +					List<EntityLivingBase> tEntitys = world.getEntitiesWithinAABB(EntityLivingBase.class, chunk);  					for (EntityLivingBase tEnt : tEntitys) {  						if (tEnt instanceof EntityPlayerMP && ((EntityPlayerMP) tEnt).capabilities.isCreativeMode)  							continue; @@ -194,131 +193,211 @@ 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);  			}  		}  	} -	private static void damageBlock(World world, int x, int y, int z, boolean sourRain){ -		if (world.isRemote)	return; +	private static void damageBlock(World world, int x, int y, int z, boolean sourRain) { +		if (world.isRemote) return;  		Block tBlock = world.getBlock(x, y, z);  		int tMeta = world.getBlockMetadata(x, y, z); -		if (tBlock == Blocks.air || tBlock == Blocks.stone || tBlock == Blocks.sand|| tBlock == Blocks.deadbush)return; - -			if (tBlock == Blocks.leaves || tBlock == Blocks.leaves2 || tBlock.getMaterial() == Material.leaves) -				world.setBlockToAir(x, y, z); -			if (tBlock == Blocks.reeds) { -				tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); -				world.setBlockToAir(x, y, z); -			} -			if (tBlock == Blocks.tallgrass) -				world.setBlock(x, y, z, Blocks.deadbush); -			if (tBlock == Blocks.vine) { -				tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); -				world.setBlockToAir(x, y, z); -			} -			if (tBlock == Blocks.waterlily || tBlock == Blocks.wheat || tBlock == Blocks.cactus || +		if (tBlock == Blocks.air || tBlock == Blocks.stone || tBlock == Blocks.sand || tBlock == Blocks.deadbush) +			return; + +		if (tBlock == Blocks.leaves || tBlock == Blocks.leaves2 || tBlock.getMaterial() == Material.leaves) +			world.setBlockToAir(x, y, z); +		if (tBlock == Blocks.reeds) { +			tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); +			world.setBlockToAir(x, y, z); +		} +		if (tBlock == Blocks.tallgrass) +			world.setBlock(x, y, z, Blocks.deadbush); +		if (tBlock == Blocks.vine) { +			tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); +			world.setBlockToAir(x, y, z); +		} +		if (tBlock == Blocks.waterlily || tBlock == Blocks.wheat || tBlock == Blocks.cactus ||  				tBlock.getMaterial() == Material.cactus || tBlock == Blocks.melon_block || tBlock == Blocks.melon_stem) { -				tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); -				world.setBlockToAir(x, y, z); -			} -			if (tBlock == Blocks.red_flower || tBlock == Blocks.yellow_flower || tBlock == Blocks.carrots || +			tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); +			world.setBlockToAir(x, y, z); +		} +		if (tBlock == Blocks.red_flower || tBlock == Blocks.yellow_flower || tBlock == Blocks.carrots ||  				tBlock == Blocks.potatoes || tBlock == Blocks.pumpkin || tBlock == Blocks.pumpkin_stem) { -				tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); -				world.setBlockToAir(x, y, z); -			} -			if (tBlock == Blocks.sapling || tBlock.getMaterial() == Material.plants) -				world.setBlock(x, y, z, Blocks.deadbush); -			if (tBlock == Blocks.cocoa) { -				tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); -				world.setBlockToAir(x, y, z); -			} -			if (tBlock == Blocks.mossy_cobblestone) +			tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); +			world.setBlockToAir(x, y, z); +		} +		if (tBlock == Blocks.sapling || tBlock.getMaterial() == Material.plants) +			world.setBlock(x, y, z, Blocks.deadbush); +		if (tBlock == Blocks.cocoa) { +			tBlock.dropBlockAsItem(world, x, y, z, tMeta, 0); +			world.setBlockToAir(x, y, z); +		} +		if (tBlock == Blocks.mossy_cobblestone) +			world.setBlock(x, y, z, Blocks.cobblestone); +		if (tBlock == Blocks.grass || tBlock.getMaterial() == Material.grass) +			world.setBlock(x, y, z, Blocks.dirt); +		if (tBlock == Blocks.farmland || tBlock == Blocks.dirt) { +			world.setBlock(x, y, z, Blocks.sand); +		} + +		if (sourRain && world.isRaining() && (tBlock == Blocks.stone || tBlock == Blocks.gravel || tBlock == Blocks.cobblestone) && +				world.getBlock(x, y + 1, z) == Blocks.air && world.canBlockSeeTheSky(x, y, z)) { +			if (tBlock == Blocks.stone) {  				world.setBlock(x, y, z, Blocks.cobblestone); -			if (tBlock == Blocks.grass || tBlock.getMaterial() == Material.grass ) -				world.setBlock(x, y, z, Blocks.dirt); -			if(tBlock == Blocks.farmland || tBlock == Blocks.dirt){ +			} else if (tBlock == Blocks.cobblestone) { +				world.setBlock(x, y, z, Blocks.gravel); +			} else if (tBlock == Blocks.gravel) {  				world.setBlock(x, y, z, Blocks.sand);  			} - -			if(sourRain && world.isRaining() && (tBlock == Blocks.stone || tBlock == Blocks.gravel || tBlock == Blocks.cobblestone) && -				world.getBlock(x, y+1, z) == Blocks.air && world.canBlockSeeTheSky(x, y, z)){ -				if(tBlock == Blocks.stone){world.setBlock(x, y, z, Blocks.cobblestone);	} -				else if(tBlock == Blocks.cobblestone){world.setBlock(x, y, z, Blocks.gravel);	} -				else if(tBlock == Blocks.gravel){world.setBlock(x, y, z, Blocks.sand);	} -			} +		}  	} -	public static void addPollution(IGregTechTileEntity te, int aPollution){ -		addPollution(te.getWorld().getChunkFromBlockCoords(te.getXCoord(),te.getZCoord()), aPollution); +	public static void addPollution(IGregTechTileEntity te, int aPollution) { +		addPollution(te.getWorld().getChunkFromBlockCoords(te.getXCoord(), te.getZCoord()), aPollution);  	} -	public static void addPollution(Chunk ch, int aPollution){ -		if(!GT_Mod.gregtechproxy.mPollution)return; -		HashMap<ChunkCoordIntPair,int[]> 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; +	public static void addPollution(Chunk ch, int aPollution) { +		if (!GT_Mod.gregtechproxy.mPollution || aPollution == 0) return; +		STORAGE.get(ch).changeAmount(aPollution);  	} -	public static int getPollution(IGregTechTileEntity te){ -		return getPollution(te.getWorld().getChunkFromBlockCoords(te.getXCoord(),te.getZCoord())); +	public static int getPollution(IGregTechTileEntity te) { +		return getPollution(te.getWorld().getChunkFromBlockCoords(te.getXCoord(), te.getZCoord()));  	} -	public static int getPollution(Chunk ch){ -		if(!GT_Mod.gregtechproxy.mPollution) +	public static int getPollution(Chunk ch) { +		if (!GT_Mod.gregtechproxy.mPollution)  			return 0; -		HashMap<ChunkCoordIntPair,int[]> 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<ChunkCoordIntPair, int[]> 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  	@Deprecated /*Don't use it... too weird way of passing position*/ -	public static void addPollution(World aWorld, ChunkPosition aPos, int aPollution){ +	public static void addPollution(World aWorld, ChunkPosition aPos, int aPollution) {  		//The abuse of ChunkPosition to store block position and dim...   		//is just bad especially when that is both used to store ChunkPos and BlockPos depending on context -		addPollution(aWorld.getChunkFromBlockCoords(aPos.chunkPosX,aPos.chunkPosZ),aPollution); +		addPollution(aWorld.getChunkFromBlockCoords(aPos.chunkPosX, aPos.chunkPosZ), aPollution); +	} + +	static void migrate(ChunkDataEvent.Load e) { +		addPollution(e.getChunk(), e.getData().getInteger("GTPOLLUTION"));  	} -	public class GT_PollutionEventHandler { +	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<ChunkData> { +		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<ChunkCoordIntPair> 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<Integer,HashMap<ChunkCoordIntPair,int []>> dimensionWiseChunkData = new HashMap<>(16);//stores chunk data that is loaded/saved      public static final HashMap<Integer,GT_Pollution> 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<ChunkPosition, int[]>  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<ChunkCoordIntPair,int []> 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<ChunkCoordIntPair, int[]> 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<ChunkCoordIntPair, int[]> 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. +     * <p> +     * Primary functionality: +     * +     * <ul> +     *     <li>Decouple data storage with chunk, making it possible to pump oil from unloaded chunks</li> +     *     <li>Regen detection. If fluid generation config is changed, chunk fluid will be regenerated.</li> +     * </ul> +     * +     * <h2>Serialized form</h2> +     * <p> +     * Since the exact file layout is controlled by the super class, here we only concern how each chunk's data is written. +     * <h3>Form A: Empty Chunk</h3> +     * <ol> +     *     <li>4 bytes of 0 </li> +     * </ol> +     * +     * <h3>Form B: Normal Chunk</h3> +     * <ol> +     *     <li>4 bytes unsigned integer. Vein Hash.</li> +     *     <li>UTF string. Vein Key.</li> +     *     <li>4 bytes signed integer. Fluid amount.</li> +     * </ol> +     * +     * @author glease +     */ +    @ParametersAreNonnullByDefault +    private static class GT_UndergroundOilStore extends GT_ChunkAssociatedData<ChunkData> { +        private static final GT_UndergroundOil.ChunkData NIL_FLUID_STACK = new GT_UndergroundOil.ChunkData(-1, null, null, false); +        private static final WeakHashMap<GT_UO_Fluid, Integer> 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); | 
