Strip all Phase references, TODO/FUTURE roadmap notes, and internal planning comments from the codebase. Run Prettier for consistent formatting across all Java files.
381 lines
11 KiB
Java
381 lines
11 KiB
Java
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<ItemStack> 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<Component> 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)
|
|
);
|
|
}
|
|
}
|
|
}
|