From 159e298e8b1235811eb9255f56d1bbce7a71c06c Mon Sep 17 00:00:00 2001 From: Zelophed <zefren1@googlemail.com> 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<BlockInfo, MovementContext> actor : getActors()) { @@ -789,6 +746,100 @@ public abstract class Contraption { return nbt; } + private CompoundNBT writeBlocksCompound() { + CompoundNBT compound = new CompoundNBT(); + PaletteHashMap<BlockState> 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<BlockState> 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<BlockState> 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<BlockState> 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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 MNkvXXt^-0~f*)C?UjP6A delta 5465 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 P00000NkvXXu0mjfTwE@e