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.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.simibubi.create.AllBlockEntityTypes;
import com.simibubi.create.AllBlocks;
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.EmptyLighter;
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.IRotate;
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.steamEngine.PoweredShaftBlockEntity;
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.trains.bogey.AbstractBogeyBlock;
import com.simibubi.create.foundation.blockEntity.IMultiBlockEntityContainer;
@ -149,6 +149,7 @@ public abstract class Contraption {
protected Map<UUID, Integer> seatMapping;
protected Map<UUID, BlockFace> stabilizedSubContraptions;
protected MountedStorageManager storage;
protected Multimap<BlockPos, StructureBlockInfo> capturedMultiblocks;
private Set<SuperGlueEntity> glueToRemove;
private Map<BlockPos, Entity> initialPassengers;
@ -183,6 +184,7 @@ public abstract class Contraption {
stabilizedSubContraptions = new HashMap<>();
simplifiedEntityColliders = Optional.empty();
storage = new MountedStorageManager();
capturedMultiblocks = ArrayListMultimap.create();
}
public ContraptionWorld getContraptionWorld() {
@ -642,6 +644,8 @@ public abstract class Contraption {
BlockEntity be = pair.getValue();
storage.addBlock(localPos, be);
captureMultiblock(localPos, structureBlockInfo, be);
if (AllMovementBehaviours.getBehaviour(captured.state) != null)
actors.add(MutablePair.of(structureBlockInfo, null));
@ -656,6 +660,25 @@ public abstract class Contraption {
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
protected CompoundTag getBlockEntityNBT(Level world, BlockPos pos) {
BlockEntity blockEntity = world.getBlockEntity(pos);
@ -666,11 +689,6 @@ public abstract class Contraption {
nbt.remove("y");
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;
}
@ -694,11 +712,25 @@ public abstract class Contraption {
Tag blocks = nbt.get("Blocks");
// used to differentiate between the 'old' and the paletted serialization
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);
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();
nbt.getList("Actors", 10)
nbt.getList("Actors", Tag.TAG_COMPOUND)
.forEach(c -> {
CompoundTag comp = (CompoundTag) c;
StructureBlockInfo info = this.blocks.get(NbtUtils.readBlockPos(comp.getCompound("Pos")));
@ -741,7 +773,7 @@ public abstract class Contraption {
storage.read(nbt, presentBlockEntities, spawnData);
if (nbt.contains("BoundsFront"))
bounds = NBTHelper.readAABB(nbt.getList("BoundsFront", 5));
bounds = NBTHelper.readAABB(nbt.getList("BoundsFront", Tag.TAG_FLOAT));
stalled = nbt.getBoolean("Stalled");
hasUniversalCreativeCrate = nbt.getBoolean("BottomlessSupply");
@ -754,6 +786,19 @@ public abstract class Contraption {
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();
for (MutablePair<StructureBlockInfo, MovementContext> actor : getActors()) {
MovementBehaviour behaviour = AllMovementBehaviours.getBehaviour(actor.left.state);
@ -804,6 +849,7 @@ public abstract class Contraption {
nbt.put("Blocks", blocksNBT);
nbt.put("Actors", actorsNBT);
nbt.put("CapturedMultiblocks", multiblocksNBT);
nbt.put("DisabledActors", disabledActorsNBT);
nbt.put("Interactors", interactorNBT);
nbt.put("Superglue", superglueNBT);
@ -859,12 +905,12 @@ public abstract class Contraption {
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();
for (int i = 0; i < list.size(); ++i)
palette.values.add(NbtUtils.readBlockState(list.getCompound(i)));
blockList = c.getList("BlockList", 10);
blockList = c.getList("BlockList", Tag.TAG_COMPOUND);
} else {
blockList = (ListTag) compound;
}
@ -1022,6 +1068,8 @@ public abstract class Contraption {
return;
disassembled = true;
translateMultiblockControllers(transform);
for (boolean nonBrittles : Iterate.trueAndFalse) {
for (StructureBlockInfo block : blocks.values()) {
if (nonBrittles == BlockMovementChecks.isBrittle(block.state))
@ -1088,8 +1136,11 @@ public abstract class Contraption {
tag.remove("InitialOffset");
}
if (blockEntity instanceof IMultiBlockEntityContainer && tag.contains("LastKnownPos"))
tag.put("LastKnownPos", NbtUtils.writeBlockPos(BlockPos.ZERO.below(Integer.MAX_VALUE - 1)));
if (blockEntity instanceof IMultiBlockEntityContainer) {
if (tag.contains("LastKnownPos") || capturedMultiblocks.isEmpty()) {
tag.put("LastKnownPos", NbtUtils.writeBlockPos(BlockPos.ZERO.below(Integer.MAX_VALUE - 1)));
}
}
blockEntity.load(tag);
storage.addStorageToWorld(block, blockEntity);
@ -1117,6 +1168,38 @@ public abstract class Contraption {
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) {
for (Entity seatedEntity : seatedEntities) {
if (getSeatMapping().isEmpty())
@ -1144,14 +1227,14 @@ public abstract class Contraption {
if (behaviour != null)
behaviour.startMoving(context);
pair.setRight(context);
if (behaviour instanceof ContraptionControlsMovement)
if (behaviour instanceof ContraptionControlsMovement)
disableActorOnStart(context);
}
for (ItemStack stack : disabledActors)
setActorsActive(stack, false);
}
protected void disableActorOnStart(MovementContext context) {
if (!ContraptionControlsMovement.isDisabledInitially(context))
return;

View file

@ -98,6 +98,16 @@ public class StructureTransform {
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) {
return applyWithoutOffset(localVec).add(Vec3.atLowerCornerOf(offset));
}
@ -110,6 +120,14 @@ public class StructureTransform {
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) {
if (be instanceof ITransformableBlockEntity)
((ITransformableBlockEntity) be).transform(this);

View file

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

View file

@ -13,24 +13,25 @@ import net.minecraftforge.fluids.IFluidTank;
public interface IMultiBlockEntityContainer {
BlockPos getController();
<T extends BlockEntity & IMultiBlockEntityContainer> T getControllerBE ();
<T extends BlockEntity & IMultiBlockEntityContainer> T getControllerBE();
boolean isController();
void setController(BlockPos pos);
void removeController (boolean keepContents);
void removeController(boolean keepContents);
BlockPos getLastKnownPos();
void preventConnectivityUpdate ();
void notifyMultiUpdated ();
void preventConnectivityUpdate();
void notifyMultiUpdated();
// 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
default Object getExtraData () { return null; }
default Object modifyExtraData (Object data) { return data; }
default Object getExtraData() { return null; }
default Object modifyExtraData(Object data) { return data; }
// multiblock structural information
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();
Direction.Axis axis;
@ -48,13 +49,13 @@ public interface IMultiBlockEntityContainer {
return axis;
}
int getMaxLength (Direction.Axis longAxis, int width);
int getMaxWidth ();
int getMaxLength(Direction.Axis longAxis, int width);
int getMaxWidth();
int getHeight ();
void setHeight (int height);
int getWidth ();
void setWidth (int width);
int getHeight();
void setHeight(int height);
int getWidth();
void setWidth(int width);
public interface Inventory extends IMultiBlockEntityContainer {
default boolean hasInventory() { return false; }