diff options
| author | Martin Robertz <dream-master@gmx.net> | 2021-11-17 18:00:30 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-17 18:00:30 +0100 |
| commit | 1dd66efceca26d9931010adb24932bba408ca630 (patch) | |
| tree | b2a778c8108323b32ead067a1714ca0e99cf438e /src/main/java/gregtech/api/util | |
| parent | 9c933933f1d5e99fc618d2e6d5f07238ccb51ba3 (diff) | |
| parent | c696e261ab826d711a09622aacff841a01cfeb2a (diff) | |
| download | GT5-Unofficial-1dd66efceca26d9931010adb24932bba408ca630.tar.gz GT5-Unofficial-1dd66efceca26d9931010adb24932bba408ca630.tar.bz2 GT5-Unofficial-1dd66efceca26d9931010adb24932bba408ca630.zip | |
Merge branch 'experimental' into lock-single-recipe
Diffstat (limited to 'src/main/java/gregtech/api/util')
| -rw-r--r-- | src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java | 437 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/util/GT_CoverBehavior.java | 202 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java | 482 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/util/GT_Utility.java | 70 | ||||
| -rw-r--r-- | src/main/java/gregtech/api/util/ISerializableObject.java | 150 |
5 files changed, 1280 insertions, 61 deletions
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..bd9148b516 --- /dev/null +++ b/src/main/java/gregtech/api/util/GT_ChunkAssociatedData.java @@ -0,0 +1,437 @@ +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(Math.floorMod(chunkX, regionLength), Math.floorMod(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(Math.floorMod(chunkX, regionLength), Math.floorMod(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(Math.floorMod(chunkX, regionLength), Math.floorMod(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) { + Map<ChunkCoordIntPair, SuperRegion> map = masterMap.get(world.provider.dimensionId); + if (map != null) + saveRegions(map.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_CoverBehavior.java b/src/main/java/gregtech/api/util/GT_CoverBehavior.java index 14c8cc1308..9972331eb3 100644 --- a/src/main/java/gregtech/api/util/GT_CoverBehavior.java +++ b/src/main/java/gregtech/api/util/GT_CoverBehavior.java @@ -3,21 +3,179 @@ package gregtech.api.util; import gregtech.api.enums.GT_Values; import gregtech.api.interfaces.tileentity.ICoverable; import gregtech.api.net.GT_Packet_TileEntityCoverGUI; -import gregtech.api.objects.GT_ItemStack; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.world.World; import net.minecraftforge.fluids.Fluid; import static gregtech.api.enums.GT_Values.E; /** - * For Covers with a special behavior. + * For Covers with a special behavior. Has fixed storage format of 4 byte. Not very convenient... */ -public abstract class GT_CoverBehavior { +public abstract class GT_CoverBehavior extends GT_CoverBehaviorBase<ISerializableObject.LegacyCoverData> { public EntityPlayer lastPlayer = null; + public GT_CoverBehavior() { + super(ISerializableObject.LegacyCoverData.class); + } + + private static int convert(ISerializableObject.LegacyCoverData data) { + return data == null ? 0 : data.get(); + } + + // region bridge the parent call to legacy calls + + @Override + public final ISerializableObject.LegacyCoverData createDataObject() { + return new ISerializableObject.LegacyCoverData(); + } + + @Override + public ISerializableObject.LegacyCoverData createDataObject(int aLegacyData) { + return new ISerializableObject.LegacyCoverData(aLegacyData); + } + + @Override + protected boolean isRedstoneSensitiveImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, long aTimer) { + return isRedstoneSensitive(aSide, aCoverID, aCoverVariable.get(), aTileEntity, aTimer); + } + + @Override + protected ISerializableObject.LegacyCoverData doCoverThingsImpl(byte aSide, byte aInputRedstone, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, long aTimer) { + if (aCoverVariable == null) + aCoverVariable = new ISerializableObject.LegacyCoverData(); + aCoverVariable.set(doCoverThings(aSide, aInputRedstone, aCoverID, aCoverVariable.get(), aTileEntity, aTimer)); + return aCoverVariable; + } + + @Override + protected boolean onCoverRightClickImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) { + return onCoverRightclick(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ); + } + + @Override + protected ISerializableObject.LegacyCoverData onCoverScrewdriverClickImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) { + if (aCoverVariable == null) + aCoverVariable = new ISerializableObject.LegacyCoverData(); + aCoverVariable.set(onCoverScrewdriverclick(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ)); + return aCoverVariable; + } + + @Override + protected boolean onCoverShiftRightClickImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer) { + return onCoverShiftRightclick(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aPlayer); + } + + @Override + protected Object getClientGUIImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, World aWorld) { + return getClientGUI(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean onCoverRemovalImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity, boolean aForced) { + return onCoverRemoval(aSide, aCoverID, convert(aCoverVariable), aTileEntity, aForced); + } + + @Override + protected String getDescriptionImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return getDescription(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected float getBlastProofLevelImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return getBlastProofLevel(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean letsRedstoneGoInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return letsRedstoneGoIn(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean letsRedstoneGoOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return letsRedstoneGoOut(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean letsFibreGoInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return letsFibreGoIn(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean letsFibreGoOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return letsFibreGoOut(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean letsEnergyInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return letsEnergyIn(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean letsEnergyOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return letsEnergyOut(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean letsFluidInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, Fluid aFluid, ICoverable aTileEntity) { + return letsFluidIn(aSide, aCoverID, convert(aCoverVariable), aFluid, aTileEntity); + } + + @Override + protected boolean letsFluidOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, Fluid aFluid, ICoverable aTileEntity) { + return letsFluidOut(aSide, aCoverID, convert(aCoverVariable), aFluid, aTileEntity); + } + + @Override + protected boolean letsItemsInImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, int aSlot, ICoverable aTileEntity) { + return letsItemsIn(aSide, aCoverID, convert(aCoverVariable), aSlot, aTileEntity); + } + + @Override + protected boolean letsItemsOutImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, int aSlot, ICoverable aTileEntity) { + return letsItemsOut(aSide, aCoverID, convert(aCoverVariable), aSlot, aTileEntity); + } + + @Override + protected boolean isGUIClickableImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return isGUIClickable(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean manipulatesSidedRedstoneOutputImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return manipulatesSidedRedstoneOutput(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected boolean alwaysLookConnectedImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return alwaysLookConnected(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected byte getRedstoneInputImpl(byte aSide, byte aInputRedstone, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return getRedstoneInput(aSide, aInputRedstone, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected int getTickRateImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return getTickRate(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected byte getLensColorImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return getLensColor(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + @Override + protected ItemStack getDropImpl(byte aSide, int aCoverID, ISerializableObject.LegacyCoverData aCoverVariable, ICoverable aTileEntity) { + return getDrop(aSide, aCoverID, convert(aCoverVariable), aTileEntity); + } + + // endregion + public boolean isRedstoneSensitive(byte aSide, int aCoverID, int aCoverVariable, ICoverable aTileEntity, long aTimer) { return true; } @@ -39,15 +197,6 @@ public abstract class GT_CoverBehavior { } /** - * Called when someone rightclicks this Cover Client Side - * <p/> - * return true, if something actually happens. - */ - public boolean onCoverRightclickClient(byte aSide, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) { - return false; - } - - /** * Called when someone rightclicks this Cover with a Screwdriver. Doesn't call @onCoverRightclick in this Case. * <p/> * return the new Value of the Cover Variable @@ -68,22 +217,11 @@ public abstract class GT_CoverBehavior { return false; } - public boolean hasCoverGUI() { - return false; - } - public Object getClientGUI(byte aSide, int aCoverID, int coverData, ICoverable aTileEntity) { return null; } /** - * Checks if the Cover can be placed on this. - */ - public boolean isCoverPlaceable(byte aSide, GT_ItemStack aStack, ICoverable aTileEntity) { - return true; - } - - /** * Removes the Cover if this returns true, or if aForced is true. * Doesn't get called when the Machine Block is getting broken, only if you break the Cover away from the Machine. */ @@ -219,13 +357,6 @@ public abstract class GT_CoverBehavior { } /** - * If this is a simple Cover, which can also be used on Bronze Machines and similar. - */ - public boolean isSimpleCover() { - return false; - } - - /** * The MC Color of this Lens. -1 for no Color (meaning this isn't a Lens then). */ public byte getLensColor(byte aSide, int aCoverID, int aCoverVariable, ICoverable aTileEntity) { @@ -238,15 +369,4 @@ public abstract class GT_CoverBehavior { public ItemStack getDrop(byte aSide, int aCoverID, int aCoverVariable, ICoverable aTileEntity) { return GT_OreDictUnificator.get(true, aTileEntity.getCoverItemAtSide(aSide)); } - - /** - * @return sets the Cover upon placement. - */ - public void placeCover(byte aSide, ItemStack aCover, ICoverable aTileEntity) { - aTileEntity.setCoverIDAtSide(aSide, GT_Utility.stackToInt(aCover)); - } - - public String trans(String aNr, String aEnglish){ - return GT_LanguageManager.addStringLocalization("Interaction_DESCRIPTION_Index_"+aNr, aEnglish, false); - } } diff --git a/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java b/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java new file mode 100644 index 0000000000..50ef2632d9 --- /dev/null +++ b/src/main/java/gregtech/api/util/GT_CoverBehaviorBase.java @@ -0,0 +1,482 @@ +package gregtech.api.util; + +import gregtech.api.enums.GT_Values; +import gregtech.api.interfaces.tileentity.ICoverable; +import gregtech.api.net.GT_Packet_TileEntityCoverGUI; +import gregtech.api.objects.GT_ItemStack; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.world.World; +import net.minecraftforge.fluids.Fluid; + +import static gregtech.api.enums.GT_Values.E; + +/** + * For Covers with a special behavior. + * + * @author glease + */ +public abstract class GT_CoverBehaviorBase<T extends ISerializableObject> { + + public EntityPlayer lastPlayer = null; + private final Class<T> typeToken; + + protected GT_CoverBehaviorBase(Class<T> typeToken) { + this.typeToken = typeToken; + } + + public abstract T createDataObject(int aLegacyData); + + public abstract T createDataObject(); + + public T createDataObject(NBTBase aNBT) { + T ret = createDataObject(); + ret.loadDataFromNBT(aNBT); + return ret; + } + + public final T cast(ISerializableObject aData) { + if (typeToken.isInstance(aData)) + return forceCast(aData); + return null; + } + + private T forceCast(ISerializableObject aData) { + return typeToken.cast(aData); + } + + // region facade + + public final boolean isRedstoneSensitive(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, long aTimer) { + return isRedstoneSensitiveImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aTimer); + } + + /** + * Called by updateEntity inside the covered TileEntity. aCoverVariable is the Value you returned last time. + */ + public final T doCoverThings(byte aSide, byte aInputRedstone, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, long aTimer) { + return doCoverThingsImpl(aSide, aInputRedstone, aCoverID, forceCast(aCoverVariable), aTileEntity, aTimer); + } + + /** + * Called when someone rightclicks this Cover. + * <p/> + * return true, if something actually happens. + */ + public final boolean onCoverRightClick(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) { + return onCoverRightClickImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ); + } + + /** + * Called when someone rightclicks this Cover with a Screwdriver. Doesn't call @onCoverRightclick in this Case. + * <p/> + * return the new Value of the Cover Variable + */ + public final T onCoverScrewdriverClick(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, float aX, float aY, float aZ) { + return onCoverScrewdriverClickImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer, aX, aY, aZ); + } + + /** + * Called when someone shift-rightclicks this Cover with no tool. Doesn't call @onCoverRightclick in this Case. + */ + public final boolean onCoverShiftRightClick(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer) { + return onCoverShiftRightClickImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer); + } + + public final Object getClientGUI(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, EntityPlayer aPlayer, World aWorld) { + return getClientGUIImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aPlayer, aWorld); + } + + /** + * Removes the Cover if this returns true, or if aForced is true. + * Doesn't get called when the Machine Block is getting broken, only if you break the Cover away from the Machine. + */ + public final boolean onCoverRemoval(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity, boolean aForced) { + return onCoverRemovalImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity, aForced); + } + + /** + * Gives a small Text for the status of the Cover. + */ + public final String getDescription(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return getDescriptionImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * How Blast Proof the Cover is. 30 is normal. + */ + public final float getBlastProofLevel(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return getBlastProofLevelImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * If it lets RS-Signals into the Block + * <p/> + * This is just Informative so that Machines know if their Redstone Input is blocked or not + */ + public final boolean letsRedstoneGoIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return letsRedstoneGoInImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * If it lets RS-Signals out of the Block + */ + public final boolean letsRedstoneGoOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return letsRedstoneGoOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * If it lets Fibre-Signals into the Block + * <p/> + * This is just Informative so that Machines know if their Redstone Input is blocked or not + */ + public final boolean letsFibreGoIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return letsFibreGoInImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * If it lets Fibre-Signals out of the Block + */ + public final boolean letsFibreGoOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return letsFibreGoOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * If it lets Energy into the Block + */ + public final boolean letsEnergyIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return letsEnergyInImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * If it lets Energy out of the Block + */ + public final boolean letsEnergyOut(byte aSide, int aCoverID, ISerializableObject aCoverVariable, ICoverable aTileEntity) { + return letsEnergyOutImpl(aSide, aCoverID, forceCast(aCoverVariable), aTileEntity); + } + + /** + * If it lets Liquids into the Block, aFluid can be null meaning if this is generally allowing Fluids or not. + */ + public final boolean letsFluidIn(byte aSide, int aCoverID, ISerializableObject aCoverVariable, Fluid aFluid, ICoverable aTileEntity) { + |
