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.
311 lines
9.1 KiB
Java
311 lines
9.1 KiB
Java
package com.tiedup.remake.entities;
|
|
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import com.tiedup.remake.items.ModItems;
|
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
|
import com.tiedup.remake.state.CollarRegistry;
|
|
import com.tiedup.remake.state.IBondageState;
|
|
import com.tiedup.remake.util.tasks.ItemTask;
|
|
import com.tiedup.remake.util.tasks.JobLoader;
|
|
import com.tiedup.remake.v2.BodyRegionV2;
|
|
import java.util.UUID;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
/**
|
|
* Manages the job system for EntityKidnapper.
|
|
*
|
|
* <p>The job system allows kidnappers to assign tasks to their captives.
|
|
* When a job is assigned:
|
|
* <ol>
|
|
* <li>Worker UUID is stored for tracking</li>
|
|
* <li>Shock collar is put on the worker</li>
|
|
* <li>Worker is untied and freed to complete the job</li>
|
|
* <li>Kidnapper waits for job completion</li>
|
|
* </ol>
|
|
*
|
|
* <p>This system IS persisted to NBT via {@link #save(CompoundTag)} and {@link #load(CompoundTag)}.
|
|
*/
|
|
public class KidnapperJobManager {
|
|
|
|
private final EntityKidnapper kidnapper;
|
|
|
|
/** Current job assigned to worker. */
|
|
@Nullable
|
|
private ItemTask currentJob = null;
|
|
|
|
/** UUID of worker doing the current job (freed but tracked). */
|
|
@Nullable
|
|
private UUID jobWorkerUUID = null;
|
|
|
|
public KidnapperJobManager(EntityKidnapper kidnapper) {
|
|
this.kidnapper = kidnapper;
|
|
}
|
|
|
|
// STATE QUERIES
|
|
|
|
/**
|
|
* Check if waiting for worker to complete job.
|
|
* Note: Worker is NOT a captive during job - they are tracked by UUID.
|
|
*/
|
|
public boolean isWaitingForJobToBeCompleted() {
|
|
return this.currentJob != null && this.jobWorkerUUID != null;
|
|
}
|
|
|
|
/**
|
|
* Get the current job assigned to worker.
|
|
*/
|
|
@Nullable
|
|
public ItemTask getCurrentJob() {
|
|
return this.currentJob;
|
|
}
|
|
|
|
/**
|
|
* Get the UUID of the worker doing the current job.
|
|
*/
|
|
@Nullable
|
|
public UUID getJobWorkerUUID() {
|
|
return this.jobWorkerUUID;
|
|
}
|
|
|
|
/**
|
|
* Set the UUID of the worker doing the current job.
|
|
* Used by maids to register labor workers for attack protection.
|
|
*/
|
|
public void setJobWorkerUUID(@Nullable UUID workerUUID) {
|
|
this.jobWorkerUUID = workerUUID;
|
|
}
|
|
|
|
/**
|
|
* Find the job worker entity by UUID.
|
|
* @return The worker as Player, or null if not found/offline
|
|
*/
|
|
@Nullable
|
|
public Player getJobWorker() {
|
|
if (this.jobWorkerUUID == null) {
|
|
return null;
|
|
}
|
|
return kidnapper.level().getPlayerByUUID(this.jobWorkerUUID);
|
|
}
|
|
|
|
// JOB ASSIGNMENT
|
|
|
|
/**
|
|
* Assign a job to the kidnapper's current captive.
|
|
* This will:
|
|
* <ol>
|
|
* <li>Store the worker UUID</li>
|
|
* <li>Untie the captive (remove bind and gag)</li>
|
|
* <li>Free them from captivity</li>
|
|
* <li>Put a shock collar on them (AFTER untie)</li>
|
|
* </ol>
|
|
*
|
|
* @param job The job to assign
|
|
* @return true if job was assigned
|
|
*/
|
|
public boolean assignJob(ItemTask job) {
|
|
if (!kidnapper.hasCaptives() || job == null) {
|
|
return false;
|
|
}
|
|
|
|
if (this.currentJob != null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[KidnapperJobManager] {} already has an active job",
|
|
kidnapper.getNpcName()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
IBondageState captive = kidnapper.getCaptive();
|
|
if (captive == null) {
|
|
return false;
|
|
}
|
|
|
|
// Store job and worker info
|
|
this.currentJob = job;
|
|
this.jobWorkerUUID = captive.asLivingEntity().getUUID();
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[KidnapperJobManager] {} assigned job to {}: {}",
|
|
kidnapper.getNpcName(),
|
|
captive.getKidnappedName(),
|
|
job.toDisplayString()
|
|
);
|
|
|
|
// IMPORTANT: Order matters here!
|
|
// 1. First untie (this clears ALL bondage slots including collar)
|
|
// 2. Then free from captivity
|
|
// 3. Finally put collar on (so it's not cleared by untie)
|
|
|
|
// Untie the worker (remove bind and gag so they can work)
|
|
captive.untie(false);
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[KidnapperJobManager] {} untied {} for job",
|
|
kidnapper.getNpcName(),
|
|
captive.getKidnappedName()
|
|
);
|
|
|
|
// Free from captivity (they keep the collar, we track via UUID)
|
|
captive.free(false);
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[KidnapperJobManager] {} freed {} to complete job",
|
|
kidnapper.getNpcName(),
|
|
captive.getKidnappedName()
|
|
);
|
|
|
|
// Put a shock collar on the worker AFTER untie/free
|
|
ItemStack shockCollar = new ItemStack(ModItems.SHOCK_COLLAR_AUTO.get());
|
|
// Add kidnapper as owner so the collar is linked
|
|
CollarHelper.addOwner(
|
|
shockCollar,
|
|
kidnapper.getUUID(),
|
|
kidnapper.getNpcName()
|
|
);
|
|
// Lock the collar so they can't remove it
|
|
if (shockCollar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
|
|
shockCollar = lockable.setLocked(shockCollar, true);
|
|
}
|
|
captive.equip(BodyRegionV2.NECK, shockCollar);
|
|
|
|
// Register collar in CollarRegistry for tracking
|
|
registerJobCollar(captive.asLivingEntity(), shockCollar);
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[KidnapperJobManager] {} put shock collar (locked, owned) on {}",
|
|
kidnapper.getNpcName(),
|
|
captive.getKidnappedName()
|
|
);
|
|
|
|
// Hold shocker controller
|
|
kidnapper.setItemInHand(
|
|
InteractionHand.MAIN_HAND,
|
|
new ItemStack(ModItems.SHOCKER_CONTROLLER.get())
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Assign a random job using JobLoader.
|
|
*
|
|
* @return true if job was assigned
|
|
*/
|
|
public boolean assignRandomJob() {
|
|
return assignJob(JobLoader.getRandomJob());
|
|
}
|
|
|
|
/**
|
|
* Clear the current job and worker tracking.
|
|
*/
|
|
public void clearCurrentJob() {
|
|
if (this.currentJob != null || this.jobWorkerUUID != null) {
|
|
TiedUpMod.LOGGER.info(
|
|
"[KidnapperJobManager] {} cleared job",
|
|
kidnapper.getNpcName()
|
|
);
|
|
this.currentJob = null;
|
|
this.jobWorkerUUID = null;
|
|
|
|
// Clear held shocker controller
|
|
kidnapper.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY);
|
|
}
|
|
}
|
|
|
|
// COLLAR REGISTRY
|
|
|
|
/**
|
|
* Register a job collar in the CollarRegistry.
|
|
* This allows tracking the worker via the collar system.
|
|
*
|
|
* @param wearer The entity wearing the collar
|
|
* @param collarStack The collar ItemStack
|
|
*/
|
|
private void registerJobCollar(LivingEntity wearer, ItemStack collarStack) {
|
|
if (wearer == null || kidnapper.level().isClientSide()) {
|
|
return;
|
|
}
|
|
|
|
if (!(kidnapper.level() instanceof ServerLevel serverLevel)) {
|
|
return;
|
|
}
|
|
|
|
CollarRegistry registry = CollarRegistry.get(serverLevel);
|
|
if (registry == null) {
|
|
return;
|
|
}
|
|
|
|
// Register kidnapper as owner of the wearer
|
|
registry.registerCollar(wearer.getUUID(), kidnapper.getUUID());
|
|
|
|
TiedUpMod.LOGGER.debug(
|
|
"[KidnapperJobManager] Registered job collar for {} owned by {}",
|
|
wearer.getName().getString(),
|
|
kidnapper.getNpcName()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Unregister a job collar from the CollarRegistry.
|
|
* Called when the job is completed and collar is removed.
|
|
*
|
|
* @param wearer The entity whose collar is being removed
|
|
*/
|
|
public void unregisterJobCollar(LivingEntity wearer) {
|
|
if (wearer == null || kidnapper.level().isClientSide()) {
|
|
return;
|
|
}
|
|
|
|
if (!(kidnapper.level() instanceof ServerLevel serverLevel)) {
|
|
return;
|
|
}
|
|
|
|
CollarRegistry registry = CollarRegistry.get(serverLevel);
|
|
if (registry == null) {
|
|
return;
|
|
}
|
|
|
|
// Unregister the wearer
|
|
registry.unregisterWearer(wearer.getUUID());
|
|
|
|
TiedUpMod.LOGGER.debug(
|
|
"[KidnapperJobManager] Unregistered job collar for {}",
|
|
wearer.getName().getString()
|
|
);
|
|
}
|
|
|
|
// NBT PERSISTENCE
|
|
|
|
/**
|
|
* Save job manager state to NBT.
|
|
* @param tag The tag to save to
|
|
*/
|
|
public void save(CompoundTag tag) {
|
|
if (this.currentJob != null) {
|
|
tag.put("CurrentJob", this.currentJob.save());
|
|
}
|
|
if (this.jobWorkerUUID != null) {
|
|
tag.putUUID("JobWorkerUUID", this.jobWorkerUUID);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load job manager state from NBT.
|
|
* @param tag The tag to load from
|
|
*/
|
|
public void load(CompoundTag tag) {
|
|
if (tag.contains("CurrentJob")) {
|
|
this.currentJob = ItemTask.load(tag.getCompound("CurrentJob"));
|
|
}
|
|
if (tag.contains("JobWorkerUUID")) {
|
|
this.jobWorkerUUID = tag.getUUID("JobWorkerUUID");
|
|
}
|
|
}
|
|
}
|