diff --git a/src/main/java/com/simibubi/create/api/behaviour/movement/ContraptionHandoffContainer.java b/src/main/java/com/simibubi/create/api/behaviour/movement/ContraptionHandoffContainer.java new file mode 100644 index 0000000000..86d4ac30be --- /dev/null +++ b/src/main/java/com/simibubi/create/api/behaviour/movement/ContraptionHandoffContainer.java @@ -0,0 +1,22 @@ +package com.simibubi.create.api.behaviour.movement; + +import org.jetbrains.annotations.NotNull; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public record ContraptionHandoffContainer(ListeningMovementBehaviour behaviour, ContraptionHandoffContext context) { + + public static ContraptionHandoffContainer of(@NotNull ListeningMovementBehaviour behaviour, @NotNull BlockState state, BlockEntity blockEntity, BlockPos structurePos, BlockPos realPos) { + return new ContraptionHandoffContainer(behaviour, new ContraptionHandoffContext(state, blockEntity, structurePos, realPos)); + } + + public static record ContraptionHandoffContext(@NotNull BlockState state, BlockEntity blockEntity, BlockPos structurePos, BlockPos realPos) { + @Override + public final String toString() { + String be = blockEntity == null ? " (No BlockEntity)" : (" '" + blockEntity.getClass().getSimpleName() + "'"); + return "ContraptionHandoffContext[" + state + be + " at (" + realPos.getX() + ", " + realPos.getY() + ", " + realPos.getZ() + "), (" + structurePos.getX() + ", " + structurePos.getY() + ", " + structurePos.getZ() + ")"; + } + } +} diff --git a/src/main/java/com/simibubi/create/api/behaviour/movement/ListeningMovementBehaviour.java b/src/main/java/com/simibubi/create/api/behaviour/movement/ListeningMovementBehaviour.java new file mode 100644 index 0000000000..6f834ab7b6 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/behaviour/movement/ListeningMovementBehaviour.java @@ -0,0 +1,37 @@ +package com.simibubi.create.api.behaviour.movement; + +import com.simibubi.create.api.behaviour.movement.ContraptionHandoffContainer.ContraptionHandoffContext; +import com.simibubi.create.content.contraptions.AbstractContraptionEntity; +import com.simibubi.create.content.contraptions.Contraption; + +import net.minecraft.world.level.LevelAccessor; + +/** + * A {@link MovementBehaviour} which allows actors to + * define custom assembly and disassembly behaviours. + */ +public interface ListeningMovementBehaviour extends MovementBehaviour { + + /** + * Called when a contraption has been initialized that had previously + * destroyed a Block registered with this behaviour. + * At the time of invocation, any Blocks or BlockEntities that may + * have existed will have already been removed, so the BlockEntity + * instance in the container is stale. + * @param world + * @param ace + * @param contraption + * @param ctx {@link ContraptionHandoffContext} containing a stale BlockEntity reference + */ + public default void onAddedToContraption(LevelAccessor world, AbstractContraptionEntity ace, Contraption contraption, ContraptionHandoffContext ctx) {} + + /** + * Called as an {@link AbstractContraptionEntity} is disassembling. + * This method is called after all blocks have been placed back into the world + * and the contraption entity has been removed. + * @param world + * @param contraption caller + * @param ctx {@link ContraptionHandoffContext} containing the now re-instantiated BlockEntity reference + */ + public default void onRemovedFromContraption(LevelAccessor world, AbstractContraptionEntity ace, Contraption contraption, ContraptionHandoffContext ctx) {} +} diff --git a/src/main/java/com/simibubi/create/api/event/ContraptionEvent.java b/src/main/java/com/simibubi/create/api/event/ContraptionEvent.java new file mode 100644 index 0000000000..fe816f402b --- /dev/null +++ b/src/main/java/com/simibubi/create/api/event/ContraptionEvent.java @@ -0,0 +1,21 @@ +package com.simibubi.create.api.event; + +import com.simibubi.create.content.contraptions.AbstractContraptionEntity; + +import net.neoforged.neoforge.event.entity.EntityEvent; + +public abstract class ContraptionEvent extends EntityEvent { + public ContraptionEvent(AbstractContraptionEntity entity) { + super(entity); + } + public static class Assemble extends ContraptionEvent { + public Assemble(AbstractContraptionEntity entity) { + super(entity); + } + } + public static class Disassemble extends ContraptionEvent { + public Disassemble(AbstractContraptionEntity entity) { + super(entity); + } + } +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java b/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java index 6d433d8f60..af1ce1eeef 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java @@ -17,6 +17,7 @@ import com.simibubi.create.AllItems; import com.simibubi.create.AllSoundEvents; import com.simibubi.create.api.behaviour.movement.MovementBehaviour; +import com.simibubi.create.api.event.ContraptionEvent; import com.simibubi.create.content.contraptions.actors.psi.PortableStorageInterfaceMovement; import com.simibubi.create.content.contraptions.actors.seat.SeatBlock; import com.simibubi.create.content.contraptions.actors.seat.SeatEntity; @@ -73,9 +74,9 @@ import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; - import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; public abstract class AbstractContraptionEntity extends Entity implements IEntityWithComplexSpawn { @@ -281,7 +282,7 @@ public boolean control(BlockPos controlsLocalPos, Collection heldContro public void stopControlling(BlockPos controlsLocalPos) { getControllingPlayer().map(level()::getPlayerByUUID) - .map(p -> (p instanceof ServerPlayer) ? ((ServerPlayer) p) : null) + .map(p -> (p instanceof ServerPlayer s) ? s : null) .ifPresent(p -> CatnipServices.NETWORK.sendToClient(p, ControlsStopControllingPacket.INSTANCE)); setControllingPlayer(null); } @@ -651,6 +652,7 @@ protected void readAdditional(CompoundTag compound, boolean spawnData) { } public void disassemble() { + if (!isAlive()) return; if (contraption == null) @@ -681,6 +683,10 @@ public void disassemble() { ejectPassengers(); moveCollidedEntitiesOnDisassembly(transform); + + contraption.consumeHandoffContainers(this, false); + NeoForge.EVENT_BUS.post(new ContraptionEvent.Disassemble(this)); + AllSoundEvents.CONTRAPTION_DISASSEMBLE.playOnServer(level(), blockPosition()); } @@ -830,6 +836,7 @@ public Vec3 getContactPointMotion(Vec3 globalContactPoint) { return contraptionLocalMovement.add(contraptionAnchorMovement); } + @Override public boolean canCollideWith(Entity e) { if (e instanceof Player && e.isSpectator()) return false; diff --git a/src/main/java/com/simibubi/create/content/contraptions/Contraption.java b/src/main/java/com/simibubi/create/content/contraptions/Contraption.java index cf213edbba..223f7986ac 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/Contraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/Contraption.java @@ -31,9 +31,12 @@ import com.simibubi.create.AllBlocks; import com.simibubi.create.AllTags.AllContraptionTypeTags; import com.simibubi.create.api.behaviour.interaction.MovingInteractionBehaviour; +import com.simibubi.create.api.behaviour.movement.ContraptionHandoffContainer; +import com.simibubi.create.api.behaviour.movement.ListeningMovementBehaviour; import com.simibubi.create.api.behaviour.movement.MovementBehaviour; import com.simibubi.create.api.contraption.BlockMovementChecks; import com.simibubi.create.api.contraption.ContraptionType; +import com.simibubi.create.api.event.ContraptionEvent; import com.simibubi.create.content.contraptions.actors.contraptionControls.ContraptionControlsMovement; import com.simibubi.create.content.contraptions.actors.harvester.HarvesterMovementBehaviour; import com.simibubi.create.content.contraptions.actors.seat.SeatBlock; @@ -76,6 +79,7 @@ import com.simibubi.create.foundation.utility.BlockHelper; import com.simibubi.create.infrastructure.config.AllConfigs; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.createmod.catnip.data.Iterate; import net.createmod.catnip.data.UniqueLinkedList; import net.createmod.catnip.math.BBHelper; @@ -128,8 +132,8 @@ import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; - import net.neoforged.neoforge.client.model.data.ModelData; +import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.registries.GameData; public abstract class Contraption { @@ -162,6 +166,8 @@ public abstract class Contraption { private CompletableFuture simplifiedEntityColliderProvider; + private @Nullable List handoffers; + // Client public Map modelData; public Map presentBlockEntities; @@ -303,6 +309,9 @@ public void onEntityInitialize(Level world, AbstractContraptionEntity contraptio continue; contraptionEntity.addSittingPassenger(passenger, seatIndex); } + + NeoForge.EVENT_BUS.post(new ContraptionEvent.Assemble(contraptionEntity)); + consumeHandoffContainers(contraptionEntity, true); } /** @@ -1103,7 +1112,16 @@ public void removeBlocksFromWorld(Level world, BlockPos offset) { blockMismatch &= !AllBlocks.POWERED_SHAFT.is(blockIn) || !AllBlocks.SHAFT.has(block.state()); if (blockMismatch) iterator.remove(); + + MovementBehaviour actor = MovementBehaviour.REGISTRY.get(block.state()); + if (actor instanceof ListeningMovementBehaviour lmb) { + if (handoffers == null) handoffers = new ObjectArrayList<>(11); + BlockEntity be = world.getBlockEntity(add); + handoffers.add(ContraptionHandoffContainer.of(lmb, block.state(), be, block.pos(), add)); + } + world.removeBlockEntity(add); + int flags = Block.UPDATE_MOVE_BY_PISTON | Block.UPDATE_SUPPRESS_DROPS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE; if (blockIn instanceof SimpleWaterloggedBlock && oldState.hasProperty(BlockStateProperties.WATERLOGGED) @@ -1253,8 +1271,13 @@ public void addBlocksToWorld(Level world, StructureTransform transform) { storage.unmount(world, block, targetPos, blockEntity); - if (blockEntity != null) { + if (blockEntity != null) transform.apply(blockEntity); + + MovementBehaviour actor = MovementBehaviour.REGISTRY.get(block.state()); + if (actor instanceof ListeningMovementBehaviour lmb) { + if (handoffers == null) handoffers = new ObjectArrayList<>(11); + handoffers.add(ContraptionHandoffContainer.of(lmb, blockState, blockEntity, block.pos(), targetPos)); } } } @@ -1275,6 +1298,15 @@ public void addBlocksToWorld(Level world, StructureTransform transform) { } } + protected void consumeHandoffContainers(AbstractContraptionEntity entity, boolean add) { + if (handoffers == null) return; + for(ContraptionHandoffContainer container : handoffers) { + if (add) container.behaviour().onAddedToContraption(world, entity, this, container.context()); + else container.behaviour().onRemovedFromContraption(world, entity, this, container.context()); + } + handoffers = null; + } + protected void translateMultiblockControllers(StructureTransform transform) { if (transform.rotationAxis != null && transform.rotationAxis != Axis.Y && transform.rotation != Rotation.NONE) { capturedMultiblocks.values().forEach(info -> {