Files
TiedUp-/src/main/java/com/tiedup/remake/blocks/BlockRopeTrap.java
NotEvil fd60086322 feat(i18n): complete migration — items, entities, AI goals, GUI screens
119 new translation keys across 3 domains:
- Items/Blocks/Misc (46 keys): tooltips, action messages, trap states
- Entities/AI Goals (55 keys): NPC speech, maid/master/guard messages
- Client GUI (18 keys): widget labels, screen buttons, merchant display

Remaining 119 Component.literal() are all intentional:
- Debug/Admin/Command wands (47) — dev tools, not player-facing
- Entity display names (~25) — dynamic getNpcName() calls
- Empty string roots (~15) — .append() chain bases
- User-typed text (~10) — /me, /pm, /norp chat content
- Runtime data (~12) — StringBuilder, gag muffling, MCA compat
2026-04-16 12:33:13 +02:00

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.translatable("block.tiedup.trap.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.translatable("block.tiedup.trap.disarmed").withStyle(
ChatFormatting.GREEN
)
);
}
} else {
tooltip.add(
Component.translatable("block.tiedup.trap.disarmed").withStyle(ChatFormatting.GREEN)
);
}
}
}