Files
TiedUp-/src/main/java/com/tiedup/remake/events/lifecycle/PlayerStateEventHandler.java
NotEvil 9b2c5dec8e chore(P2): V1 zombie comments cleanup + i18n events/network/items
Zombie comments (14 files):
- Replace references to deleted V1 classes (ItemBind, ItemCollar, IBondageItem,
  BindVariant, etc.) with V2 equivalents or past-tense historical notes
- "V1 path" → "legacy slot-index path" for still-active legacy codepaths
- Clean misleading javadoc that implied V1 code was still present

i18n (13 files, 31 new keys):
- Events: 12 Component.literal() → translatable (labor tools, gag eat,
  death escape, debt, punishment, grace, camp protection, chest, maid)
- Network: 12 Component.literal() → translatable (trader messages,
  lockpick jam, slave freedom warnings)
- Items: 2 Component.literal() → translatable (ItemOwnerTarget tooltips)
2026-04-16 11:20:17 +02:00

438 lines
17 KiB
Java

package com.tiedup.remake.events.lifecycle;
import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.events.restriction.BondageItemRestrictionHandler;
import com.tiedup.remake.labor.LaborTask;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.labor.PacketSyncLaborProgress;
import com.tiedup.remake.network.sync.PacketSyncBindState;
import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState;
import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2BondageEquipmentProvider;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
/**
* Event handler for PlayerBindState lifecycle management.
* Handles player connection, disconnection, death, and respawn.
*/
@Mod.EventBusSubscriber(
modid = TiedUpMod.MOD_ID,
bus = Mod.EventBusSubscriber.Bus.FORGE
)
public class PlayerStateEventHandler {
/** UUIDs of players who died while imprisoned — message sent on respawn */
private static final Set<UUID> pendingDeathEscapeMessage =
ConcurrentHashMap.newKeySet();
/**
* Called when a player logs in to the server.
* Initialize or reset their PlayerBindState, restore pole leash, and sync to client.
*/
@SubscribeEvent
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) {
return;
}
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.resetNewConnection(player);
}
// Restore pole leash if player was leashed to one when they disconnected
restorePoleLeashIfNeeded(player);
// MEDIUM FIX: Restore captor if player was captive when they disconnected
restoreCaptorIfNeeded(player);
// Sync V2 equipment + bind state to the logging-in player
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player);
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
player
);
if (statePacket != null) {
ModNetwork.sendToPlayer(statePacket, player);
}
// Sync Labor HUD on login
syncLaborState(player);
}
/**
* Restore leash to pole if player was leashed when they disconnected.
*/
private static void restorePoleLeashIfNeeded(ServerPlayer player) {
player
.getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
.ifPresent(cap -> {
if (!cap.wasLeashedToPole()) {
return;
}
BlockPos polePos = cap.getSavedPolePosition();
ResourceKey<Level> poleDimension = cap.getSavedPoleDimension();
if (polePos == null || poleDimension == null) {
cap.clearSavedPoleLeash();
return;
}
// Check if player is in the same dimension
if (!player.level().dimension().equals(poleDimension)) {
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected but is in different dimension. Clearing saved pole leash.",
player.getName().getString()
);
cap.clearSavedPoleLeash();
return;
}
ServerLevel level = player.serverLevel();
// Try to find or create the LeashFenceKnotEntity at that position
LeashFenceKnotEntity fenceKnot =
LeashFenceKnotEntity.getOrCreateKnot(level, polePos);
if (fenceKnot == null) {
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected but pole at {} no longer exists.",
player.getName().getString(),
polePos
);
cap.clearSavedPoleLeash();
return;
}
// Attach leash to the pole
if (player instanceof IPlayerLeashAccess access) {
access.tiedup$attachLeash(fenceKnot);
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected. Restored leash to pole at {}.",
player.getName().getString(),
polePos
);
}
// Clear saved data (no longer needed)
cap.clearSavedPoleLeash();
});
}
/**
* MEDIUM FIX: Restore captor if player was captive when they disconnected.
*/
private static void restoreCaptorIfNeeded(ServerPlayer player) {
player
.getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
.ifPresent(cap -> {
if (!cap.hasSavedCaptor()) {
return;
}
java.util.UUID captorUUID = cap.getSavedCaptorUUID();
if (captorUUID == null) {
cap.clearSavedCaptor();
return;
}
ServerLevel level = player.serverLevel();
// Try to find the captor entity
net.minecraft.world.entity.Entity captorEntity =
level.getEntity(captorUUID);
if (captorEntity == null) {
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected but captor {} no longer exists. Clearing captivity.",
player.getName().getString(),
captorUUID.toString().substring(0, 8)
);
cap.clearSavedCaptor();
return;
}
// Check if captor is still a valid ICaptor
if (
!(captorEntity instanceof
net.minecraft.world.entity.LivingEntity livingCaptor)
) {
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected but captor is not a LivingEntity. Clearing captivity.",
player.getName().getString()
);
cap.clearSavedCaptor();
return;
}
// Get the ICaptor interface
com.tiedup.remake.state.ICaptor captor = null;
if (
livingCaptor instanceof
com.tiedup.remake.state.ICaptor kidnapper
) {
captor = kidnapper;
} else {
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected but captor {} does not implement ICaptor. Clearing captivity.",
player.getName().getString(),
captorUUID.toString().substring(0, 8)
);
cap.clearSavedCaptor();
return;
}
// Check if player is still enslavable (still tied up)
PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null || !state.isTiedUp()) {
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected but is no longer tied up. Clearing saved captor.",
player.getName().getString()
);
cap.clearSavedCaptor();
return;
}
// Restore captivity using the PlayerCaptivity component
boolean success = state.getCapturedBy(captor);
if (success) {
TiedUpMod.LOGGER.info(
"[Lifecycle] {} reconnected. Restored captivity to {}.",
player.getName().getString(),
captorEntity.getName().getString()
);
} else {
TiedUpMod.LOGGER.warn(
"[Lifecycle] {} reconnected but failed to restore captivity to {}.",
player.getName().getString(),
captorEntity.getName().getString()
);
}
// Clear saved data (no longer needed)
cap.clearSavedCaptor();
});
}
/**
* Called when a player logs out of the server.
* Clean up their PlayerBindState instance.
*/
@SubscribeEvent
public static void onPlayerLoggedOut(
PlayerEvent.PlayerLoggedOutEvent event
) {
if (!(event.getEntity() instanceof ServerPlayer player)) {
return;
}
// NOTE: PlayerBindState.removeInstance() is NOT called here.
// The canonical removal point is PlayerLifecycleHandler.onPlayerLoggedOut (EventPriority.HIGH).
// Calling it here at NORMAL priority would be a redundant double-removal.
// Clean up message cooldowns to prevent memory leak
BondageItemRestrictionHandler.clearCooldowns(player.getUUID());
// Clean up pending death escape message flag (player disconnected between death and respawn)
pendingDeathEscapeMessage.remove(player.getUUID());
}
/**
* Called when a player dies.
* Handle bondage state cleanup based on game rules.
*/
@SubscribeEvent
public static void onPlayerDeath(LivingDeathEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) {
return;
}
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.onDeathKidnapped(player.level());
}
// Clean up captivity state - prisoner can't continue task after death
PrisonerManager manager = PrisonerManager.get(player.serverLevel());
PrisonerRecord record = manager.getRecord(player.getUUID());
LaborRecord laborRecord = manager.getLaborRecord(player.getUUID());
if (record.isImprisoned() || laborRecord.hasTask()) {
// SECURITY: Remove labor tools before death to prevent item duplication
// (Tools would otherwise drop on death)
removeLaborToolsOnDeath(player);
// Transition to FREE state via escape (clears all data)
long currentTime = player.serverLevel().getGameTime();
// Use centralized escape service for complete cleanup
com.tiedup.remake.prison.service.PrisonerService.get().escape(
player.serverLevel(),
player.getUUID(),
"death"
);
// Flag for respawn message
pendingDeathEscapeMessage.add(player.getUUID());
TiedUpMod.LOGGER.debug(
"[PlayerStateEventHandler] Transitioned {} to FREE state on death",
player.getName().getString()
);
} else if (record.isProtected(player.serverLevel().getGameTime())) {
// Revoke grace period on death - player can be targeted again after respawn
// Death is considered "payment" for any remaining grace
record.setProtectionExpiry(0);
}
// Release prisoner from any cells to prevent "ghost prisoner" blocking cells
// Without this, the cell still counts this player as prisoner, making it "full"
// and preventing new kidnappers from using it
CellRegistryV2 cellRegistry = CellRegistryV2.get(player.server);
if (cellRegistry != null) {
int released = cellRegistry.releasePrisonerFromAllCells(
player.getUUID()
);
if (released > 0) {
TiedUpMod.LOGGER.debug(
"[PlayerStateEventHandler] Released {} from {} cell(s) on death",
player.getName().getString(),
released
);
}
}
}
/**
* Called when a player respawns after death.
* Handled by PlayerEvent.Clone in CapabilityEventHandler (capability copying)
* and PlayerLoggedIn (state reset).
*
* The respawn process:
* 1. PlayerEvent.Clone -> Copy capability data from old player to new player
* 2. PlayerLoggedIn -> Reset PlayerBindState with new player entity
* 3. Sync to client
*/
@SubscribeEvent
public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) {
return;
}
// Get or create state for the respawned player
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.resetNewConnection(player);
}
// Sync V2 equipment + bind state to the respawned player
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player);
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
player
);
if (statePacket != null) {
ModNetwork.sendToPlayer(statePacket, player);
}
// Sync Labor HUD on respawn (restores task OR clears it if executed)
syncLaborState(player);
// Send death escape message if applicable
if (pendingDeathEscapeMessage.remove(player.getUUID())) {
player.sendSystemMessage(
net.minecraft.network.chat.Component.translatable(
"msg.tiedup.event.death_escape"
).withStyle(net.minecraft.ChatFormatting.YELLOW)
);
}
}
/**
* Helper to sync labor state to client.
* Sends current progress if active, or a clear packet if not.
*/
private static void syncLaborState(ServerPlayer player) {
PrisonerManager manager = PrisonerManager.get(player.serverLevel());
PrisonerRecord record = manager.getRecord(player.getUUID());
LaborRecord laborRecord = manager.getLaborRecord(player.getUUID());
if (record.isImprisoned() && laborRecord.hasTask()) {
LaborTask task = laborRecord.getTask();
ModNetwork.sendToPlayer(
new PacketSyncLaborProgress(
task.getDescription(),
task.getProgress(),
task.getQuota(),
task.getValue()
),
player
);
TiedUpMod.LOGGER.debug(
"[PlayerStateEventHandler] Synced labor HUD for {}: {}/{} {}",
player.getName().getString(),
task.getProgress(),
task.getQuota(),
task.getDescription()
);
} else {
// No active captivity state - ensure HUD is cleared (important after execution/death)
ModNetwork.sendToPlayer(new PacketSyncLaborProgress(), player);
TiedUpMod.LOGGER.debug(
"[PlayerStateEventHandler] Cleared labor HUD for {}",
player.getName().getString()
);
}
}
/**
* SECURITY: Remove all labor tools from player inventory before death.
* Prevents tools from dropping and being recoverable.
*/
private static void removeLaborToolsOnDeath(ServerPlayer player) {
var inventory = player.getInventory();
int removedCount = 0;
for (int i = 0; i < inventory.getContainerSize(); i++) {
ItemStack stack = inventory.getItem(i);
if (!stack.isEmpty() && stack.hasTag()) {
CompoundTag tag = stack.getTag();
if (tag != null && tag.getBoolean("LaborTool")) {
inventory.setItem(i, ItemStack.EMPTY);
removedCount++;
}
}
}
if (removedCount > 0) {
TiedUpMod.LOGGER.debug(
"[PlayerStateEventHandler] Removed {} labor tools from {} before death",
removedCount,
player.getName().getString()
);
}
}
}