diff options
Diffstat (limited to 'src/main/java/com/coasteen/armcontainers')
3 files changed, 207 insertions, 0 deletions
diff --git a/src/main/java/com/coasteen/armcontainers/ArmContainers.java b/src/main/java/com/coasteen/armcontainers/ArmContainers.java new file mode 100644 index 0000000..aceada0 --- /dev/null +++ b/src/main/java/com/coasteen/armcontainers/ArmContainers.java @@ -0,0 +1,29 @@ +package com.coasteen.armcontainers; + +import com.coasteen.armcontainers.create.GenericContainerPointType; +import com.simibubi.create.content.kinetics.mechanicalArm.ArmInteractionPointType; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.registries.RegisterEvent; + +@Mod(ArmContainers.MOD_ID) +public class ArmContainers { + public static final String MOD_ID = "armcontainers"; + + public ArmContainers(IEventBus modEventBus) { + modEventBus.addListener(this::onRegister); + } + + private void onRegister(RegisterEvent event) { + event.register( + ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath("create", "arm_interaction_point_type")), + helper -> helper.register( + ResourceLocation.fromNamespaceAndPath(MOD_ID, "generic_container"), + new GenericContainerPointType() + ) + ); + } +} diff --git a/src/main/java/com/coasteen/armcontainers/create/GenericContainerPoint.java b/src/main/java/com/coasteen/armcontainers/create/GenericContainerPoint.java new file mode 100644 index 0000000..6a8d649 --- /dev/null +++ b/src/main/java/com/coasteen/armcontainers/create/GenericContainerPoint.java @@ -0,0 +1,143 @@ +package com.coasteen.armcontainers.create; + +import com.simibubi.create.content.kinetics.mechanicalArm.ArmBlockEntity; +import com.simibubi.create.content.kinetics.mechanicalArm.ArmInteractionPoint; +import com.simibubi.create.content.kinetics.mechanicalArm.ArmInteractionPointType; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BarrelBlock; +import net.minecraft.world.level.block.ChestBlock; +import net.minecraft.world.level.block.entity.BarrelBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.entity.EnderChestBlockEntity; +import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.ChestType; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class GenericContainerPoint extends ArmInteractionPoint { + + // Track active interactions per container position to allow overlapping arm access + private static final Map<BlockPos, AtomicInteger> CONTAINER_REF_COUNTS = new ConcurrentHashMap<>(); + + public GenericContainerPoint(ArmInteractionPointType type, Level level, BlockPos pos, BlockState state) { + super(type, level, pos, state); + } + + @Override + public ItemStack extract(ArmBlockEntity arm, int slot, int amount, boolean simulate) { + ItemStack result = super.extract(arm, slot, amount, simulate); + if (!simulate && !result.isEmpty()) { + animateContainer(); + } + return result; + } + + @Override + public ItemStack extract(ArmBlockEntity arm, int amount, boolean simulate) { + ItemStack result = super.extract(arm, amount, simulate); + if (!simulate && !result.isEmpty()) { + animateContainer(); + } + return result; + } + + @Override + public ItemStack insert(ArmBlockEntity arm, ItemStack stack, boolean simulate) { + int originalCount = stack.getCount(); + ItemStack remainder = super.insert(arm, stack, simulate); + if (!simulate && remainder.getCount() < originalCount) { + animateContainer(); + } + return remainder; + } + + private void animateContainer() { + if (level == null || level.isClientSide()) return; + + BlockEntity be = level.getBlockEntity(pos); + BlockState state = level.getBlockState(pos); + + AtomicInteger refCount = CONTAINER_REF_COUNTS.computeIfAbsent(pos, k -> new AtomicInteger(0)); + + // If the container isn't currently held open by an arm, trigger the open animation + if (refCount.getAndIncrement() == 0) { + openContainer(be, state); + } + + if (level instanceof ServerLevel serverLevel) { + // Extended delay (~1.2 seconds) to keep the container open longer + CompletableFuture.delayedExecutor(1200, TimeUnit.MILLISECONDS).execute(() -> { + serverLevel.getServer().execute(() -> { + // Only close if all pending arm actions are complete + if (refCount.decrementAndGet() <= 0) { + CONTAINER_REF_COUNTS.remove(pos); + closeContainer(be); + } + }); + }); + } + } + + private void openContainer(BlockEntity be, BlockState state) { + if (be instanceof ChestBlockEntity) { + triggerChestEvent(state, 1); + playSound(SoundEvents.CHEST_OPEN); + } else if (be instanceof EnderChestBlockEntity) { + level.blockEvent(pos, state.getBlock(), 1, 1); + playSound(SoundEvents.ENDER_CHEST_OPEN); + } else if (be instanceof ShulkerBoxBlockEntity) { + level.blockEvent(pos, state.getBlock(), 1, 1); + playSound(SoundEvents.SHULKER_BOX_OPEN); + } else if (be instanceof BarrelBlockEntity) { + if (state.hasProperty(BarrelBlock.OPEN) && !state.getValue(BarrelBlock.OPEN)) { + level.setBlock(pos, state.setValue(BarrelBlock.OPEN, true), 3); + playSound(SoundEvents.BARREL_OPEN); + } + } + } + + private void closeContainer(BlockEntity be) { + if (level == null || level.isClientSide()) return; + BlockState currentState = level.getBlockState(pos); + + if (be instanceof ChestBlockEntity) { + triggerChestEvent(currentState, 0); + playSound(SoundEvents.CHEST_CLOSE); + } else if (be instanceof EnderChestBlockEntity) { + level.blockEvent(pos, currentState.getBlock(), 1, 0); + playSound(SoundEvents.ENDER_CHEST_CLOSE); + } else if (be instanceof ShulkerBoxBlockEntity) { + level.blockEvent(pos, currentState.getBlock(), 1, 0); + playSound(SoundEvents.SHULKER_BOX_CLOSE); + } else if (be instanceof BarrelBlockEntity) { + if (currentState.hasProperty(BarrelBlock.OPEN) && currentState.getValue(BarrelBlock.OPEN)) { + level.setBlock(pos, currentState.setValue(BarrelBlock.OPEN, false), 3); + playSound(SoundEvents.BARREL_CLOSE); + } + } + } + + private void triggerChestEvent(BlockState state, int param) { + level.blockEvent(pos, state.getBlock(), 1, param); + if (state.hasProperty(ChestBlock.TYPE) && state.getValue(ChestBlock.TYPE) != ChestType.SINGLE) { + BlockPos neighborPos = pos.relative(ChestBlock.getConnectedDirection(state)); + level.blockEvent(neighborPos, state.getBlock(), 1, param); + } + } + + private void playSound(SoundEvent sound) { + level.playSound(null, pos, sound, SoundSource.BLOCKS, 0.5F, level.random.nextFloat() * 0.1F + 0.9F); + } +} diff --git a/src/main/java/com/coasteen/armcontainers/create/GenericContainerPointType.java b/src/main/java/com/coasteen/armcontainers/create/GenericContainerPointType.java new file mode 100644 index 0000000..9020041 --- /dev/null +++ b/src/main/java/com/coasteen/armcontainers/create/GenericContainerPointType.java @@ -0,0 +1,35 @@ +package com.coasteen.armcontainers.create; + +import com.simibubi.create.content.kinetics.mechanicalArm.ArmInteractionPoint; +import com.simibubi.create.content.kinetics.mechanicalArm.ArmInteractionPointType; +import com.simibubi.create.content.logistics.vault.ItemVaultBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BarrelBlockEntity; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.capabilities.Capabilities; +import org.jetbrains.annotations.Nullable; + +public class GenericContainerPointType extends ArmInteractionPointType { + + @Override + public boolean canCreatePoint(Level level, BlockPos pos, BlockState state) { + var be = level.getBlockEntity(pos); + if (be == null) return false; + + boolean isSupportedType = be instanceof ChestBlockEntity + || be instanceof ShulkerBoxBlockEntity + || be instanceof BarrelBlockEntity + || be instanceof ItemVaultBlockEntity; + + return isSupportedType && level.getCapability(Capabilities.ItemHandler.BLOCK, pos, null) != null; + } + + @Nullable + @Override + public ArmInteractionPoint createPoint(Level level, BlockPos pos, BlockState state) { + return new GenericContainerPoint(this, level, pos, state); + } +} |
