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,641 @@
package 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.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.saveddata.SavedData;
import org.jetbrains.annotations.Nullable;
/**
* Phase 2: SavedData registry for confiscated player inventories.
*
* When a player is imprisoned:
* 1. Their inventory is saved to NBT
* 2. Items are transferred to a LOOT chest in the cell
* 3. Player's inventory is cleared
* 4. Data is persisted for recovery after server restart
*
* Recovery options:
* - Player finds and opens the chest manually
* - Player escapes and returns to chest
* - Admin command /tiedup returnstuff <player>
*/
public class ConfiscatedInventoryRegistry extends SavedData {
private static final String DATA_NAME =
TiedUpMod.MOD_ID + "_confiscated_inventories";
/**
* Map of prisoner UUID to their confiscated inventory data
*/
private final Map<UUID, ConfiscatedData> confiscatedInventories =
new HashMap<>();
/**
* Data class for a single confiscated inventory
*/
public static class ConfiscatedData {
public final UUID prisonerId;
public final CompoundTag inventoryNbt;
public final BlockPos chestPos;
public final UUID cellId;
public final long confiscatedTime;
public ConfiscatedData(
UUID prisonerId,
CompoundTag inventoryNbt,
BlockPos chestPos,
@Nullable UUID cellId,
long confiscatedTime
) {
this.prisonerId = prisonerId;
this.inventoryNbt = inventoryNbt;
this.chestPos = chestPos;
this.cellId = cellId;
this.confiscatedTime = confiscatedTime;
}
public CompoundTag save() {
CompoundTag tag = new CompoundTag();
tag.putUUID("prisonerId", prisonerId);
tag.put("inventory", inventoryNbt);
tag.put("chestPos", NbtUtils.writeBlockPos(chestPos));
if (cellId != null) {
tag.putUUID("cellId", cellId);
}
tag.putLong("time", confiscatedTime);
return tag;
}
public static ConfiscatedData load(CompoundTag tag) {
UUID prisonerId = tag.getUUID("prisonerId");
CompoundTag inventoryNbt = tag.getCompound("inventory");
BlockPos chestPos = NbtUtils.readBlockPos(
tag.getCompound("chestPos")
);
UUID cellId = tag.contains("cellId") ? tag.getUUID("cellId") : null;
long time = tag.getLong("time");
return new ConfiscatedData(
prisonerId,
inventoryNbt,
chestPos,
cellId,
time
);
}
}
public ConfiscatedInventoryRegistry() {}
// ==================== STATIC ACCESSORS ====================
/**
* Get or create the registry for a server level.
*/
public static ConfiscatedInventoryRegistry get(ServerLevel level) {
return level
.getDataStorage()
.computeIfAbsent(
ConfiscatedInventoryRegistry::load,
ConfiscatedInventoryRegistry::new,
DATA_NAME
);
}
// ==================== CONFISCATION METHODS ====================
/**
* Confiscate a player's inventory.
*
* @param player The player whose inventory to confiscate
* @param chestPos The position of the LOOT chest
* @param cellId The cell ID (optional)
* @return true if confiscation was successful
*/
public boolean confiscate(
ServerPlayer player,
BlockPos chestPos,
@Nullable UUID cellId
) {
Inventory inventory = player.getInventory();
// CRITICAL FIX: Transaction safety - save record BEFORE any state changes
// This ensures that if the server crashes, we have a backup to restore from
// Save inventory to NBT (backup in case of issues)
CompoundTag inventoryNbt = savePlayerInventory(player);
// Create and persist record FIRST (before modifying game state)
ConfiscatedData data = new ConfiscatedData(
player.getUUID(),
inventoryNbt,
chestPos,
cellId,
System.currentTimeMillis()
);
confiscatedInventories.put(player.getUUID(), data);
setDirty(); // Persist immediately before state changes
// Now attempt transfer to chest
boolean transferred = transferToChest(
player.serverLevel(),
chestPos,
inventory
);
if (!transferred) {
// Transfer failed - rollback: remove record and DO NOT clear inventory
confiscatedInventories.remove(player.getUUID());
setDirty(); // Persist the rollback
TiedUpMod.LOGGER.error(
"[ConfiscatedInventoryRegistry] Failed to transfer items for {} - items NOT confiscated, record rolled back",
player.getName().getString()
);
return false;
}
// Transfer succeeded - now safe to clear inventory
inventory.clearContent();
TiedUpMod.LOGGER.info(
"[ConfiscatedInventoryRegistry] Confiscated inventory from {} (chest: {}, cell: {})",
player.getName().getString(),
chestPos.toShortString(),
cellId != null ? cellId.toString().substring(0, 8) : "none"
);
// Final persist to save cleared inventory state
setDirty();
return true;
}
/**
* Dump a player's inventory to a chest WITHOUT creating a backup record.
* Used for daily labor returns - items gathered during work are transferred to camp storage.
* Does NOT clear the player's inventory - caller should clear labor tools separately.
*
* @param player The player whose inventory to dump
* @param chestPos The position of the chest
* @return true if transfer was successful
*/
public boolean dumpInventoryToChest(
ServerPlayer player,
BlockPos chestPos
) {
Inventory inventory = player.getInventory();
// Transfer items to chest
boolean transferred = transferToChest(
player.serverLevel(),
chestPos,
inventory
);
if (!transferred) {
TiedUpMod.LOGGER.warn(
"[ConfiscatedInventoryRegistry] Failed to dump labor inventory for {} - chest issue",
player.getName().getString()
);
return false;
}
// Clear player inventory (items are now in chest)
inventory.clearContent();
TiedUpMod.LOGGER.debug(
"[ConfiscatedInventoryRegistry] Dumped labor inventory from {} to chest at {}",
player.getName().getString(),
chestPos.toShortString()
);
return true;
}
/**
* Deposits items across multiple LOOT chests with smart rotation.
* Distributes items evenly to prevent single chest from filling too quickly.
*
* @param items List of items to deposit
* @param chestPositions List of chest positions to use (in priority order)
* @param level Server level
* @return Number of items successfully deposited (remainder dropped)
*/
public int depositItemsInChests(
List<ItemStack> items,
List<BlockPos> chestPositions,
ServerLevel level
) {
if (items.isEmpty() || chestPositions.isEmpty()) {
return 0;
}
int deposited = 0;
List<ItemStack> overflow = new ArrayList<>();
// Try to deposit each item
for (ItemStack stack : items) {
if (stack.isEmpty()) continue;
boolean placed = false;
// Try all chests in order
for (BlockPos chestPos : chestPositions) {
BlockEntity be = level.getBlockEntity(chestPos);
if (!(be instanceof ChestBlockEntity chest)) continue;
// Try to stack with existing items first
for (int i = 0; i < chest.getContainerSize(); i++) {
ItemStack slot = chest.getItem(i);
// Stack with existing
if (
!slot.isEmpty() &&
ItemStack.isSameItemSameTags(slot, stack) &&
slot.getCount() < slot.getMaxStackSize()
) {
int spaceInSlot =
slot.getMaxStackSize() - slot.getCount();
int toAdd = Math.min(spaceInSlot, stack.getCount());
slot.grow(toAdd);
chest.setChanged();
stack.shrink(toAdd);
deposited += toAdd;
if (stack.isEmpty()) {
placed = true;
break;
}
}
}
if (placed) break;
// Try to place in empty slot
if (!stack.isEmpty()) {
for (int i = 0; i < chest.getContainerSize(); i++) {
if (chest.getItem(i).isEmpty()) {
chest.setItem(i, stack.copy());
chest.setChanged();
deposited += stack.getCount();
placed = true;
break;
}
}
}
if (placed) break;
}
// If not placed, add to overflow
if (!placed && !stack.isEmpty()) {
overflow.add(stack);
}
}
// Drop overflow items at first chest location
if (!overflow.isEmpty() && !chestPositions.isEmpty()) {
BlockPos dropPos = chestPositions.get(0);
for (ItemStack stack : overflow) {
net.minecraft.world.entity.item.ItemEntity itemEntity =
new net.minecraft.world.entity.item.ItemEntity(
level,
dropPos.getX() + 0.5,
dropPos.getY() + 1.0,
dropPos.getZ() + 0.5,
stack
);
level.addFreshEntity(itemEntity);
}
TiedUpMod.LOGGER.warn(
"[ConfiscatedInventoryRegistry] {} items overflowed - dropped at {}",
overflow.stream().mapToInt(ItemStack::getCount).sum(),
dropPos.toShortString()
);
}
return deposited;
}
/**
* Save player inventory to NBT.
*/
private CompoundTag savePlayerInventory(ServerPlayer player) {
CompoundTag tag = new CompoundTag();
tag.put("Items", player.getInventory().save(new ListTag()));
return tag;
}
/**
* Transfer inventory contents to a chest.
* Handles ALL inventory slots:
* - Slots 0-35: Main inventory (hotbar 0-8, backpack 9-35)
* - Slots 36-39: Armor (boots, leggings, chestplate, helmet)
* - Slot 40: Offhand
*/
private boolean transferToChest(
ServerLevel level,
BlockPos chestPos,
Inventory inventory
) {
// Find an existing chest near the LOOT marker position
BlockPos actualChestPos = findExistingChestNear(level, chestPos);
if (actualChestPos == null) {
TiedUpMod.LOGGER.warn(
"[ConfiscatedInventoryRegistry] No existing chest found near {} - structure may be damaged",
chestPos.toShortString()
);
return false;
}
BlockEntity be = level.getBlockEntity(actualChestPos);
if (!(be instanceof Container chest)) {
TiedUpMod.LOGGER.warn(
"[ConfiscatedInventoryRegistry] Block at {} is not a container",
actualChestPos.toShortString()
);
return false;
}
// Transfer ALL items including armor and offhand
int mainItems = 0;
int armorItems = 0;
int offhandItems = 0;
int droppedItems = 0;
// getContainerSize() returns 41: 36 main + 4 armor + 1 offhand
for (int i = 0; i < inventory.getContainerSize(); i++) {
ItemStack stack = inventory.getItem(i);
if (!stack.isEmpty()) {
ItemStack remaining = addToContainer(chest, stack.copy());
if (remaining.isEmpty()) {
// Track which slot type was transferred
if (i < 36) {
mainItems++;
} else if (i < 40) {
armorItems++;
} else {
offhandItems++;
}
} else {
// Couldn't fit all items - drop them on the ground near the chest
// This prevents permanent item loss
net.minecraft.world.entity.item.ItemEntity itemEntity =
new net.minecraft.world.entity.item.ItemEntity(
level,
actualChestPos.getX() + 0.5,
actualChestPos.getY() + 1.0,
actualChestPos.getZ() + 0.5,
remaining
);
// Add slight random velocity to spread items
itemEntity.setDeltaMovement(
(level.random.nextDouble() - 0.5) * 0.2,
0.2,
(level.random.nextDouble() - 0.5) * 0.2
);
level.addFreshEntity(itemEntity);
droppedItems += remaining.getCount();
}
}
}
if (droppedItems > 0) {
TiedUpMod.LOGGER.info(
"[ConfiscatedInventoryRegistry] Dropped {} overflow items at {}",
droppedItems,
actualChestPos.toShortString()
);
}
int totalTransferred = mainItems + armorItems + offhandItems;
TiedUpMod.LOGGER.info(
"[ConfiscatedInventoryRegistry] Confiscated {} items (inventory: {}, armor: {}, offhand: {})",
totalTransferred,
mainItems,
armorItems,
offhandItems
);
return true;
}
/**
* Add an item stack to a container, returning any remainder.
*/
private ItemStack addToContainer(Container container, ItemStack stack) {
ItemStack remaining = stack.copy();
for (
int i = 0;
i < container.getContainerSize() && !remaining.isEmpty();
i++
) {
ItemStack slotStack = container.getItem(i);
if (slotStack.isEmpty()) {
container.setItem(i, remaining.copy());
remaining = ItemStack.EMPTY;
} else if (ItemStack.isSameItemSameTags(slotStack, remaining)) {
int space = slotStack.getMaxStackSize() - slotStack.getCount();
int toTransfer = Math.min(space, remaining.getCount());
if (toTransfer > 0) {
slotStack.grow(toTransfer);
remaining.shrink(toTransfer);
}
}
}
container.setChanged();
return remaining;
}
/**
* Find an existing chest near the given position (typically a LOOT marker).
* The LOOT marker is placed ABOVE the physical chest in the structure,
* so we check below first, then at the position, then in a small radius.
*
* Does NOT spawn new chests - structures must have chests placed by their templates.
*
* @return The position of an existing chest, or null if none found
*/
@Nullable
private static BlockPos findExistingChestNear(
ServerLevel level,
BlockPos pos
) {
// Check below the marker (chest is usually under the LOOT marker)
BlockPos below = pos.below();
if (level.getBlockEntity(below) instanceof ChestBlockEntity) {
return below;
}
// Check at the marker position itself
if (level.getBlockEntity(pos) instanceof ChestBlockEntity) {
return pos;
}
// Search in a small radius (3 blocks)
for (int radius = 1; radius <= 3; radius++) {
for (int dx = -radius; dx <= radius; dx++) {
for (int dy = -2; dy <= 1; dy++) {
for (int dz = -radius; dz <= radius; dz++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
BlockPos testPos = pos.offset(dx, dy, dz);
if (
level.getBlockEntity(testPos) instanceof
ChestBlockEntity
) {
TiedUpMod.LOGGER.debug(
"[ConfiscatedInventoryRegistry] Found existing chest at {} (offset from marker {})",
testPos.toShortString(),
pos.toShortString()
);
return testPos;
}
}
}
}
}
TiedUpMod.LOGGER.warn(
"[ConfiscatedInventoryRegistry] No existing chest found near marker at {}",
pos.toShortString()
);
return null;
}
// ==================== RECOVERY METHODS ====================
/**
* Check if a player has confiscated inventory.
*/
public boolean hasConfiscatedInventory(UUID playerId) {
return confiscatedInventories.containsKey(playerId);
}
/**
* Get confiscated data for a player.
*/
@Nullable
public ConfiscatedData getConfiscatedData(UUID playerId) {
return confiscatedInventories.get(playerId);
}
/**
* Get the chest position for a player's confiscated inventory.
*/
@Nullable
public BlockPos getChestPosition(UUID playerId) {
ConfiscatedData data = confiscatedInventories.get(playerId);
return data != null ? data.chestPos : null;
}
/**
* Restore a player's confiscated inventory from the NBT backup.
* This gives items directly to the player, bypassing the chest.
*
* @param player The player to restore inventory to
* @return true if restoration was successful, false if no confiscated data found
*/
public boolean restoreInventory(ServerPlayer player) {
ConfiscatedData data = confiscatedInventories.get(player.getUUID());
if (data == null) {
TiedUpMod.LOGGER.debug(
"[ConfiscatedInventoryRegistry] No confiscated inventory for {}",
player.getName().getString()
);
return false;
}
// Load inventory from NBT backup
if (data.inventoryNbt.contains("Items")) {
ListTag items = data.inventoryNbt.getList(
"Items",
Tag.TAG_COMPOUND
);
player.getInventory().load(items);
TiedUpMod.LOGGER.info(
"[ConfiscatedInventoryRegistry] Restored {} inventory slots to {}",
items.size(),
player.getName().getString()
);
}
// Remove the confiscation record
confiscatedInventories.remove(player.getUUID());
setDirty();
return true;
}
/**
* Remove the confiscation record for a player without restoring items.
* Used when the player has already retrieved items from the chest manually.
*
* @param playerId The player's UUID
*/
public void clearConfiscationRecord(UUID playerId) {
if (confiscatedInventories.remove(playerId) != null) {
TiedUpMod.LOGGER.debug(
"[ConfiscatedInventoryRegistry] Cleared confiscation record for {}",
playerId.toString().substring(0, 8)
);
setDirty();
}
}
// ==================== SERIALIZATION ====================
@Override
public CompoundTag save(CompoundTag tag) {
ListTag list = new ListTag();
for (ConfiscatedData data : confiscatedInventories.values()) {
list.add(data.save());
}
tag.put("confiscated", list);
return tag;
}
public static ConfiscatedInventoryRegistry load(CompoundTag tag) {
ConfiscatedInventoryRegistry registry =
new ConfiscatedInventoryRegistry();
if (tag.contains("confiscated")) {
ListTag list = tag.getList("confiscated", Tag.TAG_COMPOUND);
for (int i = 0; i < list.size(); i++) {
ConfiscatedData data = ConfiscatedData.load(
list.getCompound(i)
);
registry.confiscatedInventories.put(data.prisonerId, data);
}
}
TiedUpMod.LOGGER.info(
"[ConfiscatedInventoryRegistry] Loaded {} confiscated inventory records",
registry.confiscatedInventories.size()
);
return registry;
}
}