Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
288
src/main/java/com/tiedup/remake/blocks/BlockCellCore.java
Normal file
288
src/main/java/com/tiedup/remake/blocks/BlockCellCore.java
Normal file
@@ -0,0 +1,288 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
import com.tiedup.remake.blocks.entity.CellCoreBlockEntity;
|
||||
import com.tiedup.remake.cells.*;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.cell.PacketOpenCoreMenu;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
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.material.MapColor;
|
||||
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;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Cell Core block - the anchor for a Cell System V2 cell.
|
||||
*
|
||||
* Placed into a wall of an enclosed room. On placement, runs flood-fill to
|
||||
* detect the room boundaries and registers a new cell. On removal, destroys
|
||||
* the cell.
|
||||
*
|
||||
* Obsidian-tier hardness to prevent easy breaking by prisoners.
|
||||
*/
|
||||
public class BlockCellCore extends BaseEntityBlock {
|
||||
|
||||
public BlockCellCore() {
|
||||
super(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.STONE)
|
||||
.strength(50.0f, 1200.0f)
|
||||
.requiresCorrectToolForDrops()
|
||||
.sound(SoundType.STONE)
|
||||
.noOcclusion()
|
||||
.dynamicShape()
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new CellCoreBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderShape getRenderShape(BlockState state) {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
// ==================== DYNAMIC SHAPE (disguise-aware) ====================
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public VoxelShape getShape(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos,
|
||||
CollisionContext context
|
||||
) {
|
||||
if (level.getBlockEntity(pos) instanceof CellCoreBlockEntity core) {
|
||||
BlockState disguise = core.getDisguiseState();
|
||||
if (disguise == null) disguise = core.resolveDisguise();
|
||||
if (disguise != null) {
|
||||
return disguise.getShape(level, pos, context);
|
||||
}
|
||||
}
|
||||
return Shapes.block();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public VoxelShape getCollisionShape(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos,
|
||||
CollisionContext context
|
||||
) {
|
||||
if (level.getBlockEntity(pos) instanceof CellCoreBlockEntity core) {
|
||||
BlockState disguise = core.getDisguiseState();
|
||||
if (disguise == null) disguise = core.resolveDisguise();
|
||||
if (disguise != null) {
|
||||
return disguise.getCollisionShape(level, pos, context);
|
||||
}
|
||||
}
|
||||
return Shapes.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Right-click handler: open the Cell Core menu for the cell owner.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public InteractionResult use(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit
|
||||
) {
|
||||
if (level.isClientSide) {
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
if (!(player instanceof ServerPlayer serverPlayer)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (
|
||||
!(be instanceof CellCoreBlockEntity core) ||
|
||||
core.getCellId() == null
|
||||
) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
CellRegistryV2 registry = CellRegistryV2.get((ServerLevel) level);
|
||||
CellDataV2 cell = registry.getCell(core.getCellId());
|
||||
if (cell == null) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// Check ownership (owner, camp cell, or OP level 2)
|
||||
if (!cell.canPlayerManage(player.getUUID(), player.hasPermissions(2))) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable("msg.tiedup.cell_core.not_owner"),
|
||||
true
|
||||
);
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
// Send packet with all cell stats
|
||||
ModNetwork.sendToPlayer(
|
||||
new PacketOpenCoreMenu(
|
||||
pos,
|
||||
cell.getId(),
|
||||
cell.getName() != null ? cell.getName() : "",
|
||||
cell.getState().getSerializedName(),
|
||||
cell.getInteriorBlocks().size(),
|
||||
cell.getWallBlocks().size(),
|
||||
cell.getBreachedPositions().size(),
|
||||
cell.getPrisonerCount(),
|
||||
cell.getBeds().size(),
|
||||
cell.getDoors().size(),
|
||||
cell.getAnchors().size(),
|
||||
core.getSpawnPoint() != null,
|
||||
core.getDeliveryPoint() != null,
|
||||
core.getDisguiseState() != null
|
||||
),
|
||||
serverPlayer
|
||||
);
|
||||
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
/**
|
||||
* On placement: run flood-fill to detect the room, create a cell if successful.
|
||||
* If the room cannot be detected, break the block back and show an error.
|
||||
*/
|
||||
@Override
|
||||
public void setPlacedBy(
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
BlockState state,
|
||||
@Nullable LivingEntity placer,
|
||||
ItemStack stack
|
||||
) {
|
||||
super.setPlacedBy(level, pos, state, placer, stack);
|
||||
|
||||
if (
|
||||
level instanceof ServerLevel serverLevel &&
|
||||
placer instanceof Player player
|
||||
) {
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (!(be instanceof CellCoreBlockEntity core)) return;
|
||||
|
||||
FloodFillResult result = FloodFillAlgorithm.tryFill(level, pos);
|
||||
if (result.isSuccess()) {
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
CellDataV2 cell = registry.createCell(
|
||||
pos,
|
||||
result,
|
||||
player.getUUID()
|
||||
);
|
||||
|
||||
core.setCellId(cell.getId());
|
||||
core.setInteriorFace(result.getInteriorFace());
|
||||
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.created",
|
||||
result.getInterior().size(),
|
||||
result.getWalls().size()
|
||||
),
|
||||
true
|
||||
);
|
||||
} else {
|
||||
// Failed — break the block back (can't form a cell here)
|
||||
level.destroyBlock(pos, true);
|
||||
player.displayClientMessage(
|
||||
Component.translatable(result.getErrorKey()),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On removal: destroy the cell in the registry.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onRemove(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
BlockState newState,
|
||||
boolean movedByPiston
|
||||
) {
|
||||
if (!state.is(newState.getBlock())) {
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (
|
||||
be instanceof CellCoreBlockEntity core &&
|
||||
core.getCellId() != null
|
||||
) {
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
CellDataV2 cell = registry.getCell(core.getCellId());
|
||||
if (cell != null) {
|
||||
cell.setState(CellState.COMPROMISED);
|
||||
|
||||
// Free all prisoners
|
||||
for (UUID prisonerId : cell.getPrisonerIds()) {
|
||||
registry.releasePrisoner(
|
||||
cell.getId(),
|
||||
prisonerId,
|
||||
serverLevel.getServer()
|
||||
);
|
||||
}
|
||||
|
||||
// Notify owner
|
||||
if (cell.getOwnerId() != null) {
|
||||
ServerPlayer owner = serverLevel
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(cell.getOwnerId());
|
||||
if (owner != null) {
|
||||
String cellName =
|
||||
cell.getName() != null
|
||||
? cell.getName()
|
||||
: "Cell " +
|
||||
cell
|
||||
.getId()
|
||||
.toString()
|
||||
.substring(0, 8);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
owner,
|
||||
SystemMessageManager.MessageCategory.WARNING,
|
||||
cellName + " has been destroyed!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
registry.removeCell(cell.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
}
|
||||
}
|
||||
34
src/main/java/com/tiedup/remake/blocks/BlockCellDoor.java
Normal file
34
src/main/java/com/tiedup/remake/blocks/BlockCellDoor.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
import net.minecraft.world.level.block.DoorBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.properties.BlockSetType;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
|
||||
/**
|
||||
* Cell Door Block - Iron-like door that cannot be opened by hand.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Features:
|
||||
* - Cannot be opened by clicking (requires redstone)
|
||||
* - Uses iron door behavior
|
||||
* - Sturdy construction
|
||||
*
|
||||
* Based on original BlockKidnapDoorBase from 1.12.2
|
||||
*/
|
||||
public class BlockCellDoor extends DoorBlock {
|
||||
|
||||
public BlockCellDoor() {
|
||||
super(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.METAL)
|
||||
.strength(5.0f, 45.0f)
|
||||
.sound(SoundType.METAL)
|
||||
.requiresCorrectToolForDrops()
|
||||
.noOcclusion(),
|
||||
BlockSetType.IRON // J'ai remis moi même
|
||||
);
|
||||
}
|
||||
}
|
||||
346
src/main/java/com/tiedup/remake/blocks/BlockIronBarDoor.java
Normal file
346
src/main/java/com/tiedup/remake/blocks/BlockIronBarDoor.java
Normal file
@@ -0,0 +1,346 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
import com.tiedup.remake.blocks.entity.IronBarDoorBlockEntity;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.items.ItemCellKey;
|
||||
import com.tiedup.remake.items.ItemKey;
|
||||
import com.tiedup.remake.items.ItemMasterKey;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
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.block.*;
|
||||
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.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.*;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
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;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Iron Bar Door - A door made of iron bars that can be locked.
|
||||
*
|
||||
* Phase: Kidnapper Revamp - Cell System
|
||||
*
|
||||
* Features:
|
||||
* - Lockable with ItemKey (stores key UUID)
|
||||
* - Can be unlocked with matching key, ItemCellKey, or ItemMasterKey
|
||||
* - When locked, cannot be opened by redstone or by hand
|
||||
* - Visually connects to iron bars (like vanilla iron bars)
|
||||
* - Uses DoorBlock mechanics for open/close state
|
||||
*/
|
||||
public class BlockIronBarDoor extends DoorBlock implements EntityBlock {
|
||||
|
||||
// VoxelShapes for different orientations - centered like iron bars (7-9 = 2 pixels thick)
|
||||
protected static final VoxelShape SOUTH_AABB = Block.box(
|
||||
0.0,
|
||||
0.0,
|
||||
7.0,
|
||||
16.0,
|
||||
16.0,
|
||||
9.0
|
||||
);
|
||||
protected static final VoxelShape NORTH_AABB = Block.box(
|
||||
0.0,
|
||||
0.0,
|
||||
7.0,
|
||||
16.0,
|
||||
16.0,
|
||||
9.0
|
||||
);
|
||||
protected static final VoxelShape WEST_AABB = Block.box(
|
||||
7.0,
|
||||
0.0,
|
||||
0.0,
|
||||
9.0,
|
||||
16.0,
|
||||
16.0
|
||||
);
|
||||
protected static final VoxelShape EAST_AABB = Block.box(
|
||||
7.0,
|
||||
0.0,
|
||||
0.0,
|
||||
9.0,
|
||||
16.0,
|
||||
16.0
|
||||
);
|
||||
|
||||
public BlockIronBarDoor() {
|
||||
super(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.METAL)
|
||||
.strength(5.0f, 45.0f)
|
||||
.sound(SoundType.METAL)
|
||||
.requiresCorrectToolForDrops()
|
||||
.noOcclusion(),
|
||||
BlockSetType.IRON
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== SHAPE ====================
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos,
|
||||
CollisionContext context
|
||||
) {
|
||||
Direction facing = state.getValue(FACING);
|
||||
boolean open = state.getValue(OPEN);
|
||||
boolean hingeRight = state.getValue(HINGE) == DoorHingeSide.RIGHT;
|
||||
|
||||
Direction effectiveDirection;
|
||||
if (open) {
|
||||
effectiveDirection = hingeRight
|
||||
? facing.getCounterClockWise()
|
||||
: facing.getClockWise();
|
||||
} else {
|
||||
effectiveDirection = facing;
|
||||
}
|
||||
|
||||
return switch (effectiveDirection) {
|
||||
case NORTH -> NORTH_AABB;
|
||||
case SOUTH -> SOUTH_AABB;
|
||||
case WEST -> WEST_AABB;
|
||||
case EAST -> EAST_AABB;
|
||||
default -> SOUTH_AABB;
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== INTERACTION ====================
|
||||
|
||||
@Override
|
||||
public InteractionResult use(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit
|
||||
) {
|
||||
// Get the BlockEntity
|
||||
BlockEntity be = level.getBlockEntity(
|
||||
getBlockEntityPos(state, pos, level)
|
||||
);
|
||||
if (!(be instanceof IronBarDoorBlockEntity doorEntity)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
ItemStack heldItem = player.getItemInHand(hand);
|
||||
|
||||
// Handle key interactions
|
||||
if (heldItem.getItem() instanceof ItemMasterKey) {
|
||||
// Master key can always unlock
|
||||
if (doorEntity.isLocked()) {
|
||||
if (!level.isClientSide) {
|
||||
doorEntity.setLocked(false);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Door unlocked with master key"
|
||||
);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
} else if (heldItem.getItem() instanceof ItemCellKey) {
|
||||
// Cell key can unlock any iron bar door
|
||||
if (doorEntity.isLocked()) {
|
||||
if (!level.isClientSide) {
|
||||
doorEntity.setLocked(false);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Door unlocked with cell key"
|
||||
);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
} else if (heldItem.getItem() instanceof ItemKey key) {
|
||||
UUID keyUUID = key.getKeyUUID(heldItem);
|
||||
|
||||
if (doorEntity.isLocked()) {
|
||||
// Try to unlock
|
||||
if (doorEntity.matchesKey(keyUUID)) {
|
||||
if (!level.isClientSide) {
|
||||
doorEntity.setLocked(false);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Door unlocked"
|
||||
);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
} else {
|
||||
if (!level.isClientSide) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
"This key doesn't fit this lock"
|
||||
);
|
||||
}
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
} else {
|
||||
// Lock the door with this key
|
||||
if (!level.isClientSide) {
|
||||
doorEntity.setLockedByKeyUUID(keyUUID);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Door locked"
|
||||
);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
}
|
||||
|
||||
// If locked and no valid key, deny access
|
||||
if (doorEntity.isLocked()) {
|
||||
if (!level.isClientSide) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
"This door is locked"
|
||||
);
|
||||
}
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// Iron doors don't open by hand normally, but we allow it if unlocked
|
||||
// Toggle the door state
|
||||
if (!level.isClientSide) {
|
||||
boolean newOpen = !state.getValue(OPEN);
|
||||
setOpen(player, level, state, pos, newOpen);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BlockEntity position (handles double-height doors).
|
||||
*/
|
||||
private BlockPos getBlockEntityPos(
|
||||
BlockState state,
|
||||
BlockPos pos,
|
||||
Level level
|
||||
) {
|
||||
// LOW FIX: Check if HALF property exists before accessing (WorldEdit/SetBlock safety)
|
||||
if (!state.hasProperty(HALF)) {
|
||||
// Invalid state - assume lower half
|
||||
return pos;
|
||||
}
|
||||
|
||||
// BlockEntity is always on the lower half
|
||||
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) {
|
||||
return pos.below();
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// ==================== REDSTONE ====================
|
||||
|
||||
@Override
|
||||
public void neighborChanged(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Block neighborBlock,
|
||||
BlockPos neighborPos,
|
||||
boolean movedByPiston
|
||||
) {
|
||||
// Check if locked before allowing redstone control
|
||||
BlockEntity be = level.getBlockEntity(
|
||||
getBlockEntityPos(state, pos, level)
|
||||
);
|
||||
if (
|
||||
be instanceof IronBarDoorBlockEntity doorEntity &&
|
||||
doorEntity.isLocked()
|
||||
) {
|
||||
// Door is locked, ignore redstone
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow normal redstone behavior if unlocked
|
||||
super.neighborChanged(
|
||||
state,
|
||||
level,
|
||||
pos,
|
||||
neighborBlock,
|
||||
neighborPos,
|
||||
movedByPiston
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== BLOCK ENTITY ====================
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
// Only create block entity for the lower half
|
||||
if (state.getValue(HALF) == DoubleBlockHalf.LOWER) {
|
||||
return new IronBarDoorBlockEntity(pos, state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== PLACEMENT ====================
|
||||
|
||||
@Override
|
||||
public void setPlacedBy(
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
BlockState state,
|
||||
@Nullable net.minecraft.world.entity.LivingEntity placer,
|
||||
ItemStack stack
|
||||
) {
|
||||
super.setPlacedBy(level, pos, state, placer, stack);
|
||||
// BlockEntity is automatically created for lower half
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
BlockState newState,
|
||||
boolean movedByPiston
|
||||
) {
|
||||
if (!state.is(newState.getBlock())) {
|
||||
// Block entity is removed automatically
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
}
|
||||
|
||||
// ==================== VISUAL ====================
|
||||
|
||||
@Override
|
||||
public float getShadeBrightness(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos
|
||||
) {
|
||||
// Allow some light through like iron bars
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean propagatesSkylightDown(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
305
src/main/java/com/tiedup/remake/blocks/BlockKidnapBomb.java
Normal file
305
src/main/java/com/tiedup/remake/blocks/BlockKidnapBomb.java
Normal file
@@ -0,0 +1,305 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
import com.tiedup.remake.blocks.entity.KidnapBombBlockEntity;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityKidnapBomb;
|
||||
import com.tiedup.remake.util.BondageItemLoaderUtility;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.TntBlock;
|
||||
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.gameevent.GameEvent;
|
||||
import net.minecraft.world.level.storage.loot.LootParams;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
/**
|
||||
* Kidnap Bomb Block - TNT that applies bondage on explosion.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Features:
|
||||
* - TNT-like block that can be ignited
|
||||
* - Stores bondage items via BlockEntity
|
||||
* - On explosion, applies items to entities in radius (no block damage)
|
||||
* - Can be loaded by right-clicking with bondage items
|
||||
*
|
||||
* Based on original BlockKidnapBomb from 1.12.2
|
||||
*/
|
||||
public class BlockKidnapBomb
|
||||
extends TntBlock
|
||||
implements EntityBlock, ICanBeLoaded
|
||||
{
|
||||
|
||||
public BlockKidnapBomb() {
|
||||
super(
|
||||
BlockBehaviour.Properties.of()
|
||||
.strength(0.0f)
|
||||
.sound(SoundType.GRASS)
|
||||
.ignitedByLava()
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BLOCK ENTITY
|
||||
// ========================================
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new KidnapBombBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KidnapBombBlockEntity getBombEntity(
|
||||
BlockGetter level,
|
||||
BlockPos pos
|
||||
) {
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
return be instanceof KidnapBombBlockEntity
|
||||
? (KidnapBombBlockEntity) be
|
||||
: null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EXPLOSION HANDLING
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void onCaughtFire(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
@Nullable net.minecraft.core.Direction face,
|
||||
@Nullable LivingEntity igniter
|
||||
) {
|
||||
if (!level.isClientSide) {
|
||||
KidnapBombBlockEntity bombTile = getBombEntity(level, pos);
|
||||
explode(level, pos, bombTile, igniter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn the primed kidnap bomb entity.
|
||||
*/
|
||||
public void explode(
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
@Nullable KidnapBombBlockEntity bombTile,
|
||||
@Nullable LivingEntity igniter
|
||||
) {
|
||||
if (!level.isClientSide) {
|
||||
EntityKidnapBomb entity = new EntityKidnapBomb(
|
||||
level,
|
||||
pos.getX() + 0.5,
|
||||
pos.getY(),
|
||||
pos.getZ() + 0.5,
|
||||
igniter,
|
||||
bombTile
|
||||
);
|
||||
level.addFreshEntity(entity);
|
||||
level.playSound(
|
||||
null,
|
||||
entity.getX(),
|
||||
entity.getY(),
|
||||
entity.getZ(),
|
||||
SoundEvents.TNT_PRIMED,
|
||||
SoundSource.BLOCKS,
|
||||
1.0f,
|
||||
1.0f
|
||||
);
|
||||
level.gameEvent(igniter, GameEvent.PRIME_FUSE, pos);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BlockKidnapBomb] Bomb primed at {} by {}",
|
||||
pos,
|
||||
igniter != null ? igniter.getName().getString() : "unknown"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LOADING ITEMS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public InteractionResult use(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit
|
||||
) {
|
||||
ItemStack heldItem = player.getItemInHand(hand);
|
||||
|
||||
// First check if it's flint and steel (default TNT behavior)
|
||||
if (
|
||||
heldItem.is(Items.FLINT_AND_STEEL) || heldItem.is(Items.FIRE_CHARGE)
|
||||
) {
|
||||
return super.use(state, level, pos, player, hand, hit);
|
||||
}
|
||||
|
||||
if (hand != InteractionHand.MAIN_HAND) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
KidnapBombBlockEntity bomb = getBombEntity(level, pos);
|
||||
if (bomb == null) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// Try to load the held item into the appropriate slot
|
||||
if (
|
||||
BondageItemLoaderUtility.loadItemIntoHolder(bomb, heldItem, player)
|
||||
) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
"Item loaded into bomb",
|
||||
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 KidnapBombBlockEntity bomb) {
|
||||
CompoundTag beTag = new CompoundTag();
|
||||
bomb.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.kidnap_bomb.desc").withStyle(
|
||||
ChatFormatting.GRAY
|
||||
)
|
||||
);
|
||||
|
||||
CompoundTag nbt = stack.getTag();
|
||||
if (nbt != null && nbt.contains("BlockEntityTag")) {
|
||||
CompoundTag beTag = nbt.getCompound("BlockEntityTag");
|
||||
|
||||
// Check if loaded with any items
|
||||
if (
|
||||
beTag.contains("bind") ||
|
||||
beTag.contains("gag") ||
|
||||
beTag.contains("blindfold") ||
|
||||
beTag.contains("earplugs") ||
|
||||
beTag.contains("collar")
|
||||
) {
|
||||
tooltip.add(
|
||||
Component.literal("Loaded:").withStyle(
|
||||
ChatFormatting.YELLOW
|
||||
)
|
||||
);
|
||||
|
||||
// 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("Empty").withStyle(ChatFormatting.GREEN)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tooltip.add(
|
||||
Component.literal("Empty").withStyle(ChatFormatting.GREEN)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
247
src/main/java/com/tiedup/remake/blocks/BlockMarker.java
Normal file
247
src/main/java/com/tiedup/remake/blocks/BlockMarker.java
Normal file
@@ -0,0 +1,247 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
import com.tiedup.remake.blocks.entity.MarkerBlockEntity;
|
||||
import com.tiedup.remake.blocks.entity.ModBlockEntities;
|
||||
import com.tiedup.remake.cells.MarkerType;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.items.ItemAdminWand;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
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.material.MapColor;
|
||||
import net.minecraft.world.level.material.PushReaction;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Marker Block - Invisible block used to define cell spawn points.
|
||||
*
|
||||
* Phase: Kidnapper Revamp - Cell System
|
||||
*
|
||||
* Features:
|
||||
* - Invisible (no render)
|
||||
* - No collision
|
||||
* - Stores cell UUID via BlockEntity
|
||||
* - Destroying removes the cell from registry
|
||||
*
|
||||
* Placed by structure generation or Admin Wand.
|
||||
*/
|
||||
public class BlockMarker extends BaseEntityBlock {
|
||||
|
||||
// Small shape for selection/picking
|
||||
private static final VoxelShape SHAPE = Block.box(6, 6, 6, 10, 10, 10);
|
||||
|
||||
public BlockMarker() {
|
||||
super(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.NONE)
|
||||
.strength(0.5f) // Easy to break
|
||||
.sound(SoundType.WOOD)
|
||||
.noCollission()
|
||||
.noOcclusion()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== SHAPE ====================
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos,
|
||||
CollisionContext context
|
||||
) {
|
||||
// Small hitbox for selection
|
||||
return SHAPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getCollisionShape(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos,
|
||||
CollisionContext context
|
||||
) {
|
||||
// No collision
|
||||
return Shapes.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getVisualShape(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos,
|
||||
CollisionContext context
|
||||
) {
|
||||
// No visual shape
|
||||
return Shapes.empty();
|
||||
}
|
||||
|
||||
// ==================== RENDER ====================
|
||||
|
||||
@Override
|
||||
public RenderShape getRenderShape(BlockState state) {
|
||||
// Invisible - no model rendering
|
||||
return RenderShape.INVISIBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadeBrightness(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos
|
||||
) {
|
||||
// Full brightness (no shadow)
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean propagatesSkylightDown(
|
||||
BlockState state,
|
||||
BlockGetter level,
|
||||
BlockPos pos
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== PARTICLE FEEDBACK ====================
|
||||
|
||||
/**
|
||||
* Spawn particles to make the marker visible when a player is holding a wand.
|
||||
* This provides visual feedback for the otherwise invisible block.
|
||||
*/
|
||||
@Override
|
||||
public void animateTick(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
RandomSource random
|
||||
) {
|
||||
// Only show particles if a nearby player is holding an AdminWand
|
||||
Player nearestPlayer = level.getNearestPlayer(
|
||||
pos.getX() + 0.5,
|
||||
pos.getY() + 0.5,
|
||||
pos.getZ() + 0.5,
|
||||
16.0, // Detection range
|
||||
false // Include spectators
|
||||
);
|
||||
|
||||
if (nearestPlayer == null) return;
|
||||
|
||||
// Check if player is holding an Admin Wand
|
||||
boolean holdingWand =
|
||||
nearestPlayer.getMainHandItem().getItem() instanceof
|
||||
ItemAdminWand ||
|
||||
nearestPlayer.getOffhandItem().getItem() instanceof ItemAdminWand;
|
||||
|
||||
if (!holdingWand) return;
|
||||
|
||||
// Spawn subtle particles at the marker position
|
||||
// Use END_ROD for a glowing effect
|
||||
if (random.nextInt(3) == 0) {
|
||||
// 1 in 3 chance per tick for subtle effect
|
||||
level.addParticle(
|
||||
ParticleTypes.END_ROD,
|
||||
pos.getX() + 0.5 + (random.nextDouble() - 0.5) * 0.3,
|
||||
pos.getY() + 0.5 + (random.nextDouble() - 0.5) * 0.3,
|
||||
pos.getZ() + 0.5 + (random.nextDouble() - 0.5) * 0.3,
|
||||
0,
|
||||
0.02,
|
||||
0 // Slight upward drift
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== BLOCK ENTITY ====================
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new MarkerBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
// ==================== DESTRUCTION ====================
|
||||
|
||||
@Override
|
||||
public void onRemove(
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
BlockState newState,
|
||||
boolean movedByPiston
|
||||
) {
|
||||
if (!state.is(newState.getBlock())) {
|
||||
// Block is being destroyed, remove the cell from registry
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (be instanceof MarkerBlockEntity marker) {
|
||||
// Alert kidnappers if this was a WALL marker (potential breach)
|
||||
if (marker.getMarkerType() == MarkerType.WALL) {
|
||||
alertNearbyKidnappersOfBreach(
|
||||
serverLevel,
|
||||
pos,
|
||||
marker.getCellId()
|
||||
);
|
||||
}
|
||||
marker.deleteCell();
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alert nearby kidnappers when a WALL marker is destroyed.
|
||||
* This indicates a potential breach in the cell that prisoners could escape through.
|
||||
*
|
||||
* @param level The server level
|
||||
* @param breachPos The position of the destroyed wall
|
||||
* @param cellId The cell UUID (may be null)
|
||||
*/
|
||||
private void alertNearbyKidnappersOfBreach(
|
||||
ServerLevel level,
|
||||
BlockPos breachPos,
|
||||
UUID cellId
|
||||
) {
|
||||
if (cellId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find kidnappers within 50 blocks
|
||||
AABB searchBox = new AABB(breachPos).inflate(50, 20, 50);
|
||||
List<EntityKidnapper> kidnappers = level.getEntitiesOfClass(
|
||||
EntityKidnapper.class,
|
||||
searchBox
|
||||
);
|
||||
|
||||
if (!kidnappers.isEmpty()) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BlockMarker] WALL destroyed at {}, alerting {} kidnappers",
|
||||
breachPos.toShortString(),
|
||||
kidnappers.size()
|
||||
);
|
||||
|
||||
for (EntityKidnapper kidnapper : kidnappers) {
|
||||
kidnapper.onCellBreach(breachPos, cellId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
395
src/main/java/com/tiedup/remake/blocks/BlockRopeTrap.java
Normal file
395
src/main/java/com/tiedup/remake/blocks/BlockRopeTrap.java
Normal file
@@ -0,0 +1,395 @@
|
||||
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.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
254
src/main/java/com/tiedup/remake/blocks/BlockTrappedChest.java
Normal file
254
src/main/java/com/tiedup/remake/blocks/BlockTrappedChest.java
Normal file
@@ -0,0 +1,254 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
import com.tiedup.remake.blocks.entity.TrappedChestBlockEntity;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.items.base.*;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.BondageItemLoaderUtility;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
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.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.ChestBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.entity.ChestBlockEntity;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Trapped Chest Block - Chest that traps players when opened.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Extends vanilla ChestBlock for proper chest behavior.
|
||||
* Sneak + right-click to load bondage items.
|
||||
* Normal open triggers the trap if armed.
|
||||
*/
|
||||
public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
|
||||
|
||||
public BlockTrappedChest() {
|
||||
super(BlockBehaviour.Properties.of().strength(2.5f).noOcclusion(), () ->
|
||||
com.tiedup.remake.blocks.entity.ModBlockEntities.TRAPPED_CHEST.get()
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BLOCK ENTITY
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new TrappedChestBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TrappedChestBlockEntity getTrapEntity(
|
||||
BlockGetter level,
|
||||
BlockPos pos
|
||||
) {
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
return be instanceof TrappedChestBlockEntity
|
||||
? (TrappedChestBlockEntity) be
|
||||
: null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// INTERACTION - TRAP TRIGGER
|
||||
// ========================================
|
||||
|
||||
@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);
|
||||
|
||||
// Check if holding a bondage item = load it (don't open chest)
|
||||
if (BondageItemLoaderUtility.isLoadableBondageItem(heldItem)) {
|
||||
// Server-side only
|
||||
if (!level.isClientSide) {
|
||||
TrappedChestBlockEntity chest = getTrapEntity(level, pos);
|
||||
if (
|
||||
chest != null &&
|
||||
BondageItemLoaderUtility.loadItemIntoHolder(
|
||||
chest,
|
||||
heldItem,
|
||||
player
|
||||
)
|
||||
) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
"Item loaded into trap",
|
||||
ChatFormatting.YELLOW
|
||||
);
|
||||
}
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
// Normal open - check for trap trigger first (server-side)
|
||||
if (!level.isClientSide) {
|
||||
TrappedChestBlockEntity chest = getTrapEntity(level, pos);
|
||||
if (chest != null && chest.isArmed()) {
|
||||
IBondageState playerState = KidnappedHelper.getKidnappedState(
|
||||
player
|
||||
);
|
||||
if (playerState != null && !playerState.isTiedUp()) {
|
||||
// Apply bondage
|
||||
playerState.applyBondage(
|
||||
chest.getBind(),
|
||||
chest.getGag(),
|
||||
chest.getBlindfold(),
|
||||
chest.getEarplugs(),
|
||||
chest.getCollar(),
|
||||
chest.getClothes()
|
||||
);
|
||||
|
||||
// Clear the chest trap contents
|
||||
chest.setBind(ItemStack.EMPTY);
|
||||
chest.setGag(ItemStack.EMPTY);
|
||||
chest.setBlindfold(ItemStack.EMPTY);
|
||||
chest.setEarplugs(ItemStack.EMPTY);
|
||||
chest.setCollar(ItemStack.EMPTY);
|
||||
chest.setClothes(ItemStack.EMPTY);
|
||||
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.WARNING,
|
||||
"You fell into a trap!"
|
||||
);
|
||||
|
||||
// FIX: Don't open chest GUI after trap triggers
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal chest behavior (open GUI) - only if trap didn't trigger
|
||||
return super.use(state, level, pos, player, hand, hit);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// DROPS WITH NBT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(
|
||||
BlockState state,
|
||||
LootParams.Builder params
|
||||
) {
|
||||
List<ItemStack> drops = super.getDrops(state, params);
|
||||
|
||||
BlockEntity be = params.getOptionalParameter(
|
||||
LootContextParams.BLOCK_ENTITY
|
||||
);
|
||||
if (be instanceof TrappedChestBlockEntity chest && chest.isArmed()) {
|
||||
// Add trap data to the first drop (the chest itself)
|
||||
if (!drops.isEmpty()) {
|
||||
ItemStack stack = drops.get(0);
|
||||
CompoundTag beTag = new CompoundTag();
|
||||
chest.writeBondageData(beTag);
|
||||
if (!beTag.isEmpty()) {
|
||||
stack.addTagElement("BlockEntityTag", beTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return drops;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TOOLTIP
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void appendHoverText(
|
||||
ItemStack stack,
|
||||
@Nullable BlockGetter level,
|
||||
List<Component> tooltip,
|
||||
TooltipFlag flag
|
||||
) {
|
||||
tooltip.add(
|
||||
Component.translatable("block.tiedup.trapped_chest.desc").withStyle(
|
||||
ChatFormatting.GRAY
|
||||
)
|
||||
);
|
||||
|
||||
CompoundTag nbt = stack.getTag();
|
||||
if (nbt != null && nbt.contains("BlockEntityTag")) {
|
||||
CompoundTag beTag = nbt.getCompound("BlockEntityTag");
|
||||
|
||||
if (
|
||||
beTag.contains("bind") ||
|
||||
beTag.contains("gag") ||
|
||||
beTag.contains("blindfold") ||
|
||||
beTag.contains("earplugs") ||
|
||||
beTag.contains("collar")
|
||||
) {
|
||||
tooltip.add(
|
||||
Component.literal("Armed").withStyle(
|
||||
ChatFormatting.DARK_RED
|
||||
)
|
||||
);
|
||||
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"
|
||||
);
|
||||
} else {
|
||||
tooltip.add(
|
||||
Component.literal("Disarmed").withStyle(
|
||||
ChatFormatting.GREEN
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tooltip.add(
|
||||
Component.literal("Disarmed").withStyle(ChatFormatting.GREEN)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/main/java/com/tiedup/remake/blocks/ICanBeLoaded.java
Normal file
17
src/main/java/com/tiedup/remake/blocks/ICanBeLoaded.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
/**
|
||||
* Marker interface for blocks that can have bondage items loaded into them.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Implemented by:
|
||||
* - BlockRopesTrap - applies items when entity walks on it
|
||||
* - BlockTrappedBed - applies items when player sleeps
|
||||
* - BlockKidnapBomb - passes items to explosion entity
|
||||
*
|
||||
* These blocks have associated BlockEntities that store the bondage items.
|
||||
*/
|
||||
public interface ICanBeLoaded {
|
||||
// Marker interface - no methods required
|
||||
}
|
||||
225
src/main/java/com/tiedup/remake/blocks/ModBlocks.java
Normal file
225
src/main/java/com/tiedup/remake/blocks/ModBlocks.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package com.tiedup.remake.blocks;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.DoubleHighBlockItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.DoorBlock;
|
||||
import net.minecraft.world.level.block.SlabBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.StairBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
/**
|
||||
* Mod Blocks Registration
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Handles registration of all TiedUp blocks using DeferredRegister.
|
||||
*
|
||||
* Blocks:
|
||||
* - Padded block + variants (slab, stairs, pane)
|
||||
* - Rope trap
|
||||
* - Trapped bed
|
||||
* - Cell door
|
||||
* - Kidnap bomb
|
||||
*/
|
||||
public class ModBlocks {
|
||||
|
||||
// DeferredRegister for blocks
|
||||
public static final DeferredRegister<Block> BLOCKS =
|
||||
DeferredRegister.create(ForgeRegistries.BLOCKS, TiedUpMod.MOD_ID);
|
||||
|
||||
// DeferredRegister for block items (linked to ModItems)
|
||||
public static final DeferredRegister<Item> BLOCK_ITEMS =
|
||||
DeferredRegister.create(ForgeRegistries.ITEMS, TiedUpMod.MOD_ID);
|
||||
|
||||
// ========================================
|
||||
// PADDED BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Base padded block properties.
|
||||
* Cloth material, soft, quiet.
|
||||
*/
|
||||
private static BlockBehaviour.Properties paddedProperties() {
|
||||
return BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.WOOL)
|
||||
.strength(0.9f, 45.0f)
|
||||
.sound(SoundType.WOOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Padded Block - Basic soft block.
|
||||
*/
|
||||
public static final RegistryObject<Block> PADDED_BLOCK = registerBlock(
|
||||
"padded_block",
|
||||
() -> new Block(paddedProperties())
|
||||
);
|
||||
|
||||
/**
|
||||
* Padded Slab - Half-height padded block.
|
||||
*/
|
||||
public static final RegistryObject<Block> PADDED_SLAB = registerBlock(
|
||||
"padded_slab",
|
||||
() -> new SlabBlock(paddedProperties())
|
||||
);
|
||||
|
||||
/**
|
||||
* Padded Stairs - Stair variant of padded block.
|
||||
*/
|
||||
public static final RegistryObject<Block> PADDED_STAIRS = registerBlock(
|
||||
"padded_stairs",
|
||||
() ->
|
||||
new StairBlock(
|
||||
() -> PADDED_BLOCK.get().defaultBlockState(),
|
||||
paddedProperties()
|
||||
)
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// TRAP BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Rope Trap - Flat trap that ties up entities that walk on it.
|
||||
* Uses BlockEntity to store bondage items.
|
||||
*/
|
||||
public static final RegistryObject<Block> ROPE_TRAP = registerBlock(
|
||||
"rope_trap",
|
||||
BlockRopeTrap::new
|
||||
);
|
||||
|
||||
/**
|
||||
* Kidnap Bomb - TNT that applies bondage on explosion.
|
||||
* Uses BlockEntity to store bondage items.
|
||||
*/
|
||||
public static final RegistryObject<Block> KIDNAP_BOMB = registerBlock(
|
||||
"kidnap_bomb",
|
||||
BlockKidnapBomb::new
|
||||
);
|
||||
|
||||
/**
|
||||
* Trapped Chest - Chest that traps players when opened.
|
||||
* Uses BlockEntity to store bondage items.
|
||||
*/
|
||||
public static final RegistryObject<Block> TRAPPED_CHEST = registerBlock(
|
||||
"trapped_chest",
|
||||
BlockTrappedChest::new
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// DOOR BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Cell Door - Iron-like door that requires redstone to open.
|
||||
* Cannot be opened by hand.
|
||||
*/
|
||||
public static final RegistryObject<BlockCellDoor> CELL_DOOR =
|
||||
registerDoorBlock("cell_door", BlockCellDoor::new);
|
||||
|
||||
// ========================================
|
||||
// CELL SYSTEM BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Marker Block - Invisible block for cell spawn points.
|
||||
* Stores cell UUID and links to CellRegistry.
|
||||
*/
|
||||
public static final RegistryObject<Block> MARKER = registerBlockNoItem(
|
||||
"marker",
|
||||
BlockMarker::new
|
||||
);
|
||||
|
||||
/**
|
||||
* Iron Bar Door - Lockable door made of iron bars.
|
||||
* Can be locked with keys and unlocked with matching key, cell key, or master key.
|
||||
*/
|
||||
public static final RegistryObject<BlockIronBarDoor> IRON_BAR_DOOR =
|
||||
registerDoorBlock("iron_bar_door", BlockIronBarDoor::new);
|
||||
|
||||
/**
|
||||
* Cell Core - Anchor block for Cell System V2.
|
||||
* Placed into a wall; runs flood-fill to detect the room and register a cell.
|
||||
*/
|
||||
public static final RegistryObject<Block> CELL_CORE = registerBlock(
|
||||
"cell_core",
|
||||
BlockCellCore::new
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// REGISTRATION HELPERS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Register a block and its corresponding BlockItem.
|
||||
*
|
||||
* @param name Block registry name
|
||||
* @param blockSupplier Block supplier
|
||||
* @return RegistryObject for the block
|
||||
*/
|
||||
private static <T extends Block> RegistryObject<T> registerBlock(
|
||||
String name,
|
||||
Supplier<T> blockSupplier
|
||||
) {
|
||||
RegistryObject<T> block = BLOCKS.register(name, blockSupplier);
|
||||
registerBlockItem(name, block);
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a BlockItem for a block.
|
||||
*
|
||||
* @param name Item registry name (same as block)
|
||||
* @param block The block to create an item for
|
||||
*/
|
||||
private static <T extends Block> void registerBlockItem(
|
||||
String name,
|
||||
RegistryObject<T> block
|
||||
) {
|
||||
BLOCK_ITEMS.register(name, () ->
|
||||
new BlockItem(block.get(), new Item.Properties())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a block without an item.
|
||||
* Used for blocks that need special item handling (e.g., trapped bed, doors).
|
||||
*
|
||||
* @param name Block registry name
|
||||
* @param blockSupplier Block supplier
|
||||
* @return RegistryObject for the block
|
||||
*/
|
||||
private static <T extends Block> RegistryObject<T> registerBlockNoItem(
|
||||
String name,
|
||||
Supplier<T> blockSupplier
|
||||
) {
|
||||
return BLOCKS.register(name, blockSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a door block with DoubleHighBlockItem.
|
||||
* Doors are double-height blocks and need special item handling.
|
||||
*
|
||||
* @param name Block registry name
|
||||
* @param blockSupplier Block supplier (must return DoorBlock or subclass)
|
||||
* @return RegistryObject for the block
|
||||
*/
|
||||
private static <T extends DoorBlock> RegistryObject<T> registerDoorBlock(
|
||||
String name,
|
||||
Supplier<T> blockSupplier
|
||||
) {
|
||||
RegistryObject<T> block = BLOCKS.register(name, blockSupplier);
|
||||
BLOCK_ITEMS.register(name, () ->
|
||||
new DoubleHighBlockItem(block.get(), new Item.Properties())
|
||||
);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.items.base.ItemBlindfold;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.items.base.ItemEarplugs;
|
||||
import com.tiedup.remake.items.base.ItemGag;
|
||||
import com.tiedup.remake.items.clothes.GenericClothes;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* Base BlockEntity for blocks that store bondage items.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Stores up to 6 bondage items:
|
||||
* - Bind (ropes, chains, straitjacket, etc.)
|
||||
* - Gag
|
||||
* - Blindfold
|
||||
* - Earplugs
|
||||
* - Collar
|
||||
* - Clothes
|
||||
*
|
||||
* Features:
|
||||
* - Full NBT serialization
|
||||
* - Network synchronization for client rendering
|
||||
* - Item type validation on load
|
||||
*
|
||||
* Based on original TileEntityBondageItemHandler from 1.12.2
|
||||
*/
|
||||
public abstract class BondageItemBlockEntity
|
||||
extends BlockEntity
|
||||
implements IBondageItemHolder
|
||||
{
|
||||
|
||||
// ========================================
|
||||
// STORED ITEMS
|
||||
// ========================================
|
||||
|
||||
private ItemStack bind = ItemStack.EMPTY;
|
||||
private ItemStack gag = ItemStack.EMPTY;
|
||||
private ItemStack blindfold = ItemStack.EMPTY;
|
||||
private ItemStack earplugs = ItemStack.EMPTY;
|
||||
private ItemStack collar = ItemStack.EMPTY;
|
||||
private ItemStack clothes = ItemStack.EMPTY;
|
||||
|
||||
/**
|
||||
* Off-mode prevents network updates.
|
||||
* Used when reading NBT for tooltips without affecting the world.
|
||||
*/
|
||||
private final boolean offMode;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTORS
|
||||
// ========================================
|
||||
|
||||
public BondageItemBlockEntity(
|
||||
BlockEntityType<?> type,
|
||||
BlockPos pos,
|
||||
BlockState state
|
||||
) {
|
||||
this(type, pos, state, false);
|
||||
}
|
||||
|
||||
public BondageItemBlockEntity(
|
||||
BlockEntityType<?> type,
|
||||
BlockPos pos,
|
||||
BlockState state,
|
||||
boolean offMode
|
||||
) {
|
||||
super(type, pos, state);
|
||||
this.offMode = offMode;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BIND
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getBind() {
|
||||
return this.bind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBind(ItemStack bind) {
|
||||
this.bind = bind != null ? bind : ItemStack.EMPTY;
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// GAG
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getGag() {
|
||||
return this.gag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGag(ItemStack gag) {
|
||||
this.gag = gag != null ? gag : ItemStack.EMPTY;
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BLINDFOLD
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getBlindfold() {
|
||||
return this.blindfold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlindfold(ItemStack blindfold) {
|
||||
this.blindfold = blindfold != null ? blindfold : ItemStack.EMPTY;
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EARPLUGS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getEarplugs() {
|
||||
return this.earplugs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEarplugs(ItemStack earplugs) {
|
||||
this.earplugs = earplugs != null ? earplugs : ItemStack.EMPTY;
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// COLLAR
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getCollar() {
|
||||
return this.collar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCollar(ItemStack collar) {
|
||||
this.collar = collar != null ? collar : ItemStack.EMPTY;
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLOTHES
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getClothes() {
|
||||
return this.clothes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClothes(ItemStack clothes) {
|
||||
this.clothes = clothes != null ? clothes : ItemStack.EMPTY;
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isArmed() {
|
||||
return !this.bind.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all stored bondage items.
|
||||
* Called after applying items to a target.
|
||||
*/
|
||||
public void clearAllItems() {
|
||||
this.bind = ItemStack.EMPTY;
|
||||
this.gag = ItemStack.EMPTY;
|
||||
this.blindfold = ItemStack.EMPTY;
|
||||
this.earplugs = ItemStack.EMPTY;
|
||||
this.collar = ItemStack.EMPTY;
|
||||
this.clothes = ItemStack.EMPTY;
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
this.readBondageData(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
this.writeBondageData(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBondageData(CompoundTag tag) {
|
||||
// Read bind with type validation
|
||||
if (tag.contains("bind")) {
|
||||
ItemStack bindStack = ItemStack.of(tag.getCompound("bind"));
|
||||
if (
|
||||
!bindStack.isEmpty() && bindStack.getItem() instanceof ItemBind
|
||||
) {
|
||||
this.bind = bindStack;
|
||||
}
|
||||
}
|
||||
|
||||
// Read gag with type validation
|
||||
if (tag.contains("gag")) {
|
||||
ItemStack gagStack = ItemStack.of(tag.getCompound("gag"));
|
||||
if (!gagStack.isEmpty() && gagStack.getItem() instanceof ItemGag) {
|
||||
this.gag = gagStack;
|
||||
}
|
||||
}
|
||||
|
||||
// Read blindfold with type validation
|
||||
if (tag.contains("blindfold")) {
|
||||
ItemStack blindfoldStack = ItemStack.of(
|
||||
tag.getCompound("blindfold")
|
||||
);
|
||||
if (
|
||||
!blindfoldStack.isEmpty() &&
|
||||
blindfoldStack.getItem() instanceof ItemBlindfold
|
||||
) {
|
||||
this.blindfold = blindfoldStack;
|
||||
}
|
||||
}
|
||||
|
||||
// Read earplugs with type validation
|
||||
if (tag.contains("earplugs")) {
|
||||
ItemStack earplugsStack = ItemStack.of(tag.getCompound("earplugs"));
|
||||
if (
|
||||
!earplugsStack.isEmpty() &&
|
||||
earplugsStack.getItem() instanceof ItemEarplugs
|
||||
) {
|
||||
this.earplugs = earplugsStack;
|
||||
}
|
||||
}
|
||||
|
||||
// Read collar with type validation
|
||||
if (tag.contains("collar")) {
|
||||
ItemStack collarStack = ItemStack.of(tag.getCompound("collar"));
|
||||
if (
|
||||
!collarStack.isEmpty() &&
|
||||
collarStack.getItem() instanceof ItemCollar
|
||||
) {
|
||||
this.collar = collarStack;
|
||||
}
|
||||
}
|
||||
|
||||
// Read clothes with type validation
|
||||
if (tag.contains("clothes")) {
|
||||
ItemStack clothesStack = ItemStack.of(tag.getCompound("clothes"));
|
||||
if (
|
||||
!clothesStack.isEmpty() &&
|
||||
clothesStack.getItem() instanceof GenericClothes
|
||||
) {
|
||||
this.clothes = clothesStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag writeBondageData(CompoundTag tag) {
|
||||
if (!this.bind.isEmpty()) {
|
||||
tag.put("bind", this.bind.save(new CompoundTag()));
|
||||
}
|
||||
if (!this.gag.isEmpty()) {
|
||||
tag.put("gag", this.gag.save(new CompoundTag()));
|
||||
}
|
||||
if (!this.blindfold.isEmpty()) {
|
||||
tag.put("blindfold", this.blindfold.save(new CompoundTag()));
|
||||
}
|
||||
if (!this.earplugs.isEmpty()) {
|
||||
tag.put("earplugs", this.earplugs.save(new CompoundTag()));
|
||||
}
|
||||
if (!this.collar.isEmpty()) {
|
||||
tag.put("collar", this.collar.save(new CompoundTag()));
|
||||
}
|
||||
if (!this.clothes.isEmpty()) {
|
||||
tag.put("clothes", this.clothes.save(new CompoundTag()));
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NETWORK SYNC
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Mark dirty and sync to clients.
|
||||
*/
|
||||
protected void setChangedAndSync() {
|
||||
if (!this.offMode && this.level != null) {
|
||||
this.setChanged();
|
||||
// Notify clients of block update
|
||||
this.level.sendBlockUpdated(
|
||||
this.worldPosition,
|
||||
this.getBlockState(),
|
||||
this.getBlockState(),
|
||||
3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
CompoundTag tag = super.getUpdateTag();
|
||||
this.writeBondageData(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdateTag(CompoundTag tag) {
|
||||
if (!this.offMode) {
|
||||
this.readBondageData(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import com.tiedup.remake.cells.*;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.client.model.data.ModelData;
|
||||
import net.minecraftforge.client.model.data.ModelProperty;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Block entity for the Cell Core block.
|
||||
*
|
||||
* Stores the cell ID link, spawn/delivery points, disguise block,
|
||||
* and which face of the Core points into the cell interior.
|
||||
*/
|
||||
public class CellCoreBlockEntity extends BlockEntity {
|
||||
|
||||
/** Shared ModelProperty for disguise — defined here (not in client-only CellCoreBakedModel) to avoid server classloading issues. */
|
||||
public static final ModelProperty<BlockState> DISGUISE_PROPERTY =
|
||||
new ModelProperty<>();
|
||||
|
||||
@Nullable
|
||||
private UUID cellId;
|
||||
|
||||
@Nullable
|
||||
private BlockPos spawnPoint;
|
||||
|
||||
@Nullable
|
||||
private BlockPos deliveryPoint;
|
||||
|
||||
@Nullable
|
||||
private BlockState disguiseState;
|
||||
|
||||
@Nullable
|
||||
private Direction interiorFace;
|
||||
|
||||
@Nullable
|
||||
private List<BlockPos> pathWaypoints;
|
||||
|
||||
/** Transient: pathWaypoints set by MarkerBlockEntity during V1→V2 conversion */
|
||||
@Nullable
|
||||
private transient List<BlockPos> pendingPathWaypoints;
|
||||
|
||||
public CellCoreBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.CELL_CORE.get(), pos, state);
|
||||
}
|
||||
|
||||
// ==================== LIFECYCLE ====================
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
super.onLoad();
|
||||
|
||||
if (!(level instanceof ServerLevel serverLevel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a cellId but the cell isn't in CellRegistryV2,
|
||||
// this is a structure-placed Core that needs flood-fill registration.
|
||||
if (cellId != null) {
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
|
||||
if (
|
||||
registry.getCell(cellId) == null &&
|
||||
registry.getCellAtCore(worldPosition) == null
|
||||
) {
|
||||
// Structure-placed Core: run flood-fill and create cell
|
||||
FloodFillResult result = FloodFillAlgorithm.tryFill(
|
||||
serverLevel,
|
||||
worldPosition
|
||||
);
|
||||
|
||||
CellDataV2 newCell;
|
||||
if (result.isSuccess()) {
|
||||
newCell = registry.createCell(worldPosition, result, null);
|
||||
if (result.getInteriorFace() != null) {
|
||||
this.interiorFace = result.getInteriorFace();
|
||||
}
|
||||
} else {
|
||||
// Flood-fill failed (e.g. chunk not fully loaded) — create minimal cell
|
||||
newCell = new CellDataV2(UUID.randomUUID(), worldPosition);
|
||||
registry.registerExistingCell(newCell);
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[CellCoreBlockEntity] Flood-fill failed at {}: {}. Created minimal cell.",
|
||||
worldPosition.toShortString(),
|
||||
result.getErrorKey()
|
||||
);
|
||||
}
|
||||
|
||||
// Update our cellId to match the new cell
|
||||
this.cellId = newCell.getId();
|
||||
|
||||
// Transfer spawn/delivery to cell data
|
||||
newCell.setSpawnPoint(this.spawnPoint);
|
||||
newCell.setDeliveryPoint(this.deliveryPoint);
|
||||
|
||||
// Transfer pathWaypoints: persistent field (from NBT) or V1 migration pending
|
||||
List<BlockPos> waypointsToTransfer = null;
|
||||
if (
|
||||
this.pathWaypoints != null && !this.pathWaypoints.isEmpty()
|
||||
) {
|
||||
waypointsToTransfer = this.pathWaypoints;
|
||||
} else if (
|
||||
this.pendingPathWaypoints != null &&
|
||||
!this.pendingPathWaypoints.isEmpty()
|
||||
) {
|
||||
waypointsToTransfer = this.pendingPathWaypoints;
|
||||
this.pendingPathWaypoints = null;
|
||||
}
|
||||
if (waypointsToTransfer != null) {
|
||||
newCell.setPathWaypoints(waypointsToTransfer);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[CellCoreBlockEntity] Transferred {} pathWaypoints to cell {}",
|
||||
waypointsToTransfer.size(),
|
||||
cellId.toString().substring(0, 8)
|
||||
);
|
||||
}
|
||||
|
||||
// Mark as camp-owned and link to nearest camp
|
||||
newCell.setOwnerType(CellOwnerType.CAMP);
|
||||
|
||||
CampOwnership ownership = CampOwnership.get(serverLevel);
|
||||
CampOwnership.CampData nearestCamp =
|
||||
ownership.findNearestAliveCamp(worldPosition, 40);
|
||||
|
||||
if (nearestCamp != null) {
|
||||
newCell.setOwnerId(nearestCamp.getCampId());
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[CellCoreBlockEntity] Created cell {} linked to camp {} at {}",
|
||||
cellId.toString().substring(0, 8),
|
||||
nearestCamp.getCampId().toString().substring(0, 8),
|
||||
worldPosition.toShortString()
|
||||
);
|
||||
} else {
|
||||
// No camp yet: generate deterministic camp ID (same algo as MarkerBlockEntity)
|
||||
UUID structureCampId = generateStructureCampId(
|
||||
worldPosition
|
||||
);
|
||||
newCell.setOwnerId(structureCampId);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[CellCoreBlockEntity] Created cell {} with structure camp ID {} at {}",
|
||||
cellId.toString().substring(0, 8),
|
||||
structureCampId.toString().substring(0, 8),
|
||||
worldPosition.toShortString()
|
||||
);
|
||||
}
|
||||
|
||||
registry.updateCampIndex(newCell, null);
|
||||
registry.setDirty();
|
||||
setChangedAndSync();
|
||||
} else {
|
||||
// Existing cell: sync pathWaypoints from CellDataV2 → Core BE
|
||||
// (so re-saving structures picks them up in the new offset format)
|
||||
CellDataV2 existingCell = registry.getCell(cellId);
|
||||
if (existingCell == null) {
|
||||
existingCell = registry.getCellAtCore(worldPosition);
|
||||
}
|
||||
if (existingCell != null) {
|
||||
if (
|
||||
this.pathWaypoints == null ||
|
||||
this.pathWaypoints.isEmpty()
|
||||
) {
|
||||
List<BlockPos> cellWaypoints =
|
||||
existingCell.getPathWaypoints();
|
||||
if (!cellWaypoints.isEmpty()) {
|
||||
this.pathWaypoints = new ArrayList<>(cellWaypoints);
|
||||
setChanged(); // persist without network sync
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a deterministic camp ID based on structure position.
|
||||
* Uses the same 128-block grid algorithm as MarkerBlockEntity.
|
||||
*/
|
||||
private static UUID generateStructureCampId(BlockPos pos) {
|
||||
int gridX = (pos.getX() / 128) * 128;
|
||||
int gridZ = (pos.getZ() / 128) * 128;
|
||||
long mostSigBits = ((long) gridX << 32) | (gridZ & 0xFFFFFFFFL);
|
||||
long leastSigBits = 0x8000000000000000L | (gridX ^ gridZ);
|
||||
return new UUID(mostSigBits, leastSigBits);
|
||||
}
|
||||
|
||||
// ==================== GETTERS/SETTERS ====================
|
||||
|
||||
@Nullable
|
||||
public UUID getCellId() {
|
||||
return cellId;
|
||||
}
|
||||
|
||||
public void setCellId(@Nullable UUID cellId) {
|
||||
this.cellId = cellId;
|
||||
setChangedAndSync();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockPos getSpawnPoint() {
|
||||
return spawnPoint;
|
||||
}
|
||||
|
||||
public void setSpawnPoint(@Nullable BlockPos spawnPoint) {
|
||||
this.spawnPoint = spawnPoint;
|
||||
setChangedAndSync();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockPos getDeliveryPoint() {
|
||||
return deliveryPoint;
|
||||
}
|
||||
|
||||
public void setDeliveryPoint(@Nullable BlockPos deliveryPoint) {
|
||||
this.deliveryPoint = deliveryPoint;
|
||||
setChangedAndSync();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockState getDisguiseState() {
|
||||
return disguiseState;
|
||||
}
|
||||
|
||||
public void setDisguiseState(@Nullable BlockState disguiseState) {
|
||||
this.disguiseState = disguiseState;
|
||||
setChangedAndSync();
|
||||
requestModelDataUpdate();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Direction getInteriorFace() {
|
||||
return interiorFace;
|
||||
}
|
||||
|
||||
public void setInteriorFace(@Nullable Direction interiorFace) {
|
||||
this.interiorFace = interiorFace;
|
||||
setChangedAndSync();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<BlockPos> getPathWaypoints() {
|
||||
return pathWaypoints;
|
||||
}
|
||||
|
||||
public void setPathWaypoints(@Nullable List<BlockPos> pathWaypoints) {
|
||||
this.pathWaypoints = pathWaypoints;
|
||||
setChangedAndSync();
|
||||
}
|
||||
|
||||
public void setPendingPathWaypoints(List<BlockPos> waypoints) {
|
||||
this.pendingPathWaypoints = waypoints;
|
||||
this.pathWaypoints = waypoints; // also persist
|
||||
}
|
||||
|
||||
// ==================== MODEL DATA (Camouflage) ====================
|
||||
|
||||
@Override
|
||||
public @NotNull ModelData getModelData() {
|
||||
BlockState disguise = resolveDisguise();
|
||||
if (disguise != null) {
|
||||
return ModelData.builder()
|
||||
.with(DISGUISE_PROPERTY, disguise)
|
||||
.build();
|
||||
}
|
||||
return ModelData.EMPTY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockState resolveDisguise() {
|
||||
// Explicit disguise takes priority (preserves full BlockState including slab type, etc.)
|
||||
if (disguiseState != null) {
|
||||
return disguiseState;
|
||||
}
|
||||
|
||||
// Auto-detect most common solid neighbor
|
||||
if (level != null) {
|
||||
return detectMostCommonNeighbor();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BlockState detectMostCommonNeighbor() {
|
||||
if (level == null) return null;
|
||||
|
||||
// Track full BlockState (preserves slab type, stair facing, etc.)
|
||||
Map<BlockState, Integer> counts = new HashMap<>();
|
||||
for (Direction dir : Direction.values()) {
|
||||
BlockPos neighborPos = worldPosition.relative(dir);
|
||||
BlockState neighbor = level.getBlockState(neighborPos);
|
||||
if (
|
||||
neighbor.getBlock() instanceof
|
||||
com.tiedup.remake.blocks.BlockCellCore
|
||||
) continue;
|
||||
if (
|
||||
neighbor.isSolidRender(level, neighborPos) ||
|
||||
neighbor.getBlock() instanceof
|
||||
net.minecraft.world.level.block.SlabBlock
|
||||
) {
|
||||
counts.merge(neighbor, 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
|
||||
if (counts.isEmpty()) return null;
|
||||
|
||||
BlockState mostCommon = null;
|
||||
int maxCount = 0;
|
||||
for (Map.Entry<BlockState, Integer> entry : counts.entrySet()) {
|
||||
if (entry.getValue() > maxCount) {
|
||||
maxCount = entry.getValue();
|
||||
mostCommon = entry.getKey();
|
||||
}
|
||||
}
|
||||
return mostCommon;
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
if (cellId != null) tag.putUUID("cellId", cellId);
|
||||
// Save positions as relative offsets from core position (survives structure placement + rotation)
|
||||
if (spawnPoint != null) {
|
||||
tag.put(
|
||||
"spawnOffset",
|
||||
NbtUtils.writeBlockPos(toOffset(spawnPoint, worldPosition))
|
||||
);
|
||||
}
|
||||
if (deliveryPoint != null) {
|
||||
tag.put(
|
||||
"deliveryOffset",
|
||||
NbtUtils.writeBlockPos(toOffset(deliveryPoint, worldPosition))
|
||||
);
|
||||
}
|
||||
if (pathWaypoints != null && !pathWaypoints.isEmpty()) {
|
||||
ListTag list = new ListTag();
|
||||
for (BlockPos wp : pathWaypoints) {
|
||||
list.add(NbtUtils.writeBlockPos(toOffset(wp, worldPosition)));
|
||||
}
|
||||
tag.put("pathWaypointOffsets", list);
|
||||
}
|
||||
if (disguiseState != null) tag.put(
|
||||
"disguiseState",
|
||||
NbtUtils.writeBlockState(disguiseState)
|
||||
);
|
||||
if (interiorFace != null) tag.putString(
|
||||
"interiorFace",
|
||||
interiorFace.getSerializedName()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
cellId = tag.contains("cellId") ? tag.getUUID("cellId") : null;
|
||||
|
||||
// Spawn point: new relative offset format, then old absolute fallback
|
||||
if (tag.contains("spawnOffset")) {
|
||||
spawnPoint = fromOffset(
|
||||
NbtUtils.readBlockPos(tag.getCompound("spawnOffset")),
|
||||
worldPosition
|
||||
);
|
||||
} else if (tag.contains("spawnPoint")) {
|
||||
spawnPoint = NbtUtils.readBlockPos(tag.getCompound("spawnPoint"));
|
||||
} else {
|
||||
spawnPoint = null;
|
||||
}
|
||||
|
||||
// Delivery point: new relative offset format, then old absolute fallback
|
||||
if (tag.contains("deliveryOffset")) {
|
||||
deliveryPoint = fromOffset(
|
||||
NbtUtils.readBlockPos(tag.getCompound("deliveryOffset")),
|
||||
worldPosition
|
||||
);
|
||||
} else if (tag.contains("deliveryPoint")) {
|
||||
deliveryPoint = NbtUtils.readBlockPos(
|
||||
tag.getCompound("deliveryPoint")
|
||||
);
|
||||
} else {
|
||||
deliveryPoint = null;
|
||||
}
|
||||
|
||||
// Path waypoints (relative offsets)
|
||||
if (tag.contains("pathWaypointOffsets")) {
|
||||
ListTag list = tag.getList("pathWaypointOffsets", Tag.TAG_COMPOUND);
|
||||
pathWaypoints = new ArrayList<>();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
pathWaypoints.add(
|
||||
fromOffset(
|
||||
NbtUtils.readBlockPos(list.getCompound(i)),
|
||||
worldPosition
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pathWaypoints = null;
|
||||
}
|
||||
|
||||
// Retrocompat: old saves stored "disguiseBlock" as ResourceLocation string
|
||||
if (tag.contains("disguiseState")) {
|
||||
disguiseState = NbtUtils.readBlockState(
|
||||
BuiltInRegistries.BLOCK.asLookup(),
|
||||
tag.getCompound("disguiseState")
|
||||
);
|
||||
} else if (tag.contains("disguiseBlock")) {
|
||||
// V1 compat: convert old ResourceLocation to default BlockState
|
||||
ResourceLocation oldId = ResourceLocation.tryParse(
|
||||
tag.getString("disguiseBlock")
|
||||
);
|
||||
if (oldId != null) {
|
||||
Block block = BuiltInRegistries.BLOCK.get(oldId);
|
||||
disguiseState = (block !=
|
||||
net.minecraft.world.level.block.Blocks.AIR)
|
||||
? block.defaultBlockState()
|
||||
: null;
|
||||
}
|
||||
} else {
|
||||
disguiseState = null;
|
||||
}
|
||||
interiorFace = tag.contains("interiorFace")
|
||||
? Direction.byName(tag.getString("interiorFace"))
|
||||
: null;
|
||||
}
|
||||
|
||||
// ==================== OFFSET HELPERS ====================
|
||||
|
||||
/** Convert absolute position to relative offset from origin. */
|
||||
private static BlockPos toOffset(BlockPos absolute, BlockPos origin) {
|
||||
return new BlockPos(
|
||||
absolute.getX() - origin.getX(),
|
||||
absolute.getY() - origin.getY(),
|
||||
absolute.getZ() - origin.getZ()
|
||||
);
|
||||
}
|
||||
|
||||
/** Convert relative offset back to absolute position. */
|
||||
private static BlockPos fromOffset(BlockPos offset, BlockPos origin) {
|
||||
return origin.offset(offset.getX(), offset.getY(), offset.getZ());
|
||||
}
|
||||
|
||||
// ==================== NETWORK SYNC ====================
|
||||
|
||||
private void setChangedAndSync() {
|
||||
if (level != null) {
|
||||
setChanged();
|
||||
level.sendBlockUpdated(
|
||||
worldPosition,
|
||||
getBlockState(),
|
||||
getBlockState(),
|
||||
3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
CompoundTag tag = super.getUpdateTag();
|
||||
saveAdditional(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataPacket(
|
||||
Connection net,
|
||||
ClientboundBlockEntityDataPacket pkt
|
||||
) {
|
||||
CompoundTag tag = pkt.getTag();
|
||||
if (tag != null) {
|
||||
handleUpdateTag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdateTag(CompoundTag tag) {
|
||||
load(tag);
|
||||
requestModelDataUpdate();
|
||||
// Force chunk section re-render to pick up new model data immediately.
|
||||
// Enqueue to main thread since handleUpdateTag may run on network thread.
|
||||
if (level != null && level.isClientSide()) {
|
||||
net.minecraft.client.Minecraft.getInstance().tell(
|
||||
this::markRenderDirty
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the chunk section containing this block entity for re-rendering.
|
||||
* Must be called on the client main thread only.
|
||||
*/
|
||||
private void markRenderDirty() {
|
||||
if (level == null || !level.isClientSide()) return;
|
||||
net.minecraft.client.Minecraft mc =
|
||||
net.minecraft.client.Minecraft.getInstance();
|
||||
if (mc.levelRenderer != null) {
|
||||
mc.levelRenderer.blockChanged(
|
||||
level,
|
||||
worldPosition,
|
||||
getBlockState(),
|
||||
getBlockState(),
|
||||
8
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Interface for BlockEntities that store bondage items.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Defines the contract for storing and retrieving bondage items:
|
||||
* - Bind (ropes, chains, etc.)
|
||||
* - Gag
|
||||
* - Blindfold
|
||||
* - Earplugs
|
||||
* - Collar
|
||||
* - Clothes
|
||||
*
|
||||
* Based on original ITileEntityBondageItemHolder from 1.12.2
|
||||
*/
|
||||
public interface IBondageItemHolder {
|
||||
// ========================================
|
||||
// BIND
|
||||
// ========================================
|
||||
|
||||
ItemStack getBind();
|
||||
void setBind(ItemStack bind);
|
||||
|
||||
// ========================================
|
||||
// GAG
|
||||
// ========================================
|
||||
|
||||
ItemStack getGag();
|
||||
void setGag(ItemStack gag);
|
||||
|
||||
// ========================================
|
||||
// BLINDFOLD
|
||||
// ========================================
|
||||
|
||||
ItemStack getBlindfold();
|
||||
void setBlindfold(ItemStack blindfold);
|
||||
|
||||
// ========================================
|
||||
// EARPLUGS
|
||||
// ========================================
|
||||
|
||||
ItemStack getEarplugs();
|
||||
void setEarplugs(ItemStack earplugs);
|
||||
|
||||
// ========================================
|
||||
// COLLAR
|
||||
// ========================================
|
||||
|
||||
ItemStack getCollar();
|
||||
void setCollar(ItemStack collar);
|
||||
|
||||
// ========================================
|
||||
// CLOTHES
|
||||
// ========================================
|
||||
|
||||
ItemStack getClothes();
|
||||
void setClothes(ItemStack clothes);
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Read bondage items from NBT.
|
||||
* @param tag The compound tag to read from
|
||||
*/
|
||||
void readBondageData(CompoundTag tag);
|
||||
|
||||
/**
|
||||
* Write bondage items to NBT.
|
||||
* @param tag The compound tag to write to
|
||||
* @return The modified compound tag
|
||||
*/
|
||||
CompoundTag writeBondageData(CompoundTag tag);
|
||||
|
||||
// ========================================
|
||||
// STATE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if this holder has any bondage items loaded.
|
||||
* Typically checks if bind is present.
|
||||
* @return true if armed/loaded
|
||||
*/
|
||||
boolean isArmed();
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import com.tiedup.remake.items.base.ILockable;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* BlockEntity for iron bar door blocks.
|
||||
*
|
||||
* Phase: Kidnapper Revamp - Cell System
|
||||
*
|
||||
* Stores the lock state and key UUID for the door.
|
||||
* Implements a block-based version of the ILockable pattern.
|
||||
*/
|
||||
public class IronBarDoorBlockEntity extends BlockEntity {
|
||||
|
||||
@Nullable
|
||||
private UUID lockedByKeyUUID;
|
||||
|
||||
private boolean locked = false;
|
||||
|
||||
public IronBarDoorBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.IRON_BAR_DOOR.get(), pos, state);
|
||||
}
|
||||
|
||||
// ==================== LOCK STATE ====================
|
||||
|
||||
/**
|
||||
* Check if this door is locked.
|
||||
*/
|
||||
public boolean isLocked() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the locked state.
|
||||
*
|
||||
* @param locked true to lock, false to unlock
|
||||
*/
|
||||
public void setLocked(boolean locked) {
|
||||
this.locked = locked;
|
||||
if (!locked) {
|
||||
this.lockedByKeyUUID = null;
|
||||
}
|
||||
setChangedAndSync();
|
||||
}
|
||||
|
||||
// ==================== KEY UUID ====================
|
||||
|
||||
/**
|
||||
* Get the UUID of the key that locked this door.
|
||||
*/
|
||||
@Nullable
|
||||
public UUID getLockedByKeyUUID() {
|
||||
return lockedByKeyUUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock this door with a specific key.
|
||||
*
|
||||
* @param keyUUID The key UUID, or null to unlock
|
||||
*/
|
||||
public void setLockedByKeyUUID(@Nullable UUID keyUUID) {
|
||||
this.lockedByKeyUUID = keyUUID;
|
||||
this.locked = keyUUID != null;
|
||||
setChangedAndSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a key matches this door's lock.
|
||||
*
|
||||
* @param keyUUID The key UUID to test
|
||||
* @return true if the key matches
|
||||
*/
|
||||
public boolean matchesKey(UUID keyUUID) {
|
||||
if (keyUUID == null) return false;
|
||||
return lockedByKeyUUID != null && lockedByKeyUUID.equals(keyUUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this door can be unlocked by the given key.
|
||||
* Matches either the specific key or any master key.
|
||||
*
|
||||
* @param keyUUID The key UUID to test
|
||||
* @param isMasterKey Whether the key is a master key
|
||||
* @return true if the door can be unlocked
|
||||
*/
|
||||
public boolean canUnlockWith(UUID keyUUID, boolean isMasterKey) {
|
||||
if (!locked) return true;
|
||||
if (isMasterKey) return true;
|
||||
return matchesKey(keyUUID);
|
||||
}
|
||||
|
||||
// ==================== NBT SERIALIZATION ====================
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
|
||||
this.locked = tag.getBoolean(ILockable.NBT_LOCKED);
|
||||
|
||||
if (tag.contains(ILockable.NBT_LOCKED_BY_KEY_UUID)) {
|
||||
this.lockedByKeyUUID = tag.getUUID(
|
||||
ILockable.NBT_LOCKED_BY_KEY_UUID
|
||||
);
|
||||
} else {
|
||||
this.lockedByKeyUUID = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
|
||||
tag.putBoolean(ILockable.NBT_LOCKED, locked);
|
||||
|
||||
if (lockedByKeyUUID != null) {
|
||||
tag.putUUID(ILockable.NBT_LOCKED_BY_KEY_UUID, lockedByKeyUUID);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== NETWORK SYNC ====================
|
||||
|
||||
protected void setChangedAndSync() {
|
||||
if (level != null) {
|
||||
setChanged();
|
||||
level.sendBlockUpdated(
|
||||
worldPosition,
|
||||
getBlockState(),
|
||||
getBlockState(),
|
||||
3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
CompoundTag tag = super.getUpdateTag();
|
||||
tag.putBoolean(ILockable.NBT_LOCKED, locked);
|
||||
if (lockedByKeyUUID != null) {
|
||||
tag.putUUID(ILockable.NBT_LOCKED_BY_KEY_UUID, lockedByKeyUUID);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdateTag(CompoundTag tag) {
|
||||
this.locked = tag.getBoolean(ILockable.NBT_LOCKED);
|
||||
if (tag.contains(ILockable.NBT_LOCKED_BY_KEY_UUID)) {
|
||||
this.lockedByKeyUUID = tag.getUUID(
|
||||
ILockable.NBT_LOCKED_BY_KEY_UUID
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* BlockEntity for kidnap bomb blocks.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Stores bondage items that will be applied when the bomb explodes.
|
||||
* Simple extension of BondageItemBlockEntity.
|
||||
*
|
||||
* Based on original TileEntityKidnapBomb from 1.12.2
|
||||
*/
|
||||
public class KidnapBombBlockEntity extends BondageItemBlockEntity {
|
||||
|
||||
public KidnapBombBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.KIDNAP_BOMB.get(), pos, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with off-mode for tooltip reading.
|
||||
*/
|
||||
public KidnapBombBlockEntity(
|
||||
BlockPos pos,
|
||||
BlockState state,
|
||||
boolean offMode
|
||||
) {
|
||||
super(ModBlockEntities.KIDNAP_BOMB.get(), pos, state, offMode);
|
||||
}
|
||||
}
|
||||
1146
src/main/java/com/tiedup/remake/blocks/entity/MarkerBlockEntity.java
Normal file
1146
src/main/java/com/tiedup/remake/blocks/entity/MarkerBlockEntity.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import com.tiedup.remake.blocks.ModBlocks;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
/**
|
||||
* Mod Block Entities Registration
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Handles registration of all TiedUp block entities using DeferredRegister.
|
||||
*/
|
||||
public class ModBlockEntities {
|
||||
|
||||
// DeferredRegister for block entity types
|
||||
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES =
|
||||
DeferredRegister.create(
|
||||
ForgeRegistries.BLOCK_ENTITY_TYPES,
|
||||
TiedUpMod.MOD_ID
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// TRAP BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Trap block entity - stores bondage items for rope trap.
|
||||
*/
|
||||
public static final RegistryObject<BlockEntityType<TrapBlockEntity>> TRAP =
|
||||
BLOCK_ENTITIES.register("trap", () ->
|
||||
BlockEntityType.Builder.of(
|
||||
TrapBlockEntity::new,
|
||||
ModBlocks.ROPE_TRAP.get()
|
||||
).build(null)
|
||||
);
|
||||
|
||||
// LOW FIX: Removed BED BLOCK ENTITIES section - feature not implemented
|
||||
|
||||
// ========================================
|
||||
// BOMB BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Kidnap bomb block entity - stores bondage items for explosion effect.
|
||||
*/
|
||||
public static final RegistryObject<
|
||||
BlockEntityType<KidnapBombBlockEntity>
|
||||
> KIDNAP_BOMB = BLOCK_ENTITIES.register("kidnap_bomb", () ->
|
||||
BlockEntityType.Builder.of(
|
||||
KidnapBombBlockEntity::new,
|
||||
ModBlocks.KIDNAP_BOMB.get()
|
||||
).build(null)
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// CHEST BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Trapped chest block entity - stores bondage items for when player opens it.
|
||||
*/
|
||||
public static final RegistryObject<
|
||||
BlockEntityType<TrappedChestBlockEntity>
|
||||
> TRAPPED_CHEST = BLOCK_ENTITIES.register("trapped_chest", () ->
|
||||
BlockEntityType.Builder.of(
|
||||
TrappedChestBlockEntity::new,
|
||||
ModBlocks.TRAPPED_CHEST.get()
|
||||
).build(null)
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// CELL SYSTEM BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Marker block entity - stores cell UUID for cell system.
|
||||
*/
|
||||
public static final RegistryObject<
|
||||
BlockEntityType<MarkerBlockEntity>
|
||||
> MARKER = BLOCK_ENTITIES.register("marker", () ->
|
||||
BlockEntityType.Builder.of(
|
||||
MarkerBlockEntity::new,
|
||||
ModBlocks.MARKER.get()
|
||||
).build(null)
|
||||
);
|
||||
|
||||
/**
|
||||
* Iron bar door block entity - stores lock state and key UUID.
|
||||
*/
|
||||
public static final RegistryObject<
|
||||
BlockEntityType<IronBarDoorBlockEntity>
|
||||
> IRON_BAR_DOOR = BLOCK_ENTITIES.register("iron_bar_door", () ->
|
||||
BlockEntityType.Builder.of(
|
||||
IronBarDoorBlockEntity::new,
|
||||
ModBlocks.IRON_BAR_DOOR.get()
|
||||
).build(null)
|
||||
);
|
||||
|
||||
/**
|
||||
* Cell Core block entity - stores cell ID, spawn/delivery points, and disguise.
|
||||
*/
|
||||
public static final RegistryObject<
|
||||
BlockEntityType<CellCoreBlockEntity>
|
||||
> CELL_CORE = BLOCK_ENTITIES.register("cell_core", () ->
|
||||
BlockEntityType.Builder.of(
|
||||
CellCoreBlockEntity::new,
|
||||
ModBlocks.CELL_CORE.get()
|
||||
).build(null)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* BlockEntity for rope trap blocks.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Stores bondage items that will be applied when an entity walks on the trap.
|
||||
* Simple extension of BondageItemBlockEntity.
|
||||
*
|
||||
* Based on original TileEntityTrap from 1.12.2
|
||||
*/
|
||||
public class TrapBlockEntity extends BondageItemBlockEntity {
|
||||
|
||||
public TrapBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.TRAP.get(), pos, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with off-mode for tooltip reading.
|
||||
*/
|
||||
public TrapBlockEntity(BlockPos pos, BlockState state, boolean offMode) {
|
||||
super(ModBlockEntities.TRAP.get(), pos, state, offMode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package com.tiedup.remake.blocks.entity;
|
||||
|
||||
import com.tiedup.remake.items.base.*;
|
||||
import com.tiedup.remake.items.clothes.GenericClothes;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.ChestBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* BlockEntity for trapped chest blocks.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Extends ChestBlockEntity for proper chest behavior,
|
||||
* but also stores bondage items for the trap.
|
||||
*/
|
||||
public class TrappedChestBlockEntity
|
||||
extends ChestBlockEntity
|
||||
implements IBondageItemHolder
|
||||
{
|
||||
|
||||
// Bondage item storage (separate from chest inventory)
|
||||
private ItemStack bind = ItemStack.EMPTY;
|
||||
private ItemStack gag = ItemStack.EMPTY;
|
||||
private ItemStack blindfold = ItemStack.EMPTY;
|
||||
private ItemStack earplugs = ItemStack.EMPTY;
|
||||
private ItemStack collar = ItemStack.EMPTY;
|
||||
private ItemStack clothes = ItemStack.EMPTY;
|
||||
|
||||
public TrappedChestBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.TRAPPED_CHEST.get(), pos, state);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BONDAGE ITEM HOLDER IMPLEMENTATION
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getBind() {
|
||||
return bind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBind(ItemStack stack) {
|
||||
if (stack.isEmpty() || stack.getItem() instanceof ItemBind) {
|
||||
this.bind = stack;
|
||||
setChangedAndSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getGag() {
|
||||
return gag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGag(ItemStack stack) {
|
||||
if (stack.isEmpty() || stack.getItem() instanceof ItemGag) {
|
||||
this.gag = stack;
|
||||
setChangedAndSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getBlindfold() {
|
||||
return blindfold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlindfold(ItemStack stack) {
|
||||
if (stack.isEmpty() || stack.getItem() instanceof ItemBlindfold) {
|
||||
this.blindfold = stack;
|
||||
setChangedAndSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getEarplugs() {
|
||||
return earplugs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEarplugs(ItemStack stack) {
|
||||
if (stack.isEmpty() || stack.getItem() instanceof ItemEarplugs) {
|
||||
this.earplugs = stack;
|
||||
setChangedAndSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getCollar() {
|
||||
return collar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCollar(ItemStack stack) {
|
||||
if (stack.isEmpty() || stack.getItem() instanceof ItemCollar) {
|
||||
this.collar = stack;
|
||||
setChangedAndSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getClothes() {
|
||||
return clothes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClothes(ItemStack stack) {
|
||||
if (stack.isEmpty() || stack.getItem() instanceof GenericClothes) {
|
||||
this.clothes = stack;
|
||||
setChangedAndSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isArmed() {
|
||||
return (
|
||||
!bind.isEmpty() ||
|
||||
!gag.isEmpty() ||
|
||||
!blindfold.isEmpty() ||
|
||||
!earplugs.isEmpty() ||
|
||||
!collar.isEmpty() ||
|
||||
!clothes.isEmpty()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBondageData(CompoundTag tag) {
|
||||
if (tag.contains("bind")) bind = ItemStack.of(tag.getCompound("bind"));
|
||||
if (tag.contains("gag")) gag = ItemStack.of(tag.getCompound("gag"));
|
||||
if (tag.contains("blindfold")) blindfold = ItemStack.of(
|
||||
tag.getCompound("blindfold")
|
||||
);
|
||||
if (tag.contains("earplugs")) earplugs = ItemStack.of(
|
||||
tag.getCompound("earplugs")
|
||||
);
|
||||
if (tag.contains("collar")) collar = ItemStack.of(
|
||||
tag.getCompound("collar")
|
||||
);
|
||||
if (tag.contains("clothes")) clothes = ItemStack.of(
|
||||
tag.getCompound("clothes")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag writeBondageData(CompoundTag tag) {
|
||||
if (!bind.isEmpty()) tag.put("bind", bind.save(new CompoundTag()));
|
||||
if (!gag.isEmpty()) tag.put("gag", gag.save(new CompoundTag()));
|
||||
if (!blindfold.isEmpty()) tag.put(
|
||||
"blindfold",
|
||||
blindfold.save(new CompoundTag())
|
||||
);
|
||||
if (!earplugs.isEmpty()) tag.put(
|
||||
"earplugs",
|
||||
earplugs.save(new CompoundTag())
|
||||
);
|
||||
if (!collar.isEmpty()) tag.put(
|
||||
"collar",
|
||||
collar.save(new CompoundTag())
|
||||
);
|
||||
if (!clothes.isEmpty()) tag.put(
|
||||
"clothes",
|
||||
clothes.save(new CompoundTag())
|
||||
);
|
||||
return tag;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
readBondageData(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
writeBondageData(tag);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NETWORK SYNC
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Mark dirty and sync to clients.
|
||||
* Ensures bondage trap state is visible to all players.
|
||||
*/
|
||||
protected void setChangedAndSync() {
|
||||
if (this.level != null) {
|
||||
this.setChanged();
|
||||
// Notify clients of block update
|
||||
this.level.sendBlockUpdated(
|
||||
this.worldPosition,
|
||||
this.getBlockState(),
|
||||
this.getBlockState(),
|
||||
3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
CompoundTag tag = super.getUpdateTag();
|
||||
writeBondageData(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdateTag(CompoundTag tag) {
|
||||
super.handleUpdateTag(tag);
|
||||
readBondageData(tag);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user