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.
*
*
The job system allows kidnappers to assign tasks to their captives.
* When a job is assigned:
*
* - Worker UUID is stored for tracking
* - Shock collar is put on the worker
* - Worker is untied and freed to complete the job
* - Kidnapper waits for job completion
*
*
* 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:
*
* - Store the worker UUID
* - Untie the captive (remove bind and gag)
* - Free them from captivity
* - Put a shock collar on them (AFTER untie)
*
*
* @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 = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
new net.minecraft.resources.ResourceLocation("tiedup", "shock_collar_auto"));
// 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");
}
}
}