package com.tiedup.remake.blocks.entity; import com.tiedup.remake.items.clothes.GenericClothes; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.BindModeHelper; import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.v2.bondage.component.ComponentType; import com.tiedup.remake.v2.bondage.component.GaggingComponent; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry; 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. * * * 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 (V2 ARMS-region item) if (tag.contains("bind")) { ItemStack bindStack = ItemStack.of(tag.getCompound("bind")); if (!bindStack.isEmpty() && BindModeHelper.isBindItem(bindStack)) { this.bind = bindStack; } } // Read gag with type validation (V2 GAGGING component) if (tag.contains("gag")) { ItemStack gagStack = ItemStack.of(tag.getCompound("gag")); if (!gagStack.isEmpty() && DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null) { this.gag = gagStack; } } // Read blindfold with type validation (V2 EYES-region item) if (tag.contains("blindfold")) { ItemStack blindfoldStack = ItemStack.of(tag.getCompound("blindfold")); if (!blindfoldStack.isEmpty() && isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES)) { this.blindfold = blindfoldStack; } } // Read earplugs with type validation (V2 EARS-region item) if (tag.contains("earplugs")) { ItemStack earplugsStack = ItemStack.of(tag.getCompound("earplugs")); if (!earplugsStack.isEmpty() && isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS)) { this.earplugs = earplugsStack; } } // Read collar with type validation (V2 collar) if (tag.contains("collar")) { ItemStack collarStack = ItemStack.of(tag.getCompound("collar")); if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) { 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; } // V2 HELPERS /** Check if a stack is a data-driven item occupying the given body region. */ private static boolean isDataDrivenForRegion(ItemStack stack, BodyRegionV2 region) { DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); return def != null && def.occupiedRegions().contains(region); } // 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 getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } @Override public void handleUpdateTag(CompoundTag tag) { if (!this.offMode) { this.readBondageData(tag); } } }