package com.tiedup.remake.blocks; import com.tiedup.remake.blocks.entity.TrapBlockEntity; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.util.BondageItemLoaderUtility; import com.tiedup.remake.util.KidnappedHelper; import java.util.List; import javax.annotation.Nullable; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.RenderShape; import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; /** * Rope Trap Block - Trap that ties up entities when they walk on it. * * * Features: * - Flat block (1 pixel tall) placed on solid surfaces * - Can be loaded with bondage items by right-clicking * - When armed and entity walks on it, applies all stored items * - Destroys itself after triggering * - Preserves NBT data when broken * * Based on original BlockRopesTrap from 1.12.2 */ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded { // Shape: 1 pixel tall carpet-like block protected static final VoxelShape TRAP_SHAPE = Block.box( 0.0, 0.0, 0.0, 16.0, 1.0, 16.0 ); public BlockRopeTrap() { super( BlockBehaviour.Properties.of() .strength(1.0f, 0.5f) .sound(SoundType.WOOL) .noOcclusion() .noCollission() // Entities can walk through/on it ); } // SHAPE AND RENDERING @Override public VoxelShape getShape( BlockState state, BlockGetter level, BlockPos pos, CollisionContext context ) { return TRAP_SHAPE; } @Override public VoxelShape getCollisionShape( BlockState state, BlockGetter level, BlockPos pos, CollisionContext context ) { return Shapes.empty(); // No collision - entities walk through } @Override public RenderShape getRenderShape(BlockState state) { return RenderShape.MODEL; } // PLACEMENT RULES @Override public boolean canSurvive( BlockState state, LevelReader level, BlockPos pos ) { BlockPos below = pos.below(); BlockState belowState = level.getBlockState(below); // Can only be placed on full solid blocks return belowState.isFaceSturdy(level, below, Direction.UP); } @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext context) { if ( !canSurvive( defaultBlockState(), context.getLevel(), context.getClickedPos() ) ) { return null; } return defaultBlockState(); } @Override public BlockState updateShape( BlockState state, Direction facing, BlockState facingState, LevelAccessor level, BlockPos pos, BlockPos facingPos ) { // Break if support block is removed if (facing == Direction.DOWN && !canSurvive(state, level, pos)) { return Blocks.AIR.defaultBlockState(); } return super.updateShape( state, facing, facingState, level, pos, facingPos ); } // BLOCK ENTITY @Nullable @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new TrapBlockEntity(pos, state); } @Nullable public TrapBlockEntity getTrapEntity(BlockGetter level, BlockPos pos) { BlockEntity be = level.getBlockEntity(pos); return be instanceof TrapBlockEntity ? (TrapBlockEntity) be : null; } // TRAP TRIGGER @Override public void entityInside( BlockState state, Level level, BlockPos pos, Entity entity ) { if (level.isClientSide) return; // Only affect living entities if (!(entity instanceof LivingEntity living)) return; // Get target's kidnapped state IBondageState targetState = KidnappedHelper.getKidnappedState(living); if (targetState == null) return; // Don't trigger if already tied if (targetState.isTiedUp()) return; // Get trap data TrapBlockEntity trap = getTrapEntity(level, pos); if (trap == null || !trap.isArmed()) return; // Apply all bondage items ItemStack bind = trap.getBind(); ItemStack gag = trap.getGag(); ItemStack blindfold = trap.getBlindfold(); ItemStack earplugs = trap.getEarplugs(); ItemStack collar = trap.getCollar(); ItemStack clothes = trap.getClothes(); targetState.applyBondage( bind, gag, blindfold, earplugs, collar, clothes ); // Destroy the trap level.destroyBlock(pos, false); // Notify target if (entity instanceof Player player) { player.displayClientMessage( Component.translatable("tiedup.trap.triggered").withStyle( ChatFormatting.RED ), true ); } TiedUpMod.LOGGER.info( "[BlockRopeTrap] Trap triggered at {} on {}", pos, entity.getName().getString() ); } // LOADING ITEMS @Override public InteractionResult use( BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit ) { if (hand != InteractionHand.MAIN_HAND) { return InteractionResult.PASS; } ItemStack heldItem = player.getItemInHand(hand); // Empty hand = do nothing if (heldItem.isEmpty()) { return InteractionResult.PASS; } // Check if it's a bondage item if (!BondageItemLoaderUtility.isLoadableBondageItem(heldItem)) { return InteractionResult.PASS; } // Server-side only if (level.isClientSide) { return InteractionResult.SUCCESS; } TrapBlockEntity trap = getTrapEntity(level, pos); if (trap == null) { return InteractionResult.PASS; } // Try to load the held item into the appropriate slot if ( BondageItemLoaderUtility.loadItemIntoHolder(trap, heldItem, player) ) { SystemMessageManager.sendToPlayer( player, "Item loaded into trap", ChatFormatting.YELLOW ); return InteractionResult.SUCCESS; } return InteractionResult.PASS; } // DROPS WITH NBT @Override public List getDrops( BlockState state, LootParams.Builder params ) { BlockEntity be = params.getOptionalParameter( LootContextParams.BLOCK_ENTITY ); ItemStack stack = new ItemStack(this); if (be instanceof TrapBlockEntity trap) { CompoundTag beTag = new CompoundTag(); trap.writeBondageData(beTag); if (!beTag.isEmpty()) { stack.addTagElement("BlockEntityTag", beTag); } } return List.of(stack); } // TOOLTIP @Override public void appendHoverText( ItemStack stack, @Nullable BlockGetter level, List tooltip, TooltipFlag flag ) { tooltip.add( Component.translatable("block.tiedup.rope_trap.desc").withStyle( ChatFormatting.GRAY ) ); CompoundTag nbt = stack.getTag(); if (nbt != null && nbt.contains("BlockEntityTag")) { CompoundTag beTag = nbt.getCompound("BlockEntityTag"); // Check if armed if (beTag.contains("bind")) { tooltip.add( Component.literal("Armed").withStyle( ChatFormatting.DARK_RED ) ); // List loaded items BondageItemLoaderUtility.addItemToTooltip( tooltip, beTag, "bind" ); BondageItemLoaderUtility.addItemToTooltip( tooltip, beTag, "gag" ); BondageItemLoaderUtility.addItemToTooltip( tooltip, beTag, "blindfold" ); BondageItemLoaderUtility.addItemToTooltip( tooltip, beTag, "earplugs" ); BondageItemLoaderUtility.addItemToTooltip( tooltip, beTag, "collar" ); BondageItemLoaderUtility.addItemToTooltip( tooltip, beTag, "clothes" ); } else { tooltip.add( Component.literal("Disarmed").withStyle( ChatFormatting.GREEN ) ); } } else { tooltip.add( Component.literal("Disarmed").withStyle(ChatFormatting.GREEN) ); } } }