From 159e298e8b1235811eb9255f56d1bbce7a71c06c Mon Sep 17 00:00:00 2001 From: Zelophed Date: Wed, 17 Feb 2021 02:34:55 +0100 Subject: [PATCH] paletted contraptions - use minecraft's palette-map during serialization and deserialization to reduce compound size in most cases - also update the placement_indicator sprite sheet --- .../structureMovement/Contraption.java | 159 ++++++++++++------ .../create/foundation/gui/AllGuiTextures.java | 2 +- .../resources/META-INF/accesstransformer.cfg | 5 +- .../textures/gui/placement_indicator.png | Bin 9385 -> 9488 bytes 4 files changed, 110 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java index 6fc93a511..45638b9a3 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java @@ -28,11 +28,7 @@ import com.simibubi.create.content.logistics.block.inventories.AdjustableCrateBl import com.simibubi.create.content.logistics.block.redstone.RedstoneContactBlock; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.fluid.CombinedTankWrapper; -import com.simibubi.create.foundation.utility.BlockFace; -import com.simibubi.create.foundation.utility.Iterate; -import com.simibubi.create.foundation.utility.NBTHelper; -import com.simibubi.create.foundation.utility.NBTProcessors; -import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.foundation.utility.*; import com.simibubi.create.foundation.utility.worldWrappers.WrappedWorld; import net.minecraft.block.*; import net.minecraft.block.material.PushReaction; @@ -41,6 +37,7 @@ import net.minecraft.fluid.Fluids; import net.minecraft.fluid.IFluidState; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.INBT; import net.minecraft.nbt.ListNBT; import net.minecraft.nbt.NBTUtil; import net.minecraft.state.properties.BlockStateProperties; @@ -55,6 +52,7 @@ import net.minecraft.util.Rotation; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.palette.PaletteHashMap; import net.minecraft.world.IWorld; import net.minecraft.world.World; import net.minecraft.world.gen.feature.template.Template.BlockInfo; @@ -67,6 +65,7 @@ import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; import net.minecraftforge.fluids.capability.templates.FluidTank; import net.minecraftforge.items.IItemHandlerModifiable; import net.minecraftforge.items.wrapper.CombinedInvWrapper; +import net.minecraftforge.registries.GameData; import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -595,51 +594,16 @@ public abstract class Contraption { presentTileEntities.clear(); renderedTileEntities.clear(); - nbt.getList("Blocks", 10) - .forEach(c -> { - CompoundNBT comp = (CompoundNBT) c; - BlockInfo info = new BlockInfo(NBTUtil.readBlockPos(comp.getCompound("Pos")), - NBTUtil.readBlockState(comp.getCompound("Block")), - comp.contains("Data") ? comp.getCompound("Data") : null); - blocks.put(info.pos, info); - - if (world.isRemote) { - Block block = info.state.getBlock(); - CompoundNBT tag = info.nbt; - MovementBehaviour movementBehaviour = AllMovementBehaviours.of(block); - if (tag == null || (movementBehaviour != null && movementBehaviour.hasSpecialMovementRenderer())) - return; - - tag.putInt("x", info.pos.getX()); - tag.putInt("y", info.pos.getY()); - tag.putInt("z", info.pos.getZ()); - - TileEntity te = TileEntity.create(tag); - if (te == null) - return; - te.setLocation(new WrappedWorld(world) { - - @Override - public BlockState getBlockState(BlockPos pos) { - if (!pos.equals(te.getPos())) - return Blocks.AIR.getDefaultState(); - return info.state; - } - - }, te.getPos()); - if (te instanceof KineticTileEntity) - ((KineticTileEntity) te).setSpeed(0); - te.getBlockState(); - presentTileEntities.put(info.pos, te); - renderedTileEntities.add(te); - } - }); + INBT blocks = nbt.get("Blocks"); + //used to differentiate between the 'old' and the paletted serialization + boolean usePalettedDeserialization = blocks != null && blocks.getId() == 10 && ((CompoundNBT) blocks).contains("Palette"); + readBlocksCompound(blocks, world, usePalettedDeserialization); actors.clear(); nbt.getList("Actors", 10) .forEach(c -> { CompoundNBT comp = (CompoundNBT) c; - BlockInfo info = blocks.get(NBTUtil.readBlockPos(comp.getCompound("Pos"))); + BlockInfo info = this.blocks.get(NBTUtil.readBlockPos(comp.getCompound("Pos"))); MovementContext context = MovementContext.readNBT(world, info, comp, this); getActors().add(MutablePair.of(info, context)); }); @@ -704,15 +668,8 @@ public abstract class Contraption { public CompoundNBT writeNBT(boolean spawnPacket) { CompoundNBT nbt = new CompoundNBT(); nbt.putString("Type", getType().id); - ListNBT blocksNBT = new ListNBT(); - for (BlockInfo block : this.blocks.values()) { - CompoundNBT c = new CompoundNBT(); - c.put("Block", NBTUtil.writeBlockState(block.state)); - c.put("Pos", NBTUtil.writeBlockPos(block.pos)); - if (block.nbt != null) - c.put("Data", block.nbt); - blocksNBT.add(c); - } + + CompoundNBT blocksNBT = writeBlocksCompound(); ListNBT actorsNBT = new ListNBT(); for (MutablePair actor : getActors()) { @@ -789,6 +746,100 @@ public abstract class Contraption { return nbt; } + private CompoundNBT writeBlocksCompound() { + CompoundNBT compound = new CompoundNBT(); + PaletteHashMap palette = new PaletteHashMap<>(GameData.getBlockStateIDMap(), 16, (i, s) -> {throw new IllegalStateException("Palette Map index exceeded maximum");}, NBTUtil::readBlockState, NBTUtil::writeBlockState); + ListNBT blockList = new ListNBT(); + + for (BlockInfo block : this.blocks.values()) { + int id = palette.idFor(block.state); + CompoundNBT c = new CompoundNBT(); + c.putLong("Pos", block.pos.toLong()); + c.putInt("State", id); + if (block.nbt != null) + c.put("Data", block.nbt); + blockList.add(c); + } + + ListNBT paletteNBT = new ListNBT(); + palette.writePaletteToList(paletteNBT); + compound.put("Palette", paletteNBT); + compound.put("BlockList", blockList); + + return compound; + } + + private void readBlocksCompound(INBT compound, World world, boolean usePalettedDeserialization) { + PaletteHashMap palette = null; + ListNBT blockList; + if (usePalettedDeserialization) { + CompoundNBT c = ((CompoundNBT) compound); + palette = new PaletteHashMap<>(GameData.getBlockStateIDMap(), 16, (i, s) -> {throw new IllegalStateException("Palette Map index exceeded maximum");}, NBTUtil::readBlockState, NBTUtil::writeBlockState); + palette.read(c.getList("Palette", 10)); + + blockList = c.getList("BlockList", 10); + } else { + blockList = (ListNBT) compound; + } + + PaletteHashMap finalPalette = palette; + blockList.forEach(e -> { + CompoundNBT c = (CompoundNBT) e; + + BlockInfo info = usePalettedDeserialization ? readBlockInfo(c, finalPalette) : legacyReadBlockInfo(c); + + this.blocks.put(info.pos, info); + + if (world.isRemote) { + Block block = info.state.getBlock(); + CompoundNBT tag = info.nbt; + MovementBehaviour movementBehaviour = AllMovementBehaviours.of(block); + if (tag == null || (movementBehaviour != null && movementBehaviour.hasSpecialMovementRenderer())) + return; + + tag.putInt("x", info.pos.getX()); + tag.putInt("y", info.pos.getY()); + tag.putInt("z", info.pos.getZ()); + + TileEntity te = TileEntity.create(tag); + if (te == null) + return; + te.setLocation(new WrappedWorld(world) { + + @Override + public BlockState getBlockState(BlockPos pos) { + if (!pos.equals(te.getPos())) + return Blocks.AIR.getDefaultState(); + return info.state; + } + + }, te.getPos()); + if (te instanceof KineticTileEntity) + ((KineticTileEntity) te).setSpeed(0); + te.getBlockState(); + presentTileEntities.put(info.pos, te); + renderedTileEntities.add(te); + } + + }); + } + + private static BlockInfo readBlockInfo(CompoundNBT blockListEntry, PaletteHashMap palette) { + return new BlockInfo( + BlockPos.fromLong(blockListEntry.getLong("Pos")), + Objects.requireNonNull(palette.get(blockListEntry.getInt("State"))), + blockListEntry.contains("Data") ? blockListEntry.getCompound("Data") : null + ); + } + + private static BlockInfo legacyReadBlockInfo(CompoundNBT blockListEntry) { + return new BlockInfo( + NBTUtil.readBlockPos(blockListEntry.getCompound("Pos")), + NBTUtil.readBlockState(blockListEntry.getCompound("Block")), + blockListEntry.contains("Data") ? blockListEntry.getCompound("Data") : null + ); + } + public void removeBlocksFromWorld(World world, BlockPos offset) { storage.values() .forEach(MountedStorage::removeStorageFromWorld); diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java index 520cde09e..c66f911bb 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java @@ -79,7 +79,7 @@ public enum AllGuiTextures { INDICATOR_RED("widgets.png", 72, 18, 18, 6), // PlacementIndicator - PLACEMENT_INDICATOR_SHEET("placement_indicator.png", 0, 0, 256, 256); + PLACEMENT_INDICATOR_SHEET("placement_indicator.png", 0, 0, 16, 256); ; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 22c960e77..254548d6f 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -26,4 +26,7 @@ public net.minecraft.world.server.ServerTickList field_205374_d # pendingTickLis public net.minecraft.world.server.ServerTickList field_205375_e # pendingTickListEntriesTreeSet # GameRenderer -public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D #getFOVModifier \ No newline at end of file +public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D #getFOVModifier + +# IResizeCallback +public net.minecraft.util.palette.IResizeCallback \ No newline at end of file diff --git a/src/main/resources/assets/create/textures/gui/placement_indicator.png b/src/main/resources/assets/create/textures/gui/placement_indicator.png index cb53a47fcb3c027831ef62e2a077a255fb5ddeaf..d433d9b2d532806e2c64c137b9dc8dd6ac94cb99 100644 GIT binary patch delta 5670 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 P00000NkvXXu0mjfTwE@e