Phase 1 (state): PlayerBindState, PlayerCaptorManager, PlayerEquipment, PlayerDataRetrieval, PlayerLifecycle, PlayerShockCollar, StruggleAccessory Phase 2 (client): AnimationTickHandler, NpcAnimationTickHandler, 5 render handlers, DamselModel, 3 client mixins, SelfBondageInputHandler, SlaveManagementScreen, ActionPanel, SlaveEntryWidget, ModKeybindings Phase 3 (entities): 28 entity/AI files migrated to CollarHelper, BindModeHelper, PoseTypeHelper, createStack() Phase 4 (network): PacketSlaveAction, PacketMasterEquip, PacketAssignCellToCollar, PacketNpcCommand, PacketFurnitureForcemount Phase 5 (events): RestraintTaskTickHandler, PetPlayRestrictionHandler, PlayerEnslavementHandler, ChatEventHandler, LaborAttackPunishmentHandler Phase 6 (commands): BondageSubCommand, CollarCommand, NPCCommand, KidnapSetCommand Phase 7 (compat): MCAKidnappedAdapter, MCA mixins Phase 8 (misc): GagTalkManager, PetRequestManager, HangingCagePiece, BondageItemBlockEntity, TrappedChestBlockEntity, DispenserBehaviors, BondageItemLoaderUtility, RestraintApplicator, StruggleSessionManager, MovementStyleResolver, CampLifecycleManager Some files retain dual V1/V2 checks (instanceof V1 || V2Helper) for coexistence — V1-only branches removed in Branch D.
393 lines
10 KiB
Java
393 lines
10 KiB
Java
package com.tiedup.remake.state;
|
|
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import com.tiedup.remake.v2.BodyRegionV2;
|
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
|
|
// C6-V2: IRestrainable → IBondageState (narrowed API)
|
|
|
|
/**
|
|
*
|
|
* Manages capture relationships for player captors.
|
|
*
|
|
* Terminology:
|
|
* - "Captive" = Entity attached by leash (active physical control)
|
|
* - "Slave" = Entity wearing a collar owned by someone (passive ownership via CollarRegistry)
|
|
*
|
|
* Features:
|
|
* - Supports multiple captives simultaneously
|
|
* - Allows captive transfer between captors
|
|
* - Tracks captive list persistently
|
|
* - Handles cleanup on captive logout/escape
|
|
*
|
|
* Thread Safety:
|
|
* - Uses CopyOnWriteArrayList to avoid ConcurrentModificationException
|
|
* - Safe for iteration during modification
|
|
*
|
|
* Design:
|
|
* - Each PlayerBindState has one PlayerCaptorManager
|
|
* - Manager tracks all captives owned by that player
|
|
* - Implements ICaptor interface for polymorphic usage
|
|
*
|
|
* @see ICaptor
|
|
* @see PlayerBindState
|
|
*/
|
|
public class PlayerCaptorManager implements ICaptor {
|
|
|
|
/**
|
|
* The player who owns this manager (the captor).
|
|
*/
|
|
private final Player captor;
|
|
|
|
/**
|
|
* List of all captives currently owned by this captor.
|
|
* Thread-safe to avoid concurrent modification during iteration.
|
|
*
|
|
*/
|
|
private final List<IBondageState> captives;
|
|
|
|
/**
|
|
* Create a new captor manager for the given player.
|
|
*
|
|
* @param captor The player who will be the captor
|
|
*/
|
|
public PlayerCaptorManager(Player captor) {
|
|
this.captor = captor;
|
|
this.captives = new CopyOnWriteArrayList<>();
|
|
}
|
|
|
|
// ICaptor Implementation
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public synchronized void addCaptive(IBondageState captive) {
|
|
if (captive == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PlayerCaptorManager] Attempted to add null captive"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!captives.contains(captive)) {
|
|
captives.add(captive);
|
|
TiedUpMod.LOGGER.info(
|
|
"[PlayerCaptorManager] {} captured {} (total captives: {})",
|
|
captor.getName().getString(),
|
|
captive.getKidnappedName(),
|
|
captives.size()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Thread Safety: Synchronized on 'this' to match addCaptive and freeAllCaptives.
|
|
*/
|
|
@Override
|
|
public synchronized void removeCaptive(
|
|
IBondageState captive,
|
|
boolean transportState
|
|
) {
|
|
if (captive == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PlayerCaptorManager] Attempted to remove null captive"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (captives.remove(captive)) {
|
|
TiedUpMod.LOGGER.info(
|
|
"[PlayerCaptorManager] {} freed {} (remaining captives: {})",
|
|
captor.getName().getString(),
|
|
captive.getKidnappedName(),
|
|
captives.size()
|
|
);
|
|
|
|
// If requested, also despawn the transport entity
|
|
if (transportState && captive.getTransport() != null) {
|
|
captive.getTransport().discard();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public boolean canCapture(IBondageState target) {
|
|
if (target == null) {
|
|
return false;
|
|
}
|
|
|
|
// From original code (PlayerBindState.java:195-225):
|
|
// Can capture if:
|
|
// - Target is tied up, OR
|
|
// - Target has collar AND collar has this captor as owner
|
|
|
|
net.minecraft.world.entity.LivingEntity targetEntity =
|
|
target.asLivingEntity();
|
|
if (targetEntity == null) {
|
|
return false;
|
|
}
|
|
|
|
// Check if target is tied up
|
|
if (target.isTiedUp()) {
|
|
return true;
|
|
}
|
|
|
|
// Check if target has collar with this captor as owner
|
|
if (target.hasCollar()) {
|
|
ItemStack collar = target.getEquipment(BodyRegionV2.NECK);
|
|
if (CollarHelper.isCollar(collar)) {
|
|
if (
|
|
CollarHelper.getOwners(collar).contains(this.captor.getUUID())
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public boolean canRelease(IBondageState captive) {
|
|
if (captive == null) {
|
|
return false;
|
|
}
|
|
|
|
// Can only release if this manager is the captive's captor
|
|
return captive.getCaptor() == this;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public boolean allowCaptiveTransfer() {
|
|
// Players always allow captive transfer
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public boolean allowMultipleCaptives() {
|
|
// Players can have multiple captives
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Note: For NPC captives, this is never called (NPCs don't log out)
|
|
*/
|
|
@Override
|
|
public void onCaptiveLogout(IBondageState captive) {
|
|
if (captive == null) {
|
|
return;
|
|
}
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PlayerCaptorManager] Captive {} logged out while captured by {}",
|
|
captive.getKidnappedName(),
|
|
captor.getName().getString()
|
|
);
|
|
|
|
// Keep captive in list - they might reconnect
|
|
// Transport entity will despawn after timeout
|
|
// On reconnect, checkStillCaptive() will clean up if needed
|
|
}
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public void onCaptiveReleased(IBondageState captive) {
|
|
if (captive == null) {
|
|
return;
|
|
}
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PlayerCaptorManager] Captive {} was released from {}",
|
|
captive.getKidnappedName(),
|
|
captor.getName().getString()
|
|
);
|
|
|
|
// No special action needed - already removed from list by removeCaptive()
|
|
}
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public void onCaptiveStruggle(IBondageState captive) {
|
|
if (captive == null) {
|
|
return;
|
|
}
|
|
|
|
TiedUpMod.LOGGER.debug(
|
|
"[PlayerCaptorManager] Captive {} struggled (captor: {})",
|
|
captive.getKidnappedName(),
|
|
captor.getName().getString()
|
|
);
|
|
}
|
|
|
|
/**
|
|
*/
|
|
@Override
|
|
public boolean hasCaptives() {
|
|
return !captives.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public Entity getEntity() {
|
|
return captor;
|
|
}
|
|
|
|
// Additional Methods
|
|
|
|
/**
|
|
* Frees all captives currently owned by this captor.
|
|
*
|
|
*
|
|
* Thread Safety: Synchronized on 'this' to match addCaptive and removeCaptive.
|
|
*
|
|
* @param transportState If true, destroy the transporter entities
|
|
*/
|
|
public synchronized void freeAllCaptives(boolean transportState) {
|
|
// Use a copy to avoid concurrent modification while iterating
|
|
List<IBondageState> copy = new ArrayList<>(captives);
|
|
for (IBondageState captive : copy) {
|
|
captive.free(transportState);
|
|
}
|
|
captives.clear();
|
|
}
|
|
|
|
/**
|
|
* Frees all captives with default behavior (destroy transport entities).
|
|
*/
|
|
public void freeAllCaptives() {
|
|
freeAllCaptives(true);
|
|
}
|
|
|
|
/**
|
|
* Get a copy of the captive list.
|
|
* Safe for iteration without concurrent modification issues.
|
|
*
|
|
*
|
|
* @return Copy of the current captive list
|
|
*/
|
|
public List<IBondageState> getCaptives() {
|
|
return new ArrayList<>(captives);
|
|
}
|
|
|
|
/**
|
|
* Get the number of captives currently owned.
|
|
*
|
|
*
|
|
* @return Captive count
|
|
*/
|
|
public int getCaptiveCount() {
|
|
return captives.size();
|
|
}
|
|
|
|
/**
|
|
* Transfer all captives from this captor to a new captor.
|
|
* Used when this player gets captured themselves.
|
|
*
|
|
* From original code:
|
|
* - When a player gets captured, their captives transfer to new captor
|
|
* - Prevents circular capture issues
|
|
*
|
|
*
|
|
* @param newCaptor The new captor to transfer captives to
|
|
*/
|
|
public void transferAllCaptivesTo(ICaptor newCaptor) {
|
|
if (newCaptor == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PlayerCaptorManager] Attempted to transfer captives to null captor"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (captives.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PlayerCaptorManager] Transferring {} captives from {} to {}",
|
|
captives.size(),
|
|
captor.getName().getString(),
|
|
newCaptor.getEntity().getName().getString()
|
|
);
|
|
|
|
// Create copy to avoid concurrent modification
|
|
List<IBondageState> captivesToTransfer = new ArrayList<>(captives);
|
|
|
|
for (IBondageState captive : captivesToTransfer) {
|
|
if (captive != null) {
|
|
captive.transferCaptivityTo(newCaptor);
|
|
}
|
|
}
|
|
|
|
// All captives should now be removed from this manager's list
|
|
if (!captives.isEmpty()) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PlayerCaptorManager] {} captives remain after transfer - cleaning up",
|
|
captives.size()
|
|
);
|
|
captives.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the captor player.
|
|
*
|
|
* @return The player who owns this manager
|
|
*/
|
|
public Player getCaptor() {
|
|
return captor;
|
|
}
|
|
|
|
/**
|
|
* Clean up invalid captives from the list.
|
|
* Removes captives that are no longer valid (offline, transport gone, etc.).
|
|
*
|
|
*
|
|
* Should be called periodically (e.g., on tick).
|
|
*/
|
|
public void cleanupInvalidCaptives() {
|
|
captives.removeIf(captive -> {
|
|
if (captive == null) {
|
|
return true;
|
|
}
|
|
|
|
// Remove if not actually captured anymore
|
|
if (!captive.isCaptive()) {
|
|
TiedUpMod.LOGGER.debug(
|
|
"[PlayerCaptorManager] Removing invalid captive {}",
|
|
captive.getKidnappedName()
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// Remove if captured by different captor
|
|
if (captive.getCaptor() != this) {
|
|
TiedUpMod.LOGGER.debug(
|
|
"[PlayerCaptorManager] Removing captive {} (belongs to different captor)",
|
|
captive.getKidnappedName()
|
|
);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
// Backward Compatibility
|
|
}
|