From 17183b38247f0aae179f43e641a11a32ad9a7a78 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Tue, 28 Mar 2023 23:58:39 +0200 Subject: [PATCH] netcode works --- .gitignore | 1 + README.MD | 19 --- dependencies.gradle | 2 + readme.md | 60 +++++++++ .../chunk/api/ChunkDataManager.java | 70 ++++++---- .../chunk/api/ChunkDataRegistry.java | 12 +- .../falsepattern/chunk/api/ModdedChunk.java | 38 ------ .../falsepattern/chunk/internal/ChunkAPI.java | 23 +++- .../chunk/internal/ChunkDataRegistryImpl.java | 94 ++++++++++--- .../falsepattern/chunk/internal/Common.java | 3 + .../mixins/client/vanilla/ChunkMixin.java | 92 +++++++++++++ .../mixins/common/vanilla/ChunkDataMixin.java | 48 ------- .../vanilla/S21PacketChunkDataMixin.java | 126 ++++++++++++++++++ .../vanilla/S26PacketMapChunkBulkMixin.java | 110 +++++++++++++++ .../chunk/internal/mixin/plugin/Mixin.java | 6 + .../net/minecraft/world/chunk/Chunk.java | 12 -- .../chunk/internal/vanilla/BiomeManager.java | 46 +++++++ .../internal/vanilla/BlockIDManager.java | 83 ++++++++++++ .../internal/vanilla/BlocklightManager.java | 16 +++ .../internal/vanilla/MetadataManager.java | 16 +++ .../chunk/internal/vanilla/NibbleManager.java | 59 ++++++++ .../internal/vanilla/SkylightManager.java | 33 +++++ .../internal/vanilla/VanillaManager.java | 17 +++ ..._chunkapi.json => mixins.fp_chunkapi.json} | 2 +- 24 files changed, 827 insertions(+), 161 deletions(-) delete mode 100644 README.MD create mode 100644 readme.md delete mode 100644 src/main/java/com/falsepattern/chunk/api/ModdedChunk.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/mixin/mixins/client/vanilla/ChunkMixin.java delete mode 100644 src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/ChunkDataMixin.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S21PacketChunkDataMixin.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S26PacketMapChunkBulkMixin.java delete mode 100644 src/main/java/com/falsepattern/chunk/internal/stubpackage/net/minecraft/world/chunk/Chunk.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java create mode 100644 src/main/java/com/falsepattern/chunk/internal/vanilla/VanillaManager.java rename src/main/resources/{mixins.fplib_chunkapi.json => mixins.fp_chunkapi.json} (83%) diff --git a/.gitignore b/.gitignore index bf03ff1..ab0dd68 100644 --- a/.gitignore +++ b/.gitignore @@ -223,3 +223,4 @@ gradle-app.setting *.hprof # End of https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,forgegradle,java,eclipse,netbeans +srgmap.cfg \ No newline at end of file diff --git a/README.MD b/README.MD deleted file mode 100644 index f5b03da..0000000 --- a/README.MD +++ /dev/null @@ -1,19 +0,0 @@ -# ChunkAPI -A multipurpose chunk hooking library that makes adding arbitrary extra data to chunks much easier. - -## How -Read the code. -TL;DR: Replacing the vanilla chunk netcode and save logic with a modular system, and reimplementing the stock vanilla -logic on top of this. You can then add your own stuff. - -## Compatibility -This will definitely break things, but the mods that were developed on top of this would've broken stuff anyways, and -having the most breaking changes centralized in a single hook mod makes writing compat much simpler. - -## API -You can use the api of this mod to add your own chunk data. Minimum requirements you need: -1. A unique ID string for your data (collision avoidance) -2. An object that can be stored in a Map inside each chunk (note: the vanilla replacements bypass this!) -3. Packet data size/serialization/deserialization (for client/server communication) -4. NBT serialization/serialization (for savegames) - diff --git a/dependencies.gradle b/dependencies.gradle index 2c40c12..7ff1b25 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -6,4 +6,6 @@ dependencies { implementation("com.falsepattern:falsepatternlib-mc1.7.10:[0.10.15,0.11):dev") + compileOnly("org.jetbrains:annotations:[24,25)") + } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..a1ab780 --- /dev/null +++ b/readme.md @@ -0,0 +1,60 @@ +Minecraft mod: ChunkAPI +- Author: FalsePattern +- Type: library, coremod +- Status: in progress + + +Summary +------- + +A mod for adding custom data to chunks. + +Goals +----- + +It is a goal to provide a way to add custom data to chunks without having to modify any vanilla classes. +The API will also provide a simple way to implement the custom serialization/deserialization of the data, +both for the networking and the NBT format. +An optional goal is to provide a way to register DFUs for the custom data, but this is not a priority. + +Non-Goals +--------- + +It is not a goal to provide any kind of callback/hook system for anything except saving/loading and netcode, any kind +of extra behavior should be implemented in the mod using ChunkAPI. + +Motivation +---------- + +Modifying chunk packet data involved incompatibly modifying network packets, the chunk class, and the anvil chunk loader, +which is a lot of work for a simple feature. This mod provides a way to add custom data to chunks without having +to modify any of the vanilla classes yourself. + +Description +----------- + +//TODO no solid plan yet, gotta think about it some more + +Alternatives +------------ + +Mods can also implement their own chunk-independent data storage, or even use hidden entities to store data, but +this is not a good solution and prone to breaking. + +Testing +------- + +This mod will be tested by using it in my own mods, primarily EndlessIDs. + +Risks and Assumptions +--------------------- + +This might slow down chunk loading/saving, and the networking, if the implementation turns out to be bad. +Ideally, mods should declare their custom data as new fields in the chunk class using mixins instead of the +ModdedChunk storage, but this is not a requirement. + +Dependencies +------------ + +This mod directly depends on FalsePatternLib, and some sort of Mixin injector that provides SpongePowered Mixin >= 0.8.5. +The latter requirement may change in the future. \ No newline at end of file diff --git a/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java b/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java index 6a21ea6..ba1fb0b 100644 --- a/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java +++ b/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java @@ -1,55 +1,75 @@ package com.falsepattern.chunk.api; +import org.jetbrains.annotations.NotNull; + import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.network.PacketBuffer; -import net.minecraft.world.chunk.Chunk; + +import java.nio.ByteBuffer; /** * Singleton instances that can manage custom in chunks. + * For performance reasons, you should store any data in the chunk itself using a mixin. The type T is the type + * of the interface that you are mixing into the chunk. * Note: All the methods should be thread-safe and stateless unless otherwise specified. */ -public interface ChunkDataManager { +public interface ChunkDataManager { + /** - * The unique identifier of your data. The common convention is modid:dataid. - * @return The unique identifier of your data. Should be unique across all mods. - * @apiNote Ideally, this should be an interned string for maximum performance. (see {@link String#intern()}). - * @implNote This is used to identify your data in packets and NBT tags. + * @return The domain of this manager. Usually the modid of the mod that owns this manager. + */ + String domain(); + + /** + * @return The id of this manager. Usually the name of the manager. Unique per domain. */ String id(); + /** - * Initializes your data in a chunk. This is called when the chunk is first created. + * Used for sorting the managers. Managers with a lower order index are processed first. + * If unsure, return 0, and let the implementation decide. + */ + default int orderIndex() { + return 0; + } + + /** + * Initializes your data in a chunk. This is called at the end of the chunk constructor. One less mixin per manager! * @param chunk The chunk to initialize. */ - void init(ModdedChunk chunk); + void init(T chunk); + /** - * The amount of bytes your data will take up in a packet. - * @param chunk The chunk that the data is in. - * @return The amount of bytes your data will take up in a packet. -1 if the data is not present. + * @implNote This is used to determine the size of the packet compression/decompression buffer. + * @return The maximum amount of bytes your data can take up in a packet. */ - int getPacketSize(ModdedChunk chunk); + int maxPacketSize(); + /** - * Serializes your data into a packet. Not called if {@link #getPacketSize(ModdedChunk)} returns -1. - * @param chunk The chunk that the data is in. - * @param buffer The packet buffer to write to. + * Serializes your data into a packet. + * + * @param chunk The chunk to serialize. */ - void writeToPacket(ModdedChunk chunk, PacketBuffer buffer); + void writeToBuffer(T chunk, int ebsMask, boolean forceUpdate, ByteBuffer data); + /** - * Deserializes your data from a packet. Not called if {@link #getPacketSize(ModdedChunk)} returned -1 on the other end. - * @param chunk The chunk that the data is in. + * Deserializes your data from a packet. + * Mutates this object. + * + * @param chunk The chunk to deserialize. * @param buffer The packet buffer to read from. - * @return The deserialized data. */ - void readFromPacket(ModdedChunk chunk, PacketBuffer buffer); + void readFromBuffer(T chunk, int ebsMask, boolean forceUpdate, ByteBuffer buffer); + /** * Serializes your data into an NBT tag. This is used when saving the chunk to disk. - * @param chunk The chunk that the data is in. * @param tag The tag to write to. */ - void writeToNBT(ModdedChunk chunk, NBTTagCompound tag); + void writeToNBT(T chunk, @NotNull NBTTagCompound tag); + /** * Deserializes your data from an NBT tag. This is used when loading the chunk from disk. - * @param chunk The chunk that the data gets loaded into. + * Mutates this object. * @param tag The tag to read from. */ - void readFromNBT(ModdedChunk chunk, NBTTagCompound tag); + void readFromNBT(T chunk, @NotNull NBTTagCompound tag); } diff --git a/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java b/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java index 557a053..d193803 100644 --- a/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java +++ b/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java @@ -5,12 +5,22 @@ import com.falsepattern.chunk.api.exception.ManagerRegistrationException; /** * This class is used to register ChunkDataManagers. + * @see ChunkDataManager */ public class ChunkDataRegistry { - public static void registerDataManager(ChunkDataManager manager) throws ManagerRegistrationException { + /** + * Registers a ChunkDataManager. Only do this during the preInit phase. + * @param manager The manager to register. + * @throws ManagerRegistrationException If the manager is already registered. + */ + public static void registerDataManager(ChunkDataManager manager) throws ManagerRegistrationException { ChunkDataRegistryImpl.registerDataManager(manager); } + /** + * Disables a ChunkDataManager. Only do this during the preInit phase. + * @param id The id of the manager to disable. + */ public static void disableDataManager(String id) { ChunkDataRegistryImpl.disableDataManager(id); } diff --git a/src/main/java/com/falsepattern/chunk/api/ModdedChunk.java b/src/main/java/com/falsepattern/chunk/api/ModdedChunk.java deleted file mode 100644 index e8270f5..0000000 --- a/src/main/java/com/falsepattern/chunk/api/ModdedChunk.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.falsepattern.chunk.api; - -/** - * This interface is injected into {@link net.minecraft.world.chunk.Chunk} by ChunkAPI. You can cast any Chunk instance - * to this to access the API methods. - */ -public interface ModdedChunk { - /** - * Gets the data stored in this chunk by the specified manager. - * @param manager The manager to get the data from. - * @return The data stored in this chunk by the specified manager. - * @param The type of the data. - */ - T getData(ChunkDataManager manager); - - /** - * Sets the data stored in this chunk by the specified manager. - * @param manager The manager to set the data for. - * @param data The data to store in this chunk. - * @param The type of the data. - */ - void setData(ChunkDataManager manager, T data); - - /** - * Checks if this chunk has data stored by the specified manager. - * @param manager The manager to check for. - * @return True if this chunk has data stored by the specified manager. - */ - boolean hasData(ChunkDataManager manager); - - /** - * Removes the data stored in this chunk by the specified manager. - * @param manager The manager to remove the data from. - * @return The data that was stored in this chunk by the specified manager. - * @param The type of the data. - */ - T removeData(ChunkDataManager manager); -} diff --git a/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java b/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java index ffce22c..354951d 100644 --- a/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java +++ b/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java @@ -1,8 +1,15 @@ package com.falsepattern.chunk.internal; +import com.falsepattern.chunk.api.ChunkDataRegistry; +import com.falsepattern.chunk.internal.vanilla.BiomeManager; +import com.falsepattern.chunk.internal.vanilla.BlockIDManager; +import com.falsepattern.chunk.internal.vanilla.BlocklightManager; +import com.falsepattern.chunk.internal.vanilla.MetadataManager; +import com.falsepattern.chunk.internal.vanilla.SkylightManager; import lombok.Getter; import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; @@ -15,13 +22,27 @@ public class ChunkAPI { @Getter private static boolean registrationStage = false; + @Getter + private static boolean disableStage = false; + @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { registrationStage = true; + disableStage = true; + ChunkDataRegistry.registerDataManager(new BlockIDManager()); + ChunkDataRegistry.registerDataManager(new MetadataManager()); + ChunkDataRegistry.registerDataManager(new BlocklightManager()); + ChunkDataRegistry.registerDataManager(new SkylightManager()); + ChunkDataRegistry.registerDataManager(new BiomeManager()); + } + + @Mod.EventHandler + public void init(FMLInitializationEvent event) { + registrationStage = false; } @Mod.EventHandler public void postInit(FMLPostInitializationEvent event) { - registrationStage = false; + disableStage = false; } } \ No newline at end of file diff --git a/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java b/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java index ae7b5b7..2aa4559 100644 --- a/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java +++ b/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java @@ -5,26 +5,27 @@ import com.falsepattern.chunk.api.exception.ManagerRegistrationException; import lombok.val; import lombok.var; -import java.util.ArrayList; +import net.minecraft.world.chunk.Chunk; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; public class ChunkDataRegistryImpl { + @SuppressWarnings("rawtypes") private static final Map managers = new HashMap<>(); private static final Set disabledManagers = new HashSet<>(); - public static void registerDataManager(ChunkDataManager manager) throws ManagerRegistrationException { + private static int maxPacketSize = 0; + public static void registerDataManager(ChunkDataManager manager) throws ManagerRegistrationException { if (!ChunkAPI.isRegistrationStage()) { throw new ManagerRegistrationException("ChunkDataManager registration is not allowed at this time! " + - "Please register your ChunkDataManager in the init phase of your mod."); + "Please register your ChunkDataManager in the preInit phase of your mod."); } - var id = manager.id(); - if (id == null) { - throw new ManagerRegistrationException("ChunkDataManager " + manager + " has a null id!"); - } - id = id.intern(); + var id = manager.domain() + ":" + manager.id(); if (managers.containsKey(id)) { throw new ManagerRegistrationException("ChunkDataManager " + manager + " has a duplicate id!"); } @@ -35,22 +36,83 @@ public class ChunkDataRegistryImpl { } managers.put(id, manager); + maxPacketSize += 4 + id.getBytes(StandardCharsets.UTF_8).length; + maxPacketSize += manager.maxPacketSize(); } - public static void disableDataManager(String id) { - if (!ChunkAPI.isRegistrationStage()) { + public static void disableDataManager(String domain, String id) { + if (!ChunkAPI.isDisableStage()) { throw new ManagerRegistrationException("ChunkDataManager disabling is not allowed at this time! " + - "Please disable any ChunkDataManagers in the init phase of your mod."); - } - if (id == null) { - throw new ManagerRegistrationException("ChunkDataManager id cannot be null!"); + "Please disable any ChunkDataManagers in the preInit/init phases."); } + val fusedId = domain + ":" + id; id = id.intern(); Common.LOG.warn("Disabling ChunkDataManager " + id + ". See the stacktrace for the source of this event.", new Throwable()); //Remove the manager from the list of managers, if it exists - managers.remove(id); + val removed = managers.remove(id); + if (removed != null) { + maxPacketSize -= 4 + id.getBytes(StandardCharsets.UTF_8).length; + maxPacketSize -= removed.maxPacketSize(); + } //Add the manager to the list of disabled managers, in case it gets registered after this disable call. disabledManagers.add(id); } + + public static int maxPacketSize() { + return maxPacketSize; + } + + private static void writeString(ByteBuffer buffer, String string) { + val bytes = string.getBytes(); + buffer.putInt(bytes.length); + buffer.put(bytes); + } + + private static String readString(ByteBuffer buffer) { + val length = buffer.getInt(); + val bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes); + } + + @SuppressWarnings("unchecked") + public static void readFromBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, byte[] data) { + val buf = ByteBuffer.wrap(data); + buf.order(ByteOrder.LITTLE_ENDIAN); + int count = buf.getInt(); + for (int i = 0; i < count; i++) { + val id = readString(buf); + val length = buf.getInt(); + val manager = managers.get(id); + if (manager == null) { + Common.LOG.error("Received data for unknown ChunkDataManager " + id + ". Skipping."); + buf.position(buf.position() + length); + continue; + } + int start = buf.position(); + val section = ByteBuffer.wrap(data, start, length); + manager.readFromBuffer(chunk, ebsMask, forceUpdate, section); + buf.position(start + length); + } + } + + @SuppressWarnings("unchecked") + public static int writeToBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, byte[] data) { + val buf = ByteBuffer.wrap(data); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(managers.size()); + for (val pair: managers.entrySet()) { + val manager = pair.getValue(); + writeString(buf, pair.getKey()); + int start = buf.position() + 4; + val section = ByteBuffer.wrap(data, start, manager.maxPacketSize()); + manager.writeToBuffer(chunk, ebsMask, forceUpdate, section); + int length = section.position(); + buf.position(start - 4); + buf.putInt(length); + buf.position(start + length); + } + return buf.position(); + } } diff --git a/src/main/java/com/falsepattern/chunk/internal/Common.java b/src/main/java/com/falsepattern/chunk/internal/Common.java index 438c088..d7641d3 100644 --- a/src/main/java/com/falsepattern/chunk/internal/Common.java +++ b/src/main/java/com/falsepattern/chunk/internal/Common.java @@ -8,4 +8,7 @@ import org.apache.logging.log4j.Logger; */ public class Common { public static final Logger LOG = LogManager.getLogger(Tags.MODID); + + public static final int EBS_PER_CHUNK = 16; + public static final int BLOCKS_PER_EBS = 16 * 16 * 16; } diff --git a/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/client/vanilla/ChunkMixin.java b/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/client/vanilla/ChunkMixin.java new file mode 100644 index 0000000..3886077 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/client/vanilla/ChunkMixin.java @@ -0,0 +1,92 @@ +package com.falsepattern.chunk.internal.mixin.mixins.client.vanilla; + +import com.falsepattern.chunk.internal.ChunkDataRegistryImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.block.Block; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Mixin(Chunk.class) +public abstract class ChunkMixin { + @Shadow public Map chunkTileEntityMap; + + @Shadow private ExtendedBlockStorage[] storageArrays; + + @Shadow public boolean isLightPopulated; + + @Shadow public boolean isTerrainPopulated; + + @Shadow public abstract void generateHeightMap(); + + @Shadow public abstract Block getBlock(int p_150810_1_, int p_150810_2_, int p_150810_3_); + + @Shadow public abstract int getBlockMetadata(int p_76628_1_, int p_76628_2_, int p_76628_3_); + + @Shadow public World worldObj; + + @Shadow public abstract ExtendedBlockStorage[] getBlockStorageArray(); + + /** + * @author FalsePattern + * @reason Replace functionality + */ + @Overwrite + public void fillChunk(byte[] data, int ebsMask, int ebsMSBMask, boolean forceUpdate) { + for (Object o : chunkTileEntityMap.values()) { + TileEntity tileEntity = (TileEntity) o; + tileEntity.updateContainingBlockInfo(); + tileEntity.getBlockMetadata(); + tileEntity.getBlockType(); + } + + boolean hasSky = !this.worldObj.provider.hasNoSky; + + for (int i = 0; i < storageArrays.length; i++) { + if ((ebsMask & (1 << i)) != 0) { + if (storageArrays[i] == null) { + storageArrays[i] = new ExtendedBlockStorage(i << 4, hasSky); + } + } else if (forceUpdate && storageArrays[i] != null) { + storageArrays[i] = null; + } + } + + ChunkDataRegistryImpl.readFromBuffer((Chunk)(Object)this, ebsMask, forceUpdate, data); + + for (int i = 0; i < storageArrays.length; ++i) { + if ((storageArrays[i] != null) && ((ebsMask & (1 << i)) != 0)) { + storageArrays[i].removeInvalidBlocks(); + } + } + this.isLightPopulated = true; + this.isTerrainPopulated = true; + this.generateHeightMap(); + List invalidList = new ArrayList<>(); + for (Object o : this.chunkTileEntityMap.values()) { + TileEntity tileentity = (TileEntity) o; + int x = tileentity.xCoord & 15; + int y = tileentity.yCoord; + int z = tileentity.zCoord & 15; + Block block = tileentity.getBlockType(); + if ((block != getBlock(x, y, z) || tileentity.blockMetadata != this.getBlockMetadata(x, y, z)) && + tileentity.shouldRefresh(block, getBlock(x, y, z), tileentity.blockMetadata, + this.getBlockMetadata(x, y, z), worldObj, x, y, z)) { + invalidList.add(tileentity); + } + tileentity.updateContainingBlockInfo(); + } + + for (TileEntity te : invalidList) { + te.invalidate(); + } + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/ChunkDataMixin.java b/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/ChunkDataMixin.java deleted file mode 100644 index be0b6c0..0000000 --- a/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/ChunkDataMixin.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla; - -import com.falsepattern.chunk.api.ChunkDataManager; -import com.falsepattern.chunk.api.ModdedChunk; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.world.World; -import net.minecraft.world.chunk.Chunk; - -import java.util.HashMap; -import java.util.Map; - -/** - * This mixin injects the {@link ModdedChunk} interface into {@link Chunk}, and also creates the data map. - */ -@Mixin(Chunk.class) -public abstract class ChunkDataMixin implements ModdedChunk { - private Map data; - - @Inject(method = "(Lnet/minecraft/world/World;II)V", - at = @At("RETURN")) - private void init(World p_i1995_1_, int p_i1995_2_, int p_i1995_3_, CallbackInfo ci) { - data = new HashMap<>(); - } - - @Override - public T getData(ChunkDataManager manager) { - return (T) data.get(manager.id()); - } - - @Override - public void setData(ChunkDataManager manager, T data) { - this.data.put(manager.id(), data); - } - - @Override - public boolean hasData(ChunkDataManager manager) { - return data.containsKey(manager.id()); - } - - @Override - public T removeData(ChunkDataManager manager) { - return (T) data.remove(manager.id()); - } -} diff --git a/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S21PacketChunkDataMixin.java b/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S21PacketChunkDataMixin.java new file mode 100644 index 0000000..93613bb --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S21PacketChunkDataMixin.java @@ -0,0 +1,126 @@ +package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla; + +import com.falsepattern.chunk.internal.ChunkDataRegistryImpl; +import lombok.val; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.S21PacketChunkData; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import java.io.IOException; +import java.util.concurrent.Semaphore; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +@Mixin(S21PacketChunkData.class) +public abstract class S21PacketChunkDataMixin { + + @Shadow private Semaphore deflateGate; + @Shadow(aliases = "field_149284_a") private int xPosition; + @Shadow(aliases = "field_149282_b") private int zPosition; + @Shadow(aliases = "field_149283_c") private int ebsMask; + @Shadow(aliases = "field_149281_e") private byte[] deflatedData; + @Shadow(aliases = "field_149278_f") private byte[] data; + @Shadow(aliases = "field_149279_g") private boolean forceUpdate; + @Shadow(aliases = "field_149285_h") private int deflatedSize; + @Shadow(aliases = "field_149286_i") private static byte[] buffer; + + @Shadow protected abstract void deflate(); + + + @ModifyConstant(method = {"", "func_149275_c"}, + constant = @Constant(intValue = 196864), + require = 1) + private static int increasePacketSize(int constant) { + return ChunkDataRegistryImpl.maxPacketSize(); + } + + /** + * @author FalsePattern + * @reason Replace functionality + */ + @Overwrite + public static S21PacketChunkData.Extracted func_149269_a(Chunk chunk, boolean forceUpdate, int ebsMask) { + ExtendedBlockStorage[] ebs = chunk.getBlockStorageArray(); + S21PacketChunkData.Extracted extracted = new S21PacketChunkData.Extracted(); + + if (buffer.length < ChunkDataRegistryImpl.maxPacketSize()) { + buffer = new byte[ChunkDataRegistryImpl.maxPacketSize()]; + } + val data = buffer; + + if (forceUpdate) { + chunk.sendUpdates = true; + } + + for (int i = 0; i < ebs.length; ++i) { + if (ebs[i] != null && (!forceUpdate || !ebs[i].isEmpty()) && (ebsMask & 1 << i) != 0) { + extracted.field_150280_b |= 1 << i; + } + } + + int length = ChunkDataRegistryImpl.writeToBuffer(chunk, extracted.field_150280_b, forceUpdate, data); + + extracted.field_150282_a = new byte[length]; + System.arraycopy(data, 0, extracted.field_150282_a, 0, length); + return extracted; + } + + /** + * @author FalsePattern + * @reason Replace functionality + */ + @Overwrite + public void writePacketData(PacketBuffer data) { + if (deflatedData == null) { + deflateGate.acquireUninterruptibly(); + try { + if (deflatedData == null) { + deflate(); + } + } finally { + deflateGate.release(); + } + } + data.writeInt(xPosition); + data.writeInt(zPosition); + data.writeBoolean(forceUpdate); + data.writeShort((short)(ebsMask & 0xFFFF)); + data.writeInt(this.data.length); + data.writeInt(deflatedSize); + data.writeBytes(deflatedData, 0, deflatedSize); + } + + /** + * @author FalsePattern + * @reason Replace functionality + */ + @Overwrite + public void readPacketData(PacketBuffer data) throws IOException { + xPosition = data.readInt(); + zPosition = data.readInt(); + forceUpdate = data.readBoolean(); + ebsMask = data.readShort() & 0xFFFF; + this.data = new byte[data.readInt()]; + deflatedSize = data.readInt(); + if (buffer.length < deflatedSize) { + buffer = new byte[deflatedSize]; + } + data.readBytes(buffer, 0, deflatedSize); + val inflater = new Inflater(); + inflater.setInput(buffer, 0, deflatedSize); + try { + inflater.inflate(this.data); + } catch (DataFormatException dfe) { + throw new IOException("Bad compressed data format"); + } finally { + inflater.end(); + } + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S26PacketMapChunkBulkMixin.java b/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S26PacketMapChunkBulkMixin.java new file mode 100644 index 0000000..aa45b97 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/mixin/mixins/common/vanilla/S26PacketMapChunkBulkMixin.java @@ -0,0 +1,110 @@ +package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla; + +import lombok.val; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.S21PacketChunkData; +import net.minecraft.network.play.server.S26PacketMapChunkBulk; + +import java.io.IOException; +import java.util.concurrent.Semaphore; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +@Mixin(S26PacketMapChunkBulk.class) +public abstract class S26PacketMapChunkBulkMixin { + @Shadow(aliases = "field_149263_e") private byte[] deflatedData; + + @Shadow private Semaphore deflateGate; + + @Shadow protected abstract void deflate(); + + @Shadow(aliases = "field_149266_a") private int[] xPositions; + @Shadow(aliases = "field_149264_b") private int[] zPositions; + @Shadow(aliases = "field_149265_c") private int[] ebsMasks; + @Shadow(aliases = "field_149261_g") private int deflatedSize; + @Shadow(aliases = "field_149267_h") private boolean skylight; + @Shadow(aliases = "field_149260_f") private byte[][] datas; + @Shadow(aliases = "field_149268_i") private static byte[] inflaterBuffer; + + /** + * @author FalsePattern + * @reason Replace functionality + */ + @Overwrite + public void readPacketData(PacketBuffer data) throws IOException { + short chunkCount = data.readShort(); + val sizes = new int[chunkCount]; + for (int i = 0; i < chunkCount; i++) { + sizes[i] = data.readInt(); + } + deflatedSize = data.readInt(); + skylight = data.readBoolean(); + xPositions = new int[chunkCount]; + zPositions = new int[chunkCount]; + ebsMasks = new int[chunkCount]; + datas = new byte[chunkCount][]; + + if (inflaterBuffer.length < deflatedSize) { + inflaterBuffer = new byte[deflatedSize]; + } + + data.readBytes(inflaterBuffer, 0, deflatedSize); + byte[] buf = new byte[S21PacketChunkData.func_149275_c() * chunkCount]; + Inflater inflater = new Inflater(); + inflater.setInput(inflaterBuffer, 0, deflatedSize); + + try { + inflater.inflate(buf); + } catch (DataFormatException dataformatexception) { + throw new IOException("Bad compressed data format"); + } finally { + inflater.end(); + } + + int pos = 0; + + for (int i = 0; i < chunkCount; ++i) { + val size = sizes[i]; + xPositions[i] = data.readInt(); + zPositions[i] = data.readInt(); + ebsMasks[i] = data.readUnsignedShort(); + + datas[i] = new byte[size]; + System.arraycopy(buf, pos, datas[i], 0, size); + pos += size; + } + } + + /** + * @author FalsePattern + * @reason Replace functionality + */ + @Overwrite + public void writePacketData(PacketBuffer data) throws IOException { + if (deflatedData == null) { + deflateGate.acquireUninterruptibly(); + if (deflatedData == null) { + deflate(); + } + deflateGate.release(); + } + + data.writeShort(xPositions.length); + for (byte[] bytes: datas) { + data.writeInt(bytes.length); + } + data.writeInt(deflatedSize); + data.writeBoolean(skylight); + data.writeBytes(deflatedData, 0, deflatedSize); + + for (int i = 0; i < xPositions.length; ++i) { + data.writeInt(xPositions[i]); + data.writeInt(zPositions[i]); + data.writeShort((short)(ebsMasks[i] & 65535)); + } + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/mixin/plugin/Mixin.java b/src/main/java/com/falsepattern/chunk/internal/mixin/plugin/Mixin.java index 4e38b67..a64bf7f 100644 --- a/src/main/java/com/falsepattern/chunk/internal/mixin/plugin/Mixin.java +++ b/src/main/java/com/falsepattern/chunk/internal/mixin/plugin/Mixin.java @@ -31,9 +31,15 @@ import lombok.RequiredArgsConstructor; import java.util.List; import java.util.function.Predicate; +import static com.falsepattern.lib.mixin.IMixin.PredicateHelpers.always; + @RequiredArgsConstructor public enum Mixin implements IMixin { // @formatter:off + S21PacketChunkDataMixin(Side.COMMON, always(), "vanilla.S21PacketChunkDataMixin"), + S26PacketMapChunkBulkMixin(Side.COMMON, always(), "vanilla.S26PacketMapChunkBulkMixin"), + + ChunkMixin(Side.CLIENT, always(), "vanilla.ChunkMixin"), ; // @formatter:on diff --git a/src/main/java/com/falsepattern/chunk/internal/stubpackage/net/minecraft/world/chunk/Chunk.java b/src/main/java/com/falsepattern/chunk/internal/stubpackage/net/minecraft/world/chunk/Chunk.java deleted file mode 100644 index 45b5d5d..0000000 --- a/src/main/java/com/falsepattern/chunk/internal/stubpackage/net/minecraft/world/chunk/Chunk.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.falsepattern.chunk.internal.stubpackage.net.minecraft.world.chunk; - -import com.falsepattern.chunk.api.ModdedChunk; - -import net.minecraft.world.World; - -//Internal stub class, do not use -public abstract class Chunk extends net.minecraft.world.chunk.Chunk implements ModdedChunk { - private Chunk(World p_i1995_1_, int p_i1995_2_, int p_i1995_3_) { - super(p_i1995_1_, p_i1995_2_, p_i1995_3_); - } -} diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java new file mode 100644 index 0000000..87b69e3 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java @@ -0,0 +1,46 @@ +package com.falsepattern.chunk.internal.vanilla; + +import org.jetbrains.annotations.NotNull; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.chunk.Chunk; + +import java.nio.ByteBuffer; + +public class BiomeManager extends VanillaManager { + public static final int BYTES_PER_CHUNK = 256; + + @Override + public String id() { + return "biome"; + } + + @Override + public int maxPacketSize() { + return BYTES_PER_CHUNK; + } + + @Override + public void writeToBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer data) { + if (forceUpdate) { + data.put(chunk.getBiomeArray()); + } + } + + @Override + public void readFromBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer buffer) { + if (forceUpdate) { + buffer.get(chunk.getBiomeArray()); + } + } + + @Override + public void writeToNBT(Chunk chunk, @NotNull NBTTagCompound tag) { + + } + + @Override + public void readFromNBT(Chunk chunk, @NotNull NBTTagCompound tag) { + + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java new file mode 100644 index 0000000..7826919 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java @@ -0,0 +1,83 @@ +package com.falsepattern.chunk.internal.vanilla; + +import lombok.val; +import org.jetbrains.annotations.NotNull; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.chunk.Chunk; + +import java.nio.ByteBuffer; + +import static com.falsepattern.chunk.internal.Common.BLOCKS_PER_EBS; +import static com.falsepattern.chunk.internal.Common.EBS_PER_CHUNK; + +public class BlockIDManager extends VanillaManager { + private static final int LSB_BYTES_PER_EBS = BLOCKS_PER_EBS; + private static final int MSB_BYTES_PER_EBS = BLOCKS_PER_EBS / 2; + private static final int HEADER_SIZE = 2; + + @Override + public String id() { + return "blockid"; + } + + @Override + public int maxPacketSize() { + return HEADER_SIZE + EBS_PER_CHUNK * (LSB_BYTES_PER_EBS + MSB_BYTES_PER_EBS); + } + + @Override + public void writeToBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer data) { + val ebsArray = chunk.getBlockStorageArray(); + int currentPos = data.position(); + data.putShort((short) 0); + int msbMask = 0; + for (int i = 0; i < ebsArray.length; i++) { + if ((ebsMask & (1 << i)) != 0) { + val ebs = ebsArray[i]; + val lsb = ebs.getBlockLSBArray(); + val msb = ebs.getBlockMSBArray(); + data.put(lsb); + if (msb != null) { + msbMask |= 1 << i; + data.put(msb.data); + } + } + } + int endPos = data.position(); + data.position(currentPos); + data.putShort((short) msbMask); + data.position(endPos); + } + + @Override + public void readFromBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer buffer) { + val ebsArray = chunk.getBlockStorageArray(); + val msbMask = buffer.getShort() & 0xFFFF; + for (int i = 0; i < ebsArray.length; i++) { + val ebs = ebsArray[i]; + if ((ebsMask & (1 << i)) != 0 && ebs != null) { + buffer.get(ebs.getBlockLSBArray()); + if ((msbMask & (1 << i)) != 0) { + val msb = ebs.getBlockMSBArray(); + if (msb == null) { + ebs.createBlockMSBArray(); + } + buffer.get(ebs.getBlockMSBArray().data); + } else { + ebs.setBlockMSBArray(null); + } + } + } + } + + @Override + public void writeToNBT(Chunk chunk, @NotNull NBTTagCompound tag) { + + } + + @Override + public void readFromNBT(Chunk chunk, @NotNull NBTTagCompound tag) { + + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java new file mode 100644 index 0000000..431dea7 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java @@ -0,0 +1,16 @@ +package com.falsepattern.chunk.internal.vanilla; + +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +public class BlocklightManager extends NibbleManager { + @Override + protected NibbleArray getNibbleArray(ExtendedBlockStorage ebs) { + return ebs.getBlocklightArray(); + } + + @Override + public String id() { + return "blocklight"; + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java new file mode 100644 index 0000000..ef91296 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java @@ -0,0 +1,16 @@ +package com.falsepattern.chunk.internal.vanilla; + +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +public class MetadataManager extends NibbleManager { + @Override + protected NibbleArray getNibbleArray(ExtendedBlockStorage ebs) { + return ebs.getMetadataArray(); + } + + @Override + public String id() { + return "metadata"; + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java new file mode 100644 index 0000000..a45975c --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java @@ -0,0 +1,59 @@ +package com.falsepattern.chunk.internal.vanilla; + +import com.falsepattern.chunk.internal.Common; +import lombok.val; +import org.jetbrains.annotations.NotNull; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import java.nio.ByteBuffer; + +public abstract class NibbleManager extends VanillaManager { + public static final int BYTES_PER_EBS = Common.BLOCKS_PER_EBS / 2; + + protected abstract NibbleArray getNibbleArray(ExtendedBlockStorage ebs); + + @Override + public int maxPacketSize() { + return Common.EBS_PER_CHUNK * BYTES_PER_EBS; + } + + @Override + public void writeToBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer data) { + val ebsArray = chunk.getBlockStorageArray(); + for (int i = 0; i < ebsArray.length; i++) { + if ((ebsMask & (1 << i)) != 0) { + val ebs = ebsArray[i]; + if (ebs != null) { + data.put(getNibbleArray(ebs).data, 0, BYTES_PER_EBS); + } + } + } + } + + @Override + public void readFromBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer buffer) { + val ebsArray = chunk.getBlockStorageArray(); + for (int i = 0; i < ebsArray.length; i++) { + if ((ebsMask & (1 << i)) != 0) { + val ebs = ebsArray[i]; + if (ebs != null) { + buffer.get(getNibbleArray(ebs).data, 0, BYTES_PER_EBS); + } + } + } + } + + @Override + public void writeToNBT(Chunk chunk, @NotNull NBTTagCompound tag) { + + } + + @Override + public void readFromNBT(Chunk chunk, @NotNull NBTTagCompound tag) { + + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java new file mode 100644 index 0000000..0a5d1df --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java @@ -0,0 +1,33 @@ +package com.falsepattern.chunk.internal.vanilla; + +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.NibbleArray; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import java.nio.ByteBuffer; + +public class SkylightManager extends NibbleManager { + @Override + public String id() { + return "skylight"; + } + + @Override + protected NibbleArray getNibbleArray(ExtendedBlockStorage ebs) { + return ebs.getSkylightArray(); + } + + @Override + public void writeToBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer data) { + if (!chunk.worldObj.provider.hasNoSky) { + super.writeToBuffer(chunk, ebsMask, forceUpdate, data); + } + } + + @Override + public void readFromBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer buffer) { + if (!chunk.worldObj.provider.hasNoSky) { + super.readFromBuffer(chunk, ebsMask, forceUpdate, buffer); + } + } +} diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/VanillaManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/VanillaManager.java new file mode 100644 index 0000000..569f82d --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/VanillaManager.java @@ -0,0 +1,17 @@ +package com.falsepattern.chunk.internal.vanilla; + +import com.falsepattern.chunk.api.ChunkDataManager; + +import net.minecraft.world.chunk.Chunk; + +public abstract class VanillaManager implements ChunkDataManager { + @Override + public String domain() { + return "minecraft"; + } + + @Override + public void init(Chunk chunk) { + + } +} diff --git a/src/main/resources/mixins.fplib_chunkapi.json b/src/main/resources/mixins.fp_chunkapi.json similarity index 83% rename from src/main/resources/mixins.fplib_chunkapi.json rename to src/main/resources/mixins.fp_chunkapi.json index f139ea1..c91596d 100644 --- a/src/main/resources/mixins.fplib_chunkapi.json +++ b/src/main/resources/mixins.fp_chunkapi.json @@ -3,7 +3,7 @@ "minVersion": "0.8.5", "package": "com.falsepattern.chunk.internal.mixin.mixins", "plugin": "com.falsepattern.chunk.internal.mixin.plugin.MixinPlugin", - "refmap": "mixins.fplib_chunkapi.refmap.json", + "refmap": "mixins.fp_chunkapi.refmap.json", "target": "@env(DEFAULT)", "compatibilityLevel": "JAVA_8" }