vault shuffle

- Multiblocks (i.e. vaults and tanks) will now try to remain in the same grouping after being assembled and disassembled by a contraption
This commit is contained in:
zelophed 2024-08-06 20:07:40 +02:00
parent 2e91e16932
commit 1232a08fde
4 changed files with 143 additions and 33 deletions

View file

@ -24,6 +24,8 @@ import javax.annotation.Nullable;
import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.simibubi.create.AllBlockEntityTypes; import com.simibubi.create.AllBlockEntityTypes;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllInteractionBehaviours; import com.simibubi.create.AllInteractionBehaviours;
@ -56,7 +58,6 @@ import com.simibubi.create.content.contraptions.pulley.PulleyBlockEntity;
import com.simibubi.create.content.contraptions.render.ContraptionLighter; import com.simibubi.create.content.contraptions.render.ContraptionLighter;
import com.simibubi.create.content.contraptions.render.EmptyLighter; import com.simibubi.create.content.contraptions.render.EmptyLighter;
import com.simibubi.create.content.decoration.slidingDoor.SlidingDoorBlock; import com.simibubi.create.content.decoration.slidingDoor.SlidingDoorBlock;
import com.simibubi.create.content.fluids.tank.FluidTankBlockEntity;
import com.simibubi.create.content.kinetics.base.BlockBreakingMovementBehaviour; import com.simibubi.create.content.kinetics.base.BlockBreakingMovementBehaviour;
import com.simibubi.create.content.kinetics.base.IRotate; import com.simibubi.create.content.kinetics.base.IRotate;
import com.simibubi.create.content.kinetics.base.KineticBlockEntity; import com.simibubi.create.content.kinetics.base.KineticBlockEntity;
@ -65,7 +66,6 @@ import com.simibubi.create.content.kinetics.gantry.GantryShaftBlock;
import com.simibubi.create.content.kinetics.simpleRelays.ShaftBlock; import com.simibubi.create.content.kinetics.simpleRelays.ShaftBlock;
import com.simibubi.create.content.kinetics.steamEngine.PoweredShaftBlockEntity; import com.simibubi.create.content.kinetics.steamEngine.PoweredShaftBlockEntity;
import com.simibubi.create.content.logistics.crate.CreativeCrateBlockEntity; import com.simibubi.create.content.logistics.crate.CreativeCrateBlockEntity;
import com.simibubi.create.content.logistics.vault.ItemVaultBlockEntity;
import com.simibubi.create.content.redstone.contact.RedstoneContactBlock; import com.simibubi.create.content.redstone.contact.RedstoneContactBlock;
import com.simibubi.create.content.trains.bogey.AbstractBogeyBlock; import com.simibubi.create.content.trains.bogey.AbstractBogeyBlock;
import com.simibubi.create.foundation.blockEntity.IMultiBlockEntityContainer; import com.simibubi.create.foundation.blockEntity.IMultiBlockEntityContainer;
@ -149,6 +149,7 @@ public abstract class Contraption {
protected Map<UUID, Integer> seatMapping; protected Map<UUID, Integer> seatMapping;
protected Map<UUID, BlockFace> stabilizedSubContraptions; protected Map<UUID, BlockFace> stabilizedSubContraptions;
protected MountedStorageManager storage; protected MountedStorageManager storage;
protected Multimap<BlockPos, StructureBlockInfo> capturedMultiblocks;
private Set<SuperGlueEntity> glueToRemove; private Set<SuperGlueEntity> glueToRemove;
private Map<BlockPos, Entity> initialPassengers; private Map<BlockPos, Entity> initialPassengers;
@ -183,6 +184,7 @@ public abstract class Contraption {
stabilizedSubContraptions = new HashMap<>(); stabilizedSubContraptions = new HashMap<>();
simplifiedEntityColliders = Optional.empty(); simplifiedEntityColliders = Optional.empty();
storage = new MountedStorageManager(); storage = new MountedStorageManager();
capturedMultiblocks = ArrayListMultimap.create();
} }
public ContraptionWorld getContraptionWorld() { public ContraptionWorld getContraptionWorld() {
@ -642,6 +644,8 @@ public abstract class Contraption {
BlockEntity be = pair.getValue(); BlockEntity be = pair.getValue();
storage.addBlock(localPos, be); storage.addBlock(localPos, be);
captureMultiblock(localPos, structureBlockInfo, be);
if (AllMovementBehaviours.getBehaviour(captured.state) != null) if (AllMovementBehaviours.getBehaviour(captured.state) != null)
actors.add(MutablePair.of(structureBlockInfo, null)); actors.add(MutablePair.of(structureBlockInfo, null));
@ -656,6 +660,25 @@ public abstract class Contraption {
hasUniversalCreativeCrate = true; hasUniversalCreativeCrate = true;
} }
protected void captureMultiblock(BlockPos localPos, StructureBlockInfo structureBlockInfo, BlockEntity be) {
if (!(be instanceof IMultiBlockEntityContainer multiBlockBE))
return;
CompoundTag nbt = structureBlockInfo.nbt;
BlockPos controllerPos = nbt.contains("Controller") ?
toLocalPos(NbtUtils.readBlockPos(nbt.getCompound("Controller"))) :
localPos;
nbt.put("Controller", NbtUtils.writeBlockPos(controllerPos));
if (multiBlockBE.isController() && multiBlockBE.getHeight() <= 1 && multiBlockBE.getWidth() <= 1) {
nbt.put("LastKnownPos", NbtUtils.writeBlockPos(BlockPos.ZERO.below(Integer.MAX_VALUE - 1)));
return;
}
nbt.remove("LastKnownPos");
capturedMultiblocks.put(controllerPos, structureBlockInfo);
}
@Nullable @Nullable
protected CompoundTag getBlockEntityNBT(Level world, BlockPos pos) { protected CompoundTag getBlockEntityNBT(Level world, BlockPos pos) {
BlockEntity blockEntity = world.getBlockEntity(pos); BlockEntity blockEntity = world.getBlockEntity(pos);
@ -666,11 +689,6 @@ public abstract class Contraption {
nbt.remove("y"); nbt.remove("y");
nbt.remove("z"); nbt.remove("z");
if ((blockEntity instanceof FluidTankBlockEntity || blockEntity instanceof ItemVaultBlockEntity)
&& nbt.contains("Controller"))
nbt.put("Controller",
NbtUtils.writeBlockPos(toLocalPos(NbtUtils.readBlockPos(nbt.getCompound("Controller")))));
return nbt; return nbt;
} }
@ -694,11 +712,25 @@ public abstract class Contraption {
Tag blocks = nbt.get("Blocks"); Tag blocks = nbt.get("Blocks");
// used to differentiate between the 'old' and the paletted serialization // used to differentiate between the 'old' and the paletted serialization
boolean usePalettedDeserialization = boolean usePalettedDeserialization =
blocks != null && blocks.getId() == 10 && ((CompoundTag) blocks).contains("Palette"); blocks != null && blocks.getId() == Tag.TAG_COMPOUND && ((CompoundTag) blocks).contains("Palette");
readBlocksCompound(blocks, world, usePalettedDeserialization); readBlocksCompound(blocks, world, usePalettedDeserialization);
capturedMultiblocks.clear();
nbt.getList("CapturedMultiblocks", Tag.TAG_COMPOUND).forEach(c -> {
CompoundTag tag = (CompoundTag) c;
if (!tag.contains("Controller", Tag.TAG_COMPOUND) && !tag.contains("Parts", Tag.TAG_LIST))
return;
BlockPos controllerPos = NbtUtils.readBlockPos(tag.getCompound("Controller"));
tag.getList("Parts", Tag.TAG_COMPOUND).forEach(part -> {
BlockPos partPos = NbtUtils.readBlockPos((CompoundTag) part);
StructureBlockInfo partInfo = this.blocks.get(partPos);
capturedMultiblocks.put(controllerPos, partInfo);
});
});
actors.clear(); actors.clear();
nbt.getList("Actors", 10) nbt.getList("Actors", Tag.TAG_COMPOUND)
.forEach(c -> { .forEach(c -> {
CompoundTag comp = (CompoundTag) c; CompoundTag comp = (CompoundTag) c;
StructureBlockInfo info = this.blocks.get(NbtUtils.readBlockPos(comp.getCompound("Pos"))); StructureBlockInfo info = this.blocks.get(NbtUtils.readBlockPos(comp.getCompound("Pos")));
@ -741,7 +773,7 @@ public abstract class Contraption {
storage.read(nbt, presentBlockEntities, spawnData); storage.read(nbt, presentBlockEntities, spawnData);
if (nbt.contains("BoundsFront")) if (nbt.contains("BoundsFront"))
bounds = NBTHelper.readAABB(nbt.getList("BoundsFront", 5)); bounds = NBTHelper.readAABB(nbt.getList("BoundsFront", Tag.TAG_FLOAT));
stalled = nbt.getBoolean("Stalled"); stalled = nbt.getBoolean("Stalled");
hasUniversalCreativeCrate = nbt.getBoolean("BottomlessSupply"); hasUniversalCreativeCrate = nbt.getBoolean("BottomlessSupply");
@ -754,6 +786,19 @@ public abstract class Contraption {
CompoundTag blocksNBT = writeBlocksCompound(); CompoundTag blocksNBT = writeBlocksCompound();
ListTag multiblocksNBT = new ListTag();
capturedMultiblocks.keySet().forEach(controllerPos -> {
CompoundTag tag = new CompoundTag();
tag.put("Controller", NbtUtils.writeBlockPos(controllerPos));
Collection<StructureBlockInfo> multiblockParts = capturedMultiblocks.get(controllerPos);
ListTag partsNBT = new ListTag();
multiblockParts.forEach(info -> partsNBT.add(NbtUtils.writeBlockPos(info.pos)));
tag.put("Parts", partsNBT);
multiblocksNBT.add(tag);
});
ListTag actorsNBT = new ListTag(); ListTag actorsNBT = new ListTag();
for (MutablePair<StructureBlockInfo, MovementContext> actor : getActors()) { for (MutablePair<StructureBlockInfo, MovementContext> actor : getActors()) {
MovementBehaviour behaviour = AllMovementBehaviours.getBehaviour(actor.left.state); MovementBehaviour behaviour = AllMovementBehaviours.getBehaviour(actor.left.state);
@ -804,6 +849,7 @@ public abstract class Contraption {
nbt.put("Blocks", blocksNBT); nbt.put("Blocks", blocksNBT);
nbt.put("Actors", actorsNBT); nbt.put("Actors", actorsNBT);
nbt.put("CapturedMultiblocks", multiblocksNBT);
nbt.put("DisabledActors", disabledActorsNBT); nbt.put("DisabledActors", disabledActorsNBT);
nbt.put("Interactors", interactorNBT); nbt.put("Interactors", interactorNBT);
nbt.put("Superglue", superglueNBT); nbt.put("Superglue", superglueNBT);
@ -859,12 +905,12 @@ public abstract class Contraption {
throw new IllegalStateException("Palette Map index exceeded maximum"); throw new IllegalStateException("Palette Map index exceeded maximum");
}); });
ListTag list = c.getList("Palette", 10); ListTag list = c.getList("Palette", Tag.TAG_COMPOUND);
palette.values.clear(); palette.values.clear();
for (int i = 0; i < list.size(); ++i) for (int i = 0; i < list.size(); ++i)
palette.values.add(NbtUtils.readBlockState(list.getCompound(i))); palette.values.add(NbtUtils.readBlockState(list.getCompound(i)));
blockList = c.getList("BlockList", 10); blockList = c.getList("BlockList", Tag.TAG_COMPOUND);
} else { } else {
blockList = (ListTag) compound; blockList = (ListTag) compound;
} }
@ -1022,6 +1068,8 @@ public abstract class Contraption {
return; return;
disassembled = true; disassembled = true;
translateMultiblockControllers(transform);
for (boolean nonBrittles : Iterate.trueAndFalse) { for (boolean nonBrittles : Iterate.trueAndFalse) {
for (StructureBlockInfo block : blocks.values()) { for (StructureBlockInfo block : blocks.values()) {
if (nonBrittles == BlockMovementChecks.isBrittle(block.state)) if (nonBrittles == BlockMovementChecks.isBrittle(block.state))
@ -1088,8 +1136,11 @@ public abstract class Contraption {
tag.remove("InitialOffset"); tag.remove("InitialOffset");
} }
if (blockEntity instanceof IMultiBlockEntityContainer && tag.contains("LastKnownPos")) if (blockEntity instanceof IMultiBlockEntityContainer) {
tag.put("LastKnownPos", NbtUtils.writeBlockPos(BlockPos.ZERO.below(Integer.MAX_VALUE - 1))); if (tag.contains("LastKnownPos") || capturedMultiblocks.isEmpty()) {
tag.put("LastKnownPos", NbtUtils.writeBlockPos(BlockPos.ZERO.below(Integer.MAX_VALUE - 1)));
}
}
blockEntity.load(tag); blockEntity.load(tag);
storage.addStorageToWorld(block, blockEntity); storage.addStorageToWorld(block, blockEntity);
@ -1117,6 +1168,38 @@ public abstract class Contraption {
storage.clear(); storage.clear();
} }
protected void translateMultiblockControllers(StructureTransform transform) {
if (transform.rotationAxis != null && transform.rotationAxis != Axis.Y && transform.rotation != Rotation.NONE) {
capturedMultiblocks.values().forEach(info -> {
info.nbt.put("LastKnownPos", NbtUtils.writeBlockPos(BlockPos.ZERO.below(Integer.MAX_VALUE - 1)));
});
return;
}
capturedMultiblocks.keySet().forEach(controllerPos -> {
Collection<StructureBlockInfo> multiblockParts = capturedMultiblocks.get(controllerPos);
Optional<BoundingBox> optionalBoundingBox = BoundingBox.encapsulatingPositions(multiblockParts.stream().map(info -> transform.apply(info.pos)).toList());
if (optionalBoundingBox.isEmpty())
return;
BoundingBox boundingBox = optionalBoundingBox.get();
BlockPos newControllerPos = new BlockPos(boundingBox.minX(), boundingBox.minY(), boundingBox.minZ());
BlockPos newLocalPos = toLocalPos(newControllerPos);
BlockPos otherPos = transform.unapply(newControllerPos);
multiblockParts.forEach(info -> info.nbt.put("Controller", NbtUtils.writeBlockPos(newControllerPos)));
if (controllerPos.equals(newLocalPos))
return;
// swap nbt data to the new controller position
StructureBlockInfo prevControllerInfo = blocks.get(controllerPos);
StructureBlockInfo newControllerInfo = blocks.get(otherPos);
blocks.put(otherPos, new StructureBlockInfo(newControllerInfo.pos, newControllerInfo.state, prevControllerInfo.nbt));
blocks.put(controllerPos, new StructureBlockInfo(prevControllerInfo.pos, prevControllerInfo.state, newControllerInfo.nbt));
});
}
public void addPassengersToWorld(Level world, StructureTransform transform, List<Entity> seatedEntities) { public void addPassengersToWorld(Level world, StructureTransform transform, List<Entity> seatedEntities) {
for (Entity seatedEntity : seatedEntities) { for (Entity seatedEntity : seatedEntities) {
if (getSeatMapping().isEmpty()) if (getSeatMapping().isEmpty())
@ -1144,14 +1227,14 @@ public abstract class Contraption {
if (behaviour != null) if (behaviour != null)
behaviour.startMoving(context); behaviour.startMoving(context);
pair.setRight(context); pair.setRight(context);
if (behaviour instanceof ContraptionControlsMovement) if (behaviour instanceof ContraptionControlsMovement)
disableActorOnStart(context); disableActorOnStart(context);
} }
for (ItemStack stack : disabledActors) for (ItemStack stack : disabledActors)
setActorsActive(stack, false); setActorsActive(stack, false);
} }
protected void disableActorOnStart(MovementContext context) { protected void disableActorOnStart(MovementContext context) {
if (!ContraptionControlsMovement.isDisabledInitially(context)) if (!ContraptionControlsMovement.isDisabledInitially(context))
return; return;

View file

@ -98,6 +98,16 @@ public class StructureTransform {
return vec; return vec;
} }
public Vec3 unapplyWithoutOffset(Vec3 globalVec) {
Vec3 vec = globalVec;
if (rotationAxis != null)
vec = VecHelper.rotateCentered(vec, -angle, rotationAxis);
if (mirror != null)
vec = VecHelper.mirrorCentered(vec, mirror);
return vec;
}
public Vec3 apply(Vec3 localVec) { public Vec3 apply(Vec3 localVec) {
return applyWithoutOffset(localVec).add(Vec3.atLowerCornerOf(offset)); return applyWithoutOffset(localVec).add(Vec3.atLowerCornerOf(offset));
} }
@ -110,6 +120,14 @@ public class StructureTransform {
return applyWithoutOffset(localPos).offset(offset); return applyWithoutOffset(localPos).offset(offset);
} }
public BlockPos unapply(BlockPos globalPos) {
return unapplyWithoutOffset(globalPos.subtract(offset));
}
public BlockPos unapplyWithoutOffset(BlockPos globalPos) {
return new BlockPos(unapplyWithoutOffset(VecHelper.getCenterOf(globalPos)));
}
public void apply(BlockEntity be) { public void apply(BlockEntity be) {
if (be instanceof ITransformableBlockEntity) if (be instanceof ITransformableBlockEntity)
((ITransformableBlockEntity) be).transform(this); ((ITransformableBlockEntity) be).transform(this);

View file

@ -3,6 +3,7 @@ package com.simibubi.create.content.fluids.tank;
import static java.lang.Math.abs; import static java.lang.Math.abs;
import java.util.List; import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -48,6 +49,7 @@ public class FluidTankBlockEntity extends SmartBlockEntity implements IHaveGoggl
protected BlockPos controller; protected BlockPos controller;
protected BlockPos lastKnownPos; protected BlockPos lastKnownPos;
protected boolean updateConnectivity; protected boolean updateConnectivity;
protected boolean updateCapability;
protected boolean window; protected boolean window;
protected int luminosity; protected int luminosity;
protected int width; protected int width;
@ -68,6 +70,7 @@ public class FluidTankBlockEntity extends SmartBlockEntity implements IHaveGoggl
fluidCapability = LazyOptional.of(() -> tankInventory); fluidCapability = LazyOptional.of(() -> tankInventory);
forceFluidLevelUpdate = true; forceFluidLevelUpdate = true;
updateConnectivity = false; updateConnectivity = false;
updateCapability = false;
window = true; window = true;
height = 1; height = 1;
width = 1; width = 1;
@ -104,6 +107,10 @@ public class FluidTankBlockEntity extends SmartBlockEntity implements IHaveGoggl
return; return;
} }
if (updateCapability) {
updateCapability = false;
refreshCapability();
}
if (updateConnectivity) if (updateConnectivity)
updateConnectivity(); updateConnectivity();
if (fluidLevel != null) if (fluidLevel != null)
@ -339,7 +346,7 @@ public class FluidTankBlockEntity extends SmartBlockEntity implements IHaveGoggl
private void refreshCapability() { private void refreshCapability() {
LazyOptional<IFluidHandler> oldCap = fluidCapability; LazyOptional<IFluidHandler> oldCap = fluidCapability;
fluidCapability = LazyOptional.of(() -> handlerForCapability()); fluidCapability = LazyOptional.of(this::handlerForCapability);
oldCap.invalidate(); oldCap.invalidate();
} }
@ -415,11 +422,12 @@ public class FluidTankBlockEntity extends SmartBlockEntity implements IHaveGoggl
fluidLevel = LerpedFloat.linear() fluidLevel = LerpedFloat.linear()
.startWithValue(getFillState()); .startWithValue(getFillState());
updateCapability = true;
if (!clientPacket) if (!clientPacket)
return; return;
boolean changeOfController = boolean changeOfController = !Objects.equals(controllerBefore, controller);
controllerBefore == null ? controller != null : !controllerBefore.equals(controller);
if (changeOfController || prevSize != width || prevHeight != height) { if (changeOfController || prevSize != width || prevHeight != height) {
if (hasLevel()) if (hasLevel())
level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 16); level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 16);

View file

@ -13,24 +13,25 @@ import net.minecraftforge.fluids.IFluidTank;
public interface IMultiBlockEntityContainer { public interface IMultiBlockEntityContainer {
BlockPos getController(); BlockPos getController();
<T extends BlockEntity & IMultiBlockEntityContainer> T getControllerBE (); <T extends BlockEntity & IMultiBlockEntityContainer> T getControllerBE();
boolean isController(); boolean isController();
void setController(BlockPos pos); void setController(BlockPos pos);
void removeController (boolean keepContents); void removeController(boolean keepContents);
BlockPos getLastKnownPos(); BlockPos getLastKnownPos();
void preventConnectivityUpdate (); void preventConnectivityUpdate();
void notifyMultiUpdated (); void notifyMultiUpdated();
// only used for FluidTank windows at present. Might be useful for similar properties on other things? // only used for FluidTank windows at present. Might be useful for similar properties on other things?
default void setExtraData (@Nullable Object data) {} default void setExtraData(@Nullable Object data) {}
@Nullable @Nullable
default Object getExtraData () { return null; } default Object getExtraData() { return null; }
default Object modifyExtraData (Object data) { return data; } default Object modifyExtraData(Object data) { return data; }
// multiblock structural information // multiblock structural information
Direction.Axis getMainConnectionAxis(); Direction.Axis getMainConnectionAxis();
default Direction.Axis getMainAxisOf (BlockEntity be) { // this feels redundant, but it gives us a default to use when defining ::getMainConnectionAxis
default Direction.Axis getMainAxisOf(BlockEntity be) { // this feels redundant, but it gives us a default to use when defining ::getMainConnectionAxis
BlockState state = be.getBlockState(); BlockState state = be.getBlockState();
Direction.Axis axis; Direction.Axis axis;
@ -48,13 +49,13 @@ public interface IMultiBlockEntityContainer {
return axis; return axis;
} }
int getMaxLength (Direction.Axis longAxis, int width); int getMaxLength(Direction.Axis longAxis, int width);
int getMaxWidth (); int getMaxWidth();
int getHeight (); int getHeight();
void setHeight (int height); void setHeight(int height);
int getWidth (); int getWidth();
void setWidth (int width); void setWidth(int width);
public interface Inventory extends IMultiBlockEntityContainer { public interface Inventory extends IMultiBlockEntityContainer {
default boolean hasInventory() { return false; } default boolean hasInventory() { return false; }