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
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.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)
|
|
);
|
|
}
|
|
}
|
|
}
|