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: *

    *
  1. Worker UUID is stored for tracking
  2. *
  3. Shock collar is put on the worker
  4. *
  5. Worker is untied and freed to complete the job
  6. *
  7. Kidnapper waits for job completion
  8. *
* *

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: *

    *
  1. Store the worker UUID
  2. *
  3. Untie the captive (remove bind and gag)
  4. *
  5. Free them from captivity
  6. *
  7. Put a shock collar on them (AFTER untie)
  8. *
* * @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"); } } }