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:
NotEvil
2026-04-12 00:51:22 +02:00
parent 2e7a1d403b
commit f6466360b6
1947 changed files with 238025 additions and 1 deletions

View File

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

View File

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

View File

@@ -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();
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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