Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.goal.GoalSelector;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperAIManager callbacks.
|
||||
* Provides access to entity properties and components needed for AI goal registration.
|
||||
*/
|
||||
public interface IAIHost {
|
||||
// ========================================
|
||||
// NAVIGATION & BASIC
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the entity's navigation system.
|
||||
*/
|
||||
PathNavigation getNavigation();
|
||||
|
||||
/**
|
||||
* Get the goal selector for registering AI goals.
|
||||
*/
|
||||
GoalSelector getGoalSelector();
|
||||
|
||||
/**
|
||||
* Get the entity's level/world.
|
||||
*/
|
||||
Level getLevel();
|
||||
|
||||
// ========================================
|
||||
// STATE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the current behavioral state.
|
||||
*/
|
||||
KidnapperState getCurrentState();
|
||||
|
||||
/**
|
||||
* Set the current behavioral state.
|
||||
*/
|
||||
void setCurrentState(KidnapperState state);
|
||||
|
||||
// ========================================
|
||||
// TARGET MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the current hunt target.
|
||||
*/
|
||||
@Nullable
|
||||
LivingEntity getTarget();
|
||||
|
||||
/**
|
||||
* Set the current hunt target.
|
||||
*/
|
||||
void setTarget(@Nullable LivingEntity target);
|
||||
|
||||
/**
|
||||
* Check if an entity is a suitable target for capture.
|
||||
*/
|
||||
boolean isSuitableTarget(LivingEntity entity);
|
||||
|
||||
/**
|
||||
* Check if target is still valid during an active chase.
|
||||
*/
|
||||
boolean isTargetStillValidForChase(LivingEntity entity);
|
||||
|
||||
/**
|
||||
* Check if kidnapper is close enough to capture target.
|
||||
*/
|
||||
boolean isCloseToTarget();
|
||||
|
||||
/**
|
||||
* Find closest suitable target within radius.
|
||||
*/
|
||||
@Nullable
|
||||
LivingEntity getClosestSuitableTarget(int radius);
|
||||
|
||||
// ========================================
|
||||
// CAPTIVE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the current captive.
|
||||
*/
|
||||
@Nullable
|
||||
IBondageState getCaptive();
|
||||
|
||||
/**
|
||||
* Check if kidnapper has captives.
|
||||
*/
|
||||
boolean hasCaptives();
|
||||
|
||||
// ========================================
|
||||
// AGGRESSION SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the last entity that attacked this kidnapper.
|
||||
*/
|
||||
@Nullable
|
||||
LivingEntity getLastAttacker();
|
||||
|
||||
/**
|
||||
* Get the escaped target if still within memory time.
|
||||
*/
|
||||
@Nullable
|
||||
LivingEntity getEscapedTarget();
|
||||
|
||||
// ========================================
|
||||
// ALERT SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the alert target (escapee being searched for).
|
||||
*/
|
||||
@Nullable
|
||||
LivingEntity getAlertTarget();
|
||||
|
||||
/**
|
||||
* Set the alert target.
|
||||
*/
|
||||
void setAlertTarget(@Nullable LivingEntity target);
|
||||
|
||||
// ========================================
|
||||
// CAMP SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the associated structure UUID for this kidnapper.
|
||||
*/
|
||||
@Nullable
|
||||
UUID getAssociatedStructure();
|
||||
|
||||
/**
|
||||
* Check if this kidnapper is a "hunter" that patrols far from camp.
|
||||
*/
|
||||
boolean isHunter();
|
||||
|
||||
// ========================================
|
||||
// CELL SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get all nearby cells that have prisoners in them.
|
||||
*/
|
||||
List<CellDataV2> getNearbyCellsWithPrisoners();
|
||||
|
||||
/**
|
||||
* Get the PATROL markers near this kidnapper.
|
||||
*/
|
||||
List<BlockPos> getNearbyPatrolMarkers(int radius);
|
||||
|
||||
// ========================================
|
||||
// SALE & JOB SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if currently selling captive.
|
||||
*/
|
||||
boolean isSellingCaptive();
|
||||
|
||||
/**
|
||||
* Check if waiting for worker to complete job.
|
||||
*/
|
||||
boolean isWaitingForJobToBeCompleted();
|
||||
|
||||
// ========================================
|
||||
// COLLAR CONFIG
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if kidnapping mode is fully ready (enabled + prison set).
|
||||
*/
|
||||
boolean isKidnappingModeReady();
|
||||
|
||||
/**
|
||||
* Get cell ID from collar.
|
||||
*/
|
||||
@Nullable
|
||||
UUID getCellIdFromCollar();
|
||||
|
||||
// ========================================
|
||||
// CAPTURE EQUIPMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Equip themed bind and gag items before capture.
|
||||
*/
|
||||
void setUpHeldItems();
|
||||
|
||||
/**
|
||||
* Get bind item to use for capture.
|
||||
*/
|
||||
ItemStack getBindItem();
|
||||
|
||||
/**
|
||||
* Get gag item to use for capture.
|
||||
*/
|
||||
ItemStack getGagItem();
|
||||
|
||||
// ========================================
|
||||
// STATE FLAGS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if in "get out" state (flee behavior).
|
||||
*/
|
||||
boolean isGetOutState();
|
||||
|
||||
/**
|
||||
* Check if currently dogwalking a prisoner.
|
||||
*/
|
||||
boolean isDogwalking();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperAggressionSystem callbacks.
|
||||
* Provides access to entity information needed for aggression tracking.
|
||||
*/
|
||||
public interface IAggressionHost {
|
||||
/**
|
||||
* Get the world/level for tick timing.
|
||||
*/
|
||||
@Nullable
|
||||
Level level();
|
||||
|
||||
/**
|
||||
* Get the entity's display name for dialogue.
|
||||
*/
|
||||
String getNpcName();
|
||||
|
||||
/**
|
||||
* Trigger alert dialogue when captive escapes.
|
||||
*/
|
||||
void talkToPlayersInRadius(
|
||||
com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory category,
|
||||
int radius
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperAlertManager callbacks.
|
||||
* Provides access to entity properties needed for the alert system.
|
||||
*/
|
||||
public interface IAlertHost {
|
||||
/**
|
||||
* Get the entity's level/world.
|
||||
*/
|
||||
Level level();
|
||||
|
||||
/**
|
||||
* Get the entity's bounding box for area searches.
|
||||
*/
|
||||
AABB getBoundingBox();
|
||||
|
||||
/**
|
||||
* Get the current AI state.
|
||||
*/
|
||||
KidnapperState getCurrentState();
|
||||
|
||||
/**
|
||||
* Set the current AI state.
|
||||
*/
|
||||
void setCurrentState(KidnapperState state);
|
||||
|
||||
/**
|
||||
* Check if this kidnapper has captives.
|
||||
*/
|
||||
boolean hasCaptives();
|
||||
|
||||
/**
|
||||
* Check if this kidnapper is tied up.
|
||||
*/
|
||||
boolean isTiedUp();
|
||||
|
||||
/**
|
||||
* Get the kidnapper's name for logging.
|
||||
*/
|
||||
String getNpcName();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.entities.skins.Gender;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperAppearance callbacks.
|
||||
* Provides access to entity data and configuration needed for appearance management.
|
||||
*/
|
||||
public interface IAppearanceHost {
|
||||
/**
|
||||
* Get the entity's UUID for deterministic skin selection.
|
||||
*/
|
||||
UUID getUUID();
|
||||
|
||||
/**
|
||||
* Get the entity's random source.
|
||||
*/
|
||||
RandomSource getRandom();
|
||||
|
||||
/**
|
||||
* Get the world/level.
|
||||
*/
|
||||
@Nullable
|
||||
Level level();
|
||||
|
||||
/**
|
||||
* Set whether the kidnapper uses slim arms model.
|
||||
*/
|
||||
void setSlimArms(boolean slimArms);
|
||||
|
||||
/**
|
||||
* Set the kidnapper's gender.
|
||||
*/
|
||||
void setGender(Gender gender);
|
||||
|
||||
/**
|
||||
* Set the kidnapper's display name.
|
||||
*/
|
||||
void setNpcName(String name);
|
||||
|
||||
/**
|
||||
* Get the kidnapper's display name.
|
||||
*/
|
||||
String getNpcName();
|
||||
|
||||
/**
|
||||
* Set synced entity data string value.
|
||||
*/
|
||||
void setEntityDataString(EntityDataAccessor<String> accessor, String value);
|
||||
|
||||
/**
|
||||
* Get synced entity data string value.
|
||||
*/
|
||||
String getEntityDataString(EntityDataAccessor<String> accessor);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import net.minecraft.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperCampManager callbacks.
|
||||
* Provides access to random source and name for camp assignment logic.
|
||||
*/
|
||||
public interface ICampHost {
|
||||
/**
|
||||
* Get random source for hunter role assignment.
|
||||
*/
|
||||
RandomSource getRandom();
|
||||
|
||||
/**
|
||||
* Get the kidnapper's name for logging.
|
||||
*/
|
||||
String getNpcName();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperCaptiveManager callbacks.
|
||||
* Provides access to entity properties needed for captive management.
|
||||
*/
|
||||
public interface ICaptiveHost {
|
||||
/**
|
||||
* Get the entity's level/world.
|
||||
*/
|
||||
Level level();
|
||||
|
||||
/**
|
||||
* Get the entity's UUID.
|
||||
*/
|
||||
UUID getUUID();
|
||||
|
||||
/**
|
||||
* Get the entity's position.
|
||||
*/
|
||||
Vec3 position();
|
||||
|
||||
/**
|
||||
* Get the entity's block position.
|
||||
*/
|
||||
BlockPos blockPosition();
|
||||
|
||||
/**
|
||||
* Get the kidnapper's name for logging.
|
||||
*/
|
||||
String getNpcName();
|
||||
|
||||
/**
|
||||
* Set entity data value.
|
||||
*/
|
||||
<T> void setEntityData(EntityDataAccessor<T> accessor, T value);
|
||||
|
||||
/**
|
||||
* Check if this kidnapper is tied up.
|
||||
*/
|
||||
boolean isTiedUp();
|
||||
|
||||
/**
|
||||
* Get the current AI state.
|
||||
*/
|
||||
KidnapperState getCurrentState();
|
||||
|
||||
/**
|
||||
* Set the current AI state.
|
||||
*/
|
||||
void setCurrentState(KidnapperState state);
|
||||
|
||||
/**
|
||||
* Set the "get out" / flee state.
|
||||
*/
|
||||
void setGetOutState(boolean state);
|
||||
|
||||
/**
|
||||
* Trigger dialogue with nearby players.
|
||||
*/
|
||||
void talkToPlayersInRadius(
|
||||
com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory category,
|
||||
int radius
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the aggression system for escape tracking.
|
||||
*/
|
||||
KidnapperAggressionSystem getAggressionSystem();
|
||||
|
||||
/**
|
||||
* Get the alert manager for broadcasting alerts.
|
||||
*/
|
||||
KidnapperAlertManager getAlertManager();
|
||||
|
||||
/**
|
||||
* Broadcast alert about escaped captive.
|
||||
*/
|
||||
void broadcastAlert(LivingEntity escapee);
|
||||
|
||||
/**
|
||||
* Check if has line of sight to entity.
|
||||
*/
|
||||
boolean hasLineOfSight(LivingEntity entity);
|
||||
|
||||
/**
|
||||
* Get distance to entity.
|
||||
*/
|
||||
float distanceTo(LivingEntity entity);
|
||||
|
||||
/**
|
||||
* Find cell containing prisoner.
|
||||
*/
|
||||
@Nullable
|
||||
com.tiedup.remake.cells.CellDataV2 findCellContainingPrisoner(
|
||||
UUID prisonerId
|
||||
);
|
||||
|
||||
/**
|
||||
* Get entity as LivingEntity.
|
||||
*/
|
||||
LivingEntity asLivingEntity();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperCellManager callbacks.
|
||||
* Provides access to entity properties needed for cell management.
|
||||
*/
|
||||
public interface ICellHost {
|
||||
/**
|
||||
* Get the entity's level/world.
|
||||
*/
|
||||
Level level();
|
||||
|
||||
/**
|
||||
* Get the entity's current block position.
|
||||
*/
|
||||
BlockPos blockPosition();
|
||||
|
||||
/**
|
||||
* Get the associated structure UUID for camp-linked kidnappers.
|
||||
*/
|
||||
@Nullable
|
||||
UUID getAssociatedStructure();
|
||||
|
||||
/**
|
||||
* Get the entity's navigation component.
|
||||
*/
|
||||
PathNavigation getNavigation();
|
||||
|
||||
/**
|
||||
* Set the current AI state.
|
||||
*/
|
||||
void setCurrentState(KidnapperState state);
|
||||
|
||||
/**
|
||||
* Trigger dialogue with nearby players.
|
||||
*/
|
||||
void talkToPlayersInRadius(
|
||||
com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory category,
|
||||
int radius
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the kidnapper's name for logging.
|
||||
*/
|
||||
String getNpcName();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.KidnapperJobManager;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperDataSerializer callbacks.
|
||||
* Provides access to entity data and components for NBT persistence.
|
||||
*/
|
||||
public interface IDataSerializerHost {
|
||||
// ========================================
|
||||
// ENTITY ACCESS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the entity instance.
|
||||
*/
|
||||
EntityKidnapper getEntity();
|
||||
|
||||
// ========================================
|
||||
// ENTITY DATA ACCESS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get entity data value.
|
||||
*/
|
||||
<T> T getEntityData(EntityDataAccessor<T> accessor);
|
||||
|
||||
/**
|
||||
* Set entity data value.
|
||||
*/
|
||||
<T> void setEntityData(EntityDataAccessor<T> accessor, T value);
|
||||
|
||||
// ========================================
|
||||
// STATE FLAGS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if in "get out" state.
|
||||
*/
|
||||
boolean isGetOutState();
|
||||
|
||||
/**
|
||||
* Set "get out" state.
|
||||
*/
|
||||
void setGetOutState(boolean state);
|
||||
|
||||
// ========================================
|
||||
// COMPONENT ACCESS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get appearance manager.
|
||||
*/
|
||||
KidnapperAppearance getAppearance();
|
||||
|
||||
/**
|
||||
* Get aggression system.
|
||||
*/
|
||||
KidnapperAggressionSystem getAggressionSystem();
|
||||
|
||||
/**
|
||||
* Get captive manager.
|
||||
*/
|
||||
KidnapperCaptiveManager getCaptiveManager();
|
||||
|
||||
/**
|
||||
* Get state manager.
|
||||
*/
|
||||
KidnapperStateManager getStateManager();
|
||||
|
||||
/**
|
||||
* Get camp manager.
|
||||
*/
|
||||
KidnapperCampManager getCampManager();
|
||||
|
||||
/**
|
||||
* Get job manager.
|
||||
*/
|
||||
KidnapperJobManager getJobManager();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.state.IRestrainable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperSaleManager callbacks.
|
||||
* Provides access to entity properties needed for sale management.
|
||||
*/
|
||||
public interface ISaleHost {
|
||||
/**
|
||||
* Get the entity's level/world.
|
||||
*/
|
||||
Level level();
|
||||
|
||||
/**
|
||||
* Check if this kidnapper has captives.
|
||||
*/
|
||||
boolean hasCaptives();
|
||||
|
||||
/**
|
||||
* Get the current captive.
|
||||
*/
|
||||
@Nullable
|
||||
IRestrainable getCaptive();
|
||||
|
||||
/**
|
||||
* Get the kidnapper's name for logging.
|
||||
*/
|
||||
String getNpcName();
|
||||
|
||||
/**
|
||||
* Set the "get out" / flee state after completing a sale.
|
||||
*/
|
||||
void setGetOutState(boolean state);
|
||||
|
||||
/**
|
||||
* Get the captive transfer flag.
|
||||
*/
|
||||
boolean getAllowCaptiveTransferFlag();
|
||||
|
||||
/**
|
||||
* Set the captive transfer flag.
|
||||
*/
|
||||
void setAllowCaptiveTransferFlag(boolean flag);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperStateManager callbacks.
|
||||
* Provides access to entity information needed for state management logging.
|
||||
*/
|
||||
public interface IStateHost {
|
||||
/**
|
||||
* Get the name of this kidnapper for logging purposes.
|
||||
*
|
||||
* @return The kidnapper's name
|
||||
*/
|
||||
String getNpcName();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.entities.KidnapperCollarConfig;
|
||||
import com.tiedup.remake.entities.KidnapperJobManager;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.entity.ai.sensing.Sensing;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* Host interface for KidnapperTargetSelector callbacks.
|
||||
* Provides access to entity properties needed for target selection and validation.
|
||||
*/
|
||||
public interface ITargetHost {
|
||||
/**
|
||||
* Get the entity's level/world.
|
||||
*/
|
||||
Level level();
|
||||
|
||||
/**
|
||||
* Get the entity's sensing component for line-of-sight checks.
|
||||
*/
|
||||
Sensing getSensing();
|
||||
|
||||
/**
|
||||
* Get the entity's bounding box for area searches.
|
||||
*/
|
||||
AABB getBoundingBox();
|
||||
|
||||
/**
|
||||
* Calculate distance to another entity.
|
||||
*/
|
||||
float distanceTo(LivingEntity entity);
|
||||
|
||||
/**
|
||||
* Get the entity's UUID.
|
||||
*/
|
||||
UUID getUUID();
|
||||
|
||||
/**
|
||||
* Check if this kidnapper is tied up.
|
||||
*/
|
||||
boolean isTiedUp();
|
||||
|
||||
/**
|
||||
* Check if this kidnapper has captives.
|
||||
*/
|
||||
boolean hasCaptives();
|
||||
|
||||
/**
|
||||
* Check if waiting for a job to be completed.
|
||||
*/
|
||||
boolean isWaitingForJobToBeCompleted();
|
||||
|
||||
/**
|
||||
* Check if in "get out" / fleeing state.
|
||||
*/
|
||||
boolean isGetOutState();
|
||||
|
||||
/**
|
||||
* Check if kidnapping mode is enabled.
|
||||
*/
|
||||
boolean isKidnappingModeEnabled();
|
||||
|
||||
/**
|
||||
* Get the associated structure UUID for camp-linked kidnappers.
|
||||
*/
|
||||
@Nullable
|
||||
UUID getAssociatedStructure();
|
||||
|
||||
/**
|
||||
* Get the entity's navigation component.
|
||||
*/
|
||||
PathNavigation getNavigation();
|
||||
|
||||
/**
|
||||
* Get the aggression system for robbery immunity checks.
|
||||
*/
|
||||
KidnapperAggressionSystem getAggressionSystem();
|
||||
|
||||
/**
|
||||
* Get the job manager for worker UUID checks.
|
||||
*/
|
||||
KidnapperJobManager getJobManager();
|
||||
|
||||
/**
|
||||
* Get the collar config for kidnapping mode validation.
|
||||
*/
|
||||
KidnapperCollarConfig getCollarConfig();
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.*;
|
||||
import net.minecraft.world.entity.ai.goal.*;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
/**
|
||||
* KidnapperAIManager - Manages AI goal registration.
|
||||
* Phase 3.2: AI management component (~79 lines).
|
||||
*
|
||||
* Handles:
|
||||
* - Registering all 20 AI goals with priorities
|
||||
* - Does NOT call super() - kidnappers have completely different AI than damsels
|
||||
*
|
||||
* <p><b>Low complexity</b> - Straightforward goal registration.</p>
|
||||
*/
|
||||
public class KidnapperAIManager {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Entity reference (needed for goal constructors) */
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
/** Host callbacks */
|
||||
private final IAIHost host;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperAIManager(EntityKidnapper entity, IAIHost host) {
|
||||
this.entity = entity;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// AI GOAL REGISTRATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Register all AI goals for the kidnapper.
|
||||
* CRITICAL: Does NOT call super() - kidnappers have completely different AI than damsels.
|
||||
*/
|
||||
public void registerGoals() {
|
||||
// Don't call super - kidnapper has completely different AI than damsel
|
||||
|
||||
// Priority 0: Always swim
|
||||
host.getGoalSelector().addGoal(0, new FloatGoal(this.entity));
|
||||
|
||||
// Priority 1: Fight back if attacked while holding captive (MEDIUM FIX: higher priority - self defense)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(1, new KidnapperFightBackGoal(this.entity));
|
||||
|
||||
// Priority 1: Self-defense when attacked without captives (prevents PROTECTED exploit)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(1, new KidnapperSelfDefenseGoal(this.entity));
|
||||
|
||||
// Priority 2: Hunt monsters (camp kidnappers only - protect captives)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(2, new KidnapperHuntMonstersGoal(this.entity));
|
||||
|
||||
// Priority 3: ALERT - Search for escaped prisoner (high priority) (MEDIUM FIX: bumped from 2 to 3)
|
||||
host.getGoalSelector().addGoal(3, new KidnapperAlertGoal(this.entity));
|
||||
|
||||
// Priority 3: Find and capture targets
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(3, new KidnapperFindTargetGoal(this.entity, 25));
|
||||
|
||||
// Priority 3: PUNISH - Punish recaptured prisoner (after Alert, before Capture)
|
||||
host.getGoalSelector().addGoal(3, new KidnapperPunishGoal(this.entity));
|
||||
|
||||
// Priority 4: Capture target
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(4, new KidnapperCaptureGoal(this.entity));
|
||||
|
||||
// Priority 4: Recapture escaped captives (same priority as capture, but checked after)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(4, new KidnapperRecaptureGoal(this.entity));
|
||||
|
||||
// Priority 5: Bring slave to cell (unified goal handles both camp and player cells)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(5, new KidnapperBringToCellGoal(this.entity));
|
||||
|
||||
// Priority 6: Decide what to do with slave (sell or job)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(6, new KidnapperDecideNextActionGoal(this.entity));
|
||||
|
||||
// Priority 6: Dispersal - Force movement away when too many near camp center
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(6, new KidnapperDispersalGoal(this.entity));
|
||||
|
||||
// Priority 7: GUARD - Watch over occupied cells
|
||||
host.getGoalSelector().addGoal(7, new KidnapperGuardGoal(this.entity));
|
||||
|
||||
// Priority 7: Thief behavior for wild kidnappers (steal items and flee)
|
||||
// BUG FIX (Phase 2): DISABLED - Integrated into DecideNextActionGoal
|
||||
// Problem: ThiefGoal (P7) never executed because DecideNextActionGoal (P6)
|
||||
// finished first and changed state (sell/job), blocking ThiefGoal activation.
|
||||
// Solution: Integrated theft as a 3rd option (30% chance) in DecideNextActionGoal.
|
||||
// The standalone ThiefGoal is now DEAD CODE and has been disabled.
|
||||
// host.getGoalSelector().addGoal(7, new KidnapperThiefGoal(this.entity));
|
||||
|
||||
// Priority 7: Walk prisoner (DISABLED - chunk unload loses goal state,
|
||||
// causing prisoners to be sold/stolen instead of returned to cell)
|
||||
// host.getGoalSelector().addGoal(7, new KidnapperWalkPrisonerGoal(this.entity));
|
||||
|
||||
// Priority 8: Patrol when no slave (close to camp)
|
||||
host.getGoalSelector().addGoal(8, new KidnapperPatrolGoal(this.entity));
|
||||
|
||||
// Priority 8: Hunt far from camp (only for hunters)
|
||||
host.getGoalSelector().addGoal(8, new KidnapperHuntGoal(this.entity));
|
||||
|
||||
// Priority 9-10: Flee behavior (getOutState)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(9, new KidnapperFleeWithCaptiveGoal(this.entity));
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(10, new KidnapperFleeSafeGoal(this.entity));
|
||||
|
||||
// Priority 11-12: Sale and Job systems
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(11, new KidnapperWaitForBuyerGoal(this.entity));
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(12, new KidnapperWaitForJobGoal(this.entity));
|
||||
|
||||
// Priority 13-15: Basic movement (lower priority)
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(13, new WaterAvoidingRandomStrollGoal(this.entity, 1.0D));
|
||||
// Use custom LookAtPlayerGoal that ignores players in cells
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(
|
||||
14,
|
||||
new KidnapperLookAtPlayerGoal(this.entity, Player.class, 8.0F)
|
||||
);
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(15, new RandomLookAroundGoal(this.entity));
|
||||
|
||||
// Priority 16: Open doors
|
||||
host
|
||||
.getGoalSelector()
|
||||
.addGoal(16, new OpenDoorGoal(this.entity, false));
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[EntityKidnapper] Registered {} AI goals (ThiefGoal disabled)",
|
||||
20
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.EntityDialogueManager;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
/**
|
||||
* KidnapperAggressionSystem - Manages aggression tracking and immunity.
|
||||
* Phase 1.3: Aggression management component (151 lines).
|
||||
*
|
||||
* Handles three aggression subsystems:
|
||||
* 1. **Escaped Target Memory** - Remember escapees for pursuit (30 seconds)
|
||||
* 2. **Fight Back System** - Track last attacker for retaliation (5 seconds)
|
||||
* 3. **Robbery Immunity** - Prevent repeated targeting after robbery (2 minutes)
|
||||
*
|
||||
* <p><b>Low complexity</b> - Simple state tracking with time-based expiration.</p>
|
||||
*/
|
||||
public class KidnapperAggressionSystem {
|
||||
|
||||
// ========================================
|
||||
// CONSTANTS
|
||||
// ========================================
|
||||
|
||||
/** How long to remember an escaped target (30 seconds = 600 ticks) */
|
||||
private static final long ESCAPED_TARGET_MEMORY = 600;
|
||||
|
||||
/** Duration of robbed immunity (2 minutes = 2400 ticks) */
|
||||
private static final long ROBBED_IMMUNITY_DURATION = 2400L;
|
||||
|
||||
/** How long to remember last attacker for fight-back (5 seconds = 100 ticks) */
|
||||
private static final int FIGHT_BACK_MEMORY = 100;
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final IAggressionHost host;
|
||||
|
||||
// Escaped Target Tracking
|
||||
/** Entity that escaped from this kidnapper */
|
||||
@Nullable
|
||||
private LivingEntity escapedTarget = null;
|
||||
|
||||
/** Tick count when captive escaped */
|
||||
private long escapedTargetTime = 0;
|
||||
|
||||
// Fight Back System
|
||||
/** Last entity that attacked this kidnapper */
|
||||
@Nullable
|
||||
private LivingEntity lastAttacker = null;
|
||||
|
||||
/** Tick count when last attacked */
|
||||
private int lastAttackTime = 0;
|
||||
|
||||
// Robbery Immunity System
|
||||
/**
|
||||
* Map of player UUIDs to game time when immunity expires.
|
||||
* Players robbed by this kidnapper cannot be targeted again for 2 minutes.
|
||||
*/
|
||||
private final Map<UUID, Long> robbedImmunity = new HashMap<>();
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperAggressionSystem(IAggressionHost host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ESCAPED TARGET SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Called when a captive escapes from this kidnapper.
|
||||
* Records the escaped entity for potential recapture.
|
||||
*
|
||||
* @param escaped The entity that escaped (IBondageState or LivingEntity)
|
||||
*/
|
||||
public void onCaptiveEscaped(@Nullable Object escaped) {
|
||||
LivingEntity escapee = null;
|
||||
|
||||
if (escaped instanceof IBondageState kidnapped) {
|
||||
if (kidnapped instanceof LivingEntity living) {
|
||||
escapee = living;
|
||||
}
|
||||
} else if (escaped instanceof LivingEntity living) {
|
||||
escapee = living;
|
||||
}
|
||||
|
||||
if (escapee != null) {
|
||||
this.escapedTarget = escapee;
|
||||
this.escapedTargetTime = getCurrentTick();
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperAggressionSystem] {} remembering escapee: {}",
|
||||
host.getNpcName(),
|
||||
escapee.getName().getString()
|
||||
);
|
||||
|
||||
// Trigger alert dialogue
|
||||
host.talkToPlayersInRadius(
|
||||
EntityDialogueManager.DialogueCategory.CAPTURE_ESCAPE,
|
||||
20
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the escaped target if still remembered.
|
||||
* Returns null if memory expired or target is dead/removed.
|
||||
*
|
||||
* @return The escaped entity, or null if forgotten/dead
|
||||
*/
|
||||
@Nullable
|
||||
public LivingEntity getEscapedTarget() {
|
||||
if (this.escapedTarget == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if memory expired (30 seconds)
|
||||
long currentTick = getCurrentTick();
|
||||
if (currentTick - this.escapedTargetTime > ESCAPED_TARGET_MEMORY) {
|
||||
this.escapedTarget = null;
|
||||
this.escapedTargetTime = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if target is still valid
|
||||
if (!this.escapedTarget.isAlive() || this.escapedTarget.isRemoved()) {
|
||||
this.escapedTarget = null;
|
||||
this.escapedTargetTime = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.escapedTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the escaped target memory.
|
||||
*/
|
||||
public void clearEscapedTarget() {
|
||||
this.escapedTarget = null;
|
||||
this.escapedTargetTime = 0;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// FIGHT BACK SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the last entity that attacked this kidnapper.
|
||||
* Returns null if attack was more than 5 seconds ago.
|
||||
*
|
||||
* @return The attacker if recent, null otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public LivingEntity getLastAttacker() {
|
||||
if (this.lastAttacker == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if attack was recent (5 seconds = 100 ticks)
|
||||
int currentTick = (int) getCurrentTick();
|
||||
if (currentTick - this.lastAttackTime > FIGHT_BACK_MEMORY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.lastAttacker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last attacker for fight-back mechanics.
|
||||
*
|
||||
* @param attacker The entity that attacked
|
||||
*/
|
||||
public void setLastAttacker(LivingEntity attacker) {
|
||||
this.lastAttacker = attacker;
|
||||
this.lastAttackTime = (int) getCurrentTick();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ROBBERY IMMUNITY SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Grant robbery immunity to a player for 2 minutes.
|
||||
* Players with immunity cannot be targeted by this kidnapper.
|
||||
*
|
||||
* @param playerUUID The player's UUID
|
||||
*/
|
||||
public void grantRobbedImmunity(UUID playerUUID) {
|
||||
long expirationTime = getCurrentTick() + ROBBED_IMMUNITY_DURATION;
|
||||
this.robbedImmunity.put(playerUUID, expirationTime);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperAggressionSystem] {} granted robbery immunity to player {}",
|
||||
host.getNpcName(),
|
||||
playerUUID
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player has active robbery immunity.
|
||||
* Lazily removes expired immunity during check.
|
||||
*
|
||||
* @param playerUUID The player's UUID
|
||||
* @return true if player has active immunity
|
||||
*/
|
||||
public boolean hasRobbedImmunity(UUID playerUUID) {
|
||||
Long expirationTime = this.robbedImmunity.get(playerUUID);
|
||||
if (expirationTime == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if immunity expired (lazy cleanup)
|
||||
long currentTick = getCurrentTick();
|
||||
if (currentTick >= expirationTime) {
|
||||
this.robbedImmunity.remove(playerUUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear robbery immunity for a specific player.
|
||||
*
|
||||
* @param playerUUID The player's UUID
|
||||
*/
|
||||
public void clearRobbedImmunity(UUID playerUUID) {
|
||||
this.robbedImmunity.remove(playerUUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all robbery immunities (e.g., on death/removal).
|
||||
*/
|
||||
public void clearAllRobbedImmunities() {
|
||||
this.robbedImmunity.clear();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// HELPER METHODS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get current game tick from world.
|
||||
*/
|
||||
private long getCurrentTick() {
|
||||
if (host.level() != null) {
|
||||
return host.level().getGameTime();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save aggression data to NBT.
|
||||
* Note: Only saves robbery immunity map, not transient tracking fields.
|
||||
*/
|
||||
public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Save robbery immunity map
|
||||
if (!this.robbedImmunity.isEmpty()) {
|
||||
net.minecraft.nbt.CompoundTag immunityTag =
|
||||
new net.minecraft.nbt.CompoundTag();
|
||||
for (Map.Entry<UUID, Long> entry : this.robbedImmunity.entrySet()) {
|
||||
immunityTag.putLong(
|
||||
entry.getKey().toString(),
|
||||
entry.getValue()
|
||||
);
|
||||
}
|
||||
tag.put("RobbedImmunity", immunityTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load aggression data from NBT.
|
||||
*/
|
||||
public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Load robbery immunity map
|
||||
if (tag.contains("RobbedImmunity")) {
|
||||
net.minecraft.nbt.CompoundTag immunityTag = tag.getCompound(
|
||||
"RobbedImmunity"
|
||||
);
|
||||
for (String key : immunityTag.getAllKeys()) {
|
||||
try {
|
||||
UUID playerUUID = UUID.fromString(key);
|
||||
long expirationTime = immunityTag.getLong(key);
|
||||
this.robbedImmunity.put(playerUUID, expirationTime);
|
||||
} catch (IllegalArgumentException e) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperAggressionSystem] Invalid UUID in robbery immunity: {}",
|
||||
key
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
/**
|
||||
* KidnapperAlertManager - Manages alert system between kidnappers.
|
||||
* Phase 2.1: Alert management component (~75 lines).
|
||||
*
|
||||
* Handles:
|
||||
* - Broadcasting alerts about escaped captives to nearby kidnappers
|
||||
* - Receiving alert broadcasts from other kidnappers
|
||||
* - Alert cooldown to prevent spam (5 second cooldown = 100 ticks)
|
||||
* - Alert target tracking for ALERT state
|
||||
*
|
||||
* <p><b>Low complexity</b> - Simple broadcast/receive pattern with cooldown.</p>
|
||||
*/
|
||||
public class KidnapperAlertManager {
|
||||
|
||||
// ========================================
|
||||
// CONSTANTS
|
||||
// ========================================
|
||||
|
||||
/** Cooldown duration before receiving another alert (5 seconds = 100 ticks) */
|
||||
private static final int ALERT_COOLDOWN_DURATION = 100;
|
||||
|
||||
/** Broadcast radius for alerts (50 blocks horizontal, 20 blocks vertical) */
|
||||
private static final double BROADCAST_RADIUS_HORIZONTAL = 50.0;
|
||||
private static final double BROADCAST_RADIUS_VERTICAL = 20.0;
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final IAlertHost host;
|
||||
|
||||
/** Entity reference (needed for getEntitiesOfClass filtering) */
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
/** Target entity being searched for during ALERT state */
|
||||
@Nullable
|
||||
private LivingEntity alertTarget;
|
||||
|
||||
/** Cooldown before receiving another alert broadcast (in ticks) */
|
||||
private int alertCooldown = 0;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperAlertManager(EntityKidnapper entity, IAlertHost host) {
|
||||
this.entity = entity;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ALERT TARGET TRACKING
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the current alert target.
|
||||
*
|
||||
* @return The target entity being searched for, or null if none
|
||||
*/
|
||||
@Nullable
|
||||
public LivingEntity getAlertTarget() {
|
||||
return this.alertTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alert target.
|
||||
*
|
||||
* @param target The target to search for
|
||||
*/
|
||||
public void setAlertTarget(@Nullable LivingEntity target) {
|
||||
this.alertTarget = target;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ALERT BROADCASTING
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Broadcast an alert about an escaped captive to nearby kidnappers.
|
||||
* Searches for kidnappers within 50 blocks horizontally and 20 blocks vertically.
|
||||
* Only kidnappers in states that can receive alerts will be notified.
|
||||
*
|
||||
* @param escapee The entity that escaped
|
||||
*/
|
||||
public void broadcastAlert(LivingEntity escapee) {
|
||||
if (!(host.level() instanceof ServerLevel serverLevel)) {
|
||||
return;
|
||||
}
|
||||
if (escapee == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearby kidnappers that can receive alerts
|
||||
List<EntityKidnapper> nearby = serverLevel.getEntitiesOfClass(
|
||||
EntityKidnapper.class,
|
||||
host
|
||||
.getBoundingBox()
|
||||
.inflate(
|
||||
BROADCAST_RADIUS_HORIZONTAL,
|
||||
BROADCAST_RADIUS_VERTICAL,
|
||||
BROADCAST_RADIUS_HORIZONTAL
|
||||
),
|
||||
k -> k != this.entity && k.getCurrentState().canReceiveAlerts()
|
||||
);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperAlertManager] {} broadcasting alert about {} to {} kidnappers",
|
||||
host.getNpcName(),
|
||||
escapee.getName().getString(),
|
||||
nearby.size()
|
||||
);
|
||||
|
||||
// Send alert to each nearby kidnapper
|
||||
for (EntityKidnapper other : nearby) {
|
||||
other.receiveAlertBroadcast(escapee, this.entity);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ALERT RECEIVING
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Receive an alert broadcast from another kidnapper.
|
||||
* Ignores alerts if:
|
||||
* - On cooldown (prevents spam)
|
||||
* - Already has captives
|
||||
* - Is tied up
|
||||
*
|
||||
* If accepted, sets alert target and switches to ALERT state.
|
||||
*
|
||||
* @param escapee The entity that escaped
|
||||
* @param source The kidnapper that sent the alert
|
||||
*/
|
||||
public void receiveAlertBroadcast(
|
||||
LivingEntity escapee,
|
||||
EntityKidnapper source
|
||||
) {
|
||||
// On cooldown, ignore alert
|
||||
if (this.alertCooldown > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't respond if we already have a captive or are tied up
|
||||
if (host.hasCaptives() || host.isTiedUp()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set alert target and state
|
||||
this.setAlertTarget(escapee);
|
||||
host.setCurrentState(KidnapperState.ALERT);
|
||||
this.alertCooldown = ALERT_COOLDOWN_DURATION; // 5 second cooldown
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperAlertManager] {} received alert from {} about {}",
|
||||
host.getNpcName(),
|
||||
source.getNpcName(),
|
||||
escapee.getName().getString()
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TICK PROCESSING
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Called each tick to update alert system state.
|
||||
* Decrements the alert cooldown timer.
|
||||
*/
|
||||
public void tick() {
|
||||
// Decrement alert cooldown
|
||||
if (this.alertCooldown > 0) {
|
||||
this.alertCooldown--;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save alert data to NBT.
|
||||
* Note: alertTarget is transient and not saved (recalculated on load).
|
||||
* Note: alertCooldown is transient and not saved (resets on load).
|
||||
*/
|
||||
public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Alert system uses only transient runtime state
|
||||
// No persistent data needs to be saved
|
||||
}
|
||||
|
||||
/**
|
||||
* Load alert data from NBT.
|
||||
* Note: alertTarget is transient and not loaded.
|
||||
* Note: alertCooldown is transient and not loaded.
|
||||
*/
|
||||
public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Alert system uses only transient runtime state
|
||||
// No persistent data needs to be loaded
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.KidnapperItemSelector;
|
||||
import com.tiedup.remake.entities.KidnapperTheme;
|
||||
import com.tiedup.remake.entities.KidnapperVariant;
|
||||
import com.tiedup.remake.entities.skins.Gender;
|
||||
import com.tiedup.remake.entities.skins.KidnapperSkinManager;
|
||||
import com.tiedup.remake.items.base.ItemColor;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* KidnapperAppearance - Manages visual appearance and item selection.
|
||||
* Phase 1.2: Appearance management component (~220 lines).
|
||||
*
|
||||
* Handles:
|
||||
* - Kidnapper skin variants (texture selection)
|
||||
* - Item themes (bind/gag sets)
|
||||
* - Theme colors
|
||||
* - Item selection results
|
||||
* - Initialization and persistence
|
||||
*
|
||||
* <p><b>Medium complexity</b> - Lazy loading, synced data integration.</p>
|
||||
*/
|
||||
public class KidnapperAppearance {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Parent entity for accessing static data accessors */
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
/** Host callbacks */
|
||||
private final IAppearanceHost host;
|
||||
|
||||
/** DATA accessors passed in (cannot be moved due to Minecraft static requirements) */
|
||||
private final EntityDataAccessor<String> dataVariantId;
|
||||
private final EntityDataAccessor<String> dataTheme;
|
||||
private final EntityDataAccessor<String> dataThemeColor;
|
||||
|
||||
/** Current kidnapper skin variant (volatile for thread-safe render access) */
|
||||
@Nullable
|
||||
private volatile KidnapperVariant kidnapperVariant;
|
||||
|
||||
/** Current item theme */
|
||||
@Nullable
|
||||
private KidnapperTheme currentTheme;
|
||||
|
||||
/** Current theme color */
|
||||
@Nullable
|
||||
private ItemColor themeColor;
|
||||
|
||||
/** Selected items for this kidnapper */
|
||||
@Nullable
|
||||
private KidnapperItemSelector.SelectionResult itemSelection;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperAppearance(
|
||||
EntityKidnapper entity,
|
||||
IAppearanceHost host,
|
||||
EntityDataAccessor<String> dataVariantId,
|
||||
EntityDataAccessor<String> dataTheme,
|
||||
EntityDataAccessor<String> dataThemeColor
|
||||
) {
|
||||
this.entity = entity;
|
||||
this.host = host;
|
||||
this.dataVariantId = dataVariantId;
|
||||
this.dataTheme = dataTheme;
|
||||
this.dataThemeColor = dataThemeColor;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// VARIANT SYSTEM
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Set kidnapper variant and name.
|
||||
*/
|
||||
public void setKidnapperVariant(KidnapperVariant variant) {
|
||||
if (variant == null) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperAppearance] Attempted to set null kidnapper variant"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.kidnapperVariant = variant;
|
||||
host.setEntityDataString(dataVariantId, variant.id());
|
||||
host.setSlimArms(variant.hasSlimArms());
|
||||
host.setGender(variant.gender());
|
||||
applyVariantName(variant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current kidnapper variant.
|
||||
*/
|
||||
@Nullable
|
||||
public KidnapperVariant getKidnapperVariant() {
|
||||
// Try to load from synced variant ID first
|
||||
if (
|
||||
this.kidnapperVariant == null && !getKidnapperVariantId().isEmpty()
|
||||
) {
|
||||
this.kidnapperVariant = lookupVariantById(getKidnapperVariantId());
|
||||
}
|
||||
// Fallback: compute from UUID if not yet synced (client-side race condition fix)
|
||||
if (this.kidnapperVariant == null) {
|
||||
this.kidnapperVariant = computeVariantForEntity(host.getUUID());
|
||||
}
|
||||
return this.kidnapperVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a variant by ID from the skin manager.
|
||||
* Delegates to entity's virtual method to allow subclass overrides.
|
||||
*/
|
||||
protected KidnapperVariant lookupVariantById(String variantId) {
|
||||
return entity.lookupVariantById(variantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute which variant to use based on UUID.
|
||||
* Delegates to entity's virtual method to allow subclass overrides.
|
||||
*/
|
||||
protected KidnapperVariant computeVariantForEntity(UUID entityUUID) {
|
||||
return entity.computeVariantForEntity(entityUUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the texture folder for this kidnapper type.
|
||||
* Delegates to entity's virtual method to allow subclass overrides.
|
||||
*/
|
||||
protected String getVariantTextureFolder() {
|
||||
return entity.getVariantTextureFolder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default variant ID for fallback.
|
||||
* Delegates to entity's virtual method to allow subclass overrides.
|
||||
*/
|
||||
protected String getDefaultVariantId() {
|
||||
return entity.getDefaultVariantId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the name from variant to this entity.
|
||||
* Delegates to entity's virtual method to allow subclass overrides.
|
||||
*/
|
||||
protected void applyVariantName(KidnapperVariant variant) {
|
||||
entity.applyVariantName(variant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NBT key for saving the variant.
|
||||
* Delegates to entity's virtual method to allow subclass overrides.
|
||||
*/
|
||||
protected String getVariantNBTKey() {
|
||||
return entity.getVariantNBTKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get kidnapper variant ID (synced data).
|
||||
*/
|
||||
public String getKidnapperVariantId() {
|
||||
return host.getEntityDataString(dataVariantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skin texture resource location.
|
||||
* Reads variant ID directly from synced entityData to work on LAN clients
|
||||
* where SkinManager is not populated.
|
||||
*/
|
||||
public ResourceLocation getSkinTexture() {
|
||||
String variantId = getKidnapperVariantId();
|
||||
if (!variantId.isEmpty()) {
|
||||
return ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup",
|
||||
getVariantTextureFolder() + variantId + ".png"
|
||||
);
|
||||
}
|
||||
// Fallback to default
|
||||
return ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup",
|
||||
getVariantTextureFolder() + getDefaultVariantId() + ".png"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate variant cache (called when synced data updates from server).
|
||||
*/
|
||||
public void invalidateVariantCache() {
|
||||
this.kidnapperVariant = null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// INITIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Initialize variant and theme when entity is first added to world.
|
||||
* Should only be called on server side.
|
||||
*/
|
||||
public void initializeAppearance() {
|
||||
initializeVariantOnSpawn();
|
||||
initializeTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize variant on spawn (server-side only).
|
||||
*/
|
||||
protected void initializeVariantOnSpawn() {
|
||||
if (host.level() == null || host.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only set variant if not already set
|
||||
if (this.kidnapperVariant == null) {
|
||||
KidnapperVariant variant = computeVariantForEntity(host.getUUID());
|
||||
setKidnapperVariant(variant);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize theme (server-side only).
|
||||
*/
|
||||
protected void initializeTheme() {
|
||||
if (host.level() == null || host.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only initialize if not already set
|
||||
if (this.currentTheme == null) {
|
||||
this.itemSelection = selectItemsForKidnapper();
|
||||
this.currentTheme = this.itemSelection.theme;
|
||||
this.themeColor = this.itemSelection.color;
|
||||
|
||||
// Sync to client
|
||||
host.setEntityDataString(dataTheme, this.currentTheme.name());
|
||||
if (this.themeColor != null) {
|
||||
host.setEntityDataString(
|
||||
dataThemeColor,
|
||||
this.themeColor.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select themed items for this kidnapper.
|
||||
* Delegates to entity's virtual method to allow subclass overrides.
|
||||
*/
|
||||
protected KidnapperItemSelector.SelectionResult selectItemsForKidnapper() {
|
||||
return entity.selectItemsForKidnapper();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// THEME & ITEM SELECTION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get current theme (lazy load from synced data if needed).
|
||||
*/
|
||||
public KidnapperTheme getTheme() {
|
||||
if (this.currentTheme == null) {
|
||||
String themeName = host.getEntityDataString(dataTheme);
|
||||
if (!themeName.isEmpty()) {
|
||||
try {
|
||||
this.currentTheme = KidnapperTheme.valueOf(themeName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperAppearance] Invalid theme from synced data: {}",
|
||||
themeName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.currentTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current theme color (lazy load from synced data if needed).
|
||||
*/
|
||||
@Nullable
|
||||
public ItemColor getThemeColor() {
|
||||
if (this.themeColor == null) {
|
||||
String colorName = host.getEntityDataString(dataThemeColor);
|
||||
if (!colorName.isEmpty()) {
|
||||
this.themeColor = ItemColor.fromName(colorName);
|
||||
}
|
||||
}
|
||||
return this.themeColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item selection result.
|
||||
*/
|
||||
@Nullable
|
||||
public KidnapperItemSelector.SelectionResult getItemSelection() {
|
||||
return this.itemSelection;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save appearance data to NBT.
|
||||
*/
|
||||
public void saveToNBT(CompoundTag tag) {
|
||||
// Save kidnapper variant using NBT key
|
||||
if (this.kidnapperVariant != null) {
|
||||
tag.putString(getVariantNBTKey(), this.kidnapperVariant.id());
|
||||
}
|
||||
|
||||
// Save theme and color
|
||||
if (this.currentTheme != null) {
|
||||
tag.putString("Theme", this.currentTheme.name());
|
||||
}
|
||||
if (this.themeColor != null) {
|
||||
tag.putString("ThemeColor", this.themeColor.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load appearance data from NBT.
|
||||
*/
|
||||
public void loadFromNBT(CompoundTag tag) {
|
||||
// Restore kidnapper variant using NBT key and lookup
|
||||
String nbtKey = getVariantNBTKey();
|
||||
if (tag.contains(nbtKey)) {
|
||||
String variantId = tag.getString(nbtKey);
|
||||
KidnapperVariant variant = lookupVariantById(variantId);
|
||||
if (variant != null) {
|
||||
this.kidnapperVariant = variant;
|
||||
host.setEntityDataString(dataVariantId, variantId);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore theme and color
|
||||
if (tag.contains("Theme")) {
|
||||
try {
|
||||
this.currentTheme = KidnapperTheme.valueOf(
|
||||
tag.getString("Theme")
|
||||
);
|
||||
host.setEntityDataString(dataTheme, this.currentTheme.name());
|
||||
} catch (IllegalArgumentException e) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperAppearance] Invalid theme from NBT: {}",
|
||||
tag.getString("Theme")
|
||||
);
|
||||
}
|
||||
}
|
||||
if (tag.contains("ThemeColor")) {
|
||||
this.themeColor = ItemColor.fromName(tag.getString("ThemeColor"));
|
||||
if (this.themeColor != null) {
|
||||
host.setEntityDataString(
|
||||
dataThemeColor,
|
||||
this.themeColor.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate item selection if theme was loaded
|
||||
if (this.currentTheme != null) {
|
||||
this.itemSelection = new KidnapperItemSelector.SelectionResult(
|
||||
this.currentTheme,
|
||||
this.themeColor,
|
||||
KidnapperItemSelector.createBind(
|
||||
this.currentTheme.getBind(),
|
||||
this.themeColor
|
||||
),
|
||||
KidnapperItemSelector.createGag(
|
||||
this.currentTheme.getPrimaryGag(),
|
||||
this.themeColor
|
||||
),
|
||||
KidnapperItemSelector.createMittens(),
|
||||
KidnapperItemSelector.createEarplugs(),
|
||||
this.currentTheme.hasBlindfolds()
|
||||
? KidnapperItemSelector.createBlindfold(
|
||||
this.currentTheme.getPrimaryBlindfold(),
|
||||
this.themeColor
|
||||
)
|
||||
: ItemStack.EMPTY
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* KidnapperCampManager - Manages camp/structure association.
|
||||
* Phase 1.4: Camp management component (67 lines).
|
||||
*
|
||||
* Handles:
|
||||
* - Structure UUID tracking (which camp this kidnapper belongs to)
|
||||
* - Hunter role assignment (patrols far vs stays close to camp)
|
||||
*
|
||||
* <p><b>Low complexity</b> - Simple state tracking with smart initialization.</p>
|
||||
*/
|
||||
public class KidnapperCampManager {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final ICampHost host;
|
||||
|
||||
/** UUID of the structure this kidnapper belongs to (for marker queries) */
|
||||
@Nullable
|
||||
private UUID associatedStructure;
|
||||
|
||||
/**
|
||||
* Whether this kidnapper is a "hunter" that patrols far from camp.
|
||||
* Hunters patrol 80-150 blocks from camp looking for prey.
|
||||
* Non-hunters stay closer (GUARD/PATROL within 40 blocks).
|
||||
* Decided randomly when associated with a camp (50/50 chance).
|
||||
*/
|
||||
private boolean isHunter = false;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperCampManager(ICampHost host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STRUCTURE ASSOCIATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the associated structure UUID for this kidnapper.
|
||||
*
|
||||
* @return The structure UUID, or null if not associated
|
||||
*/
|
||||
@Nullable
|
||||
public UUID getAssociatedStructure() {
|
||||
return this.associatedStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated structure UUID.
|
||||
* When first associated with a camp, randomly decides if this kidnapper
|
||||
* becomes a "hunter" (50% chance) that patrols far from camp.
|
||||
*
|
||||
* @param structureId The structure this kidnapper belongs to
|
||||
*/
|
||||
public void setAssociatedStructure(@Nullable UUID structureId) {
|
||||
// If being newly associated with a camp, randomly assign hunter role
|
||||
if (structureId != null && this.associatedStructure == null) {
|
||||
this.isHunter = host.getRandom().nextBoolean(); // 50% chance
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperCampManager] {} assigned to camp, isHunter: {}",
|
||||
host.getNpcName(),
|
||||
this.isHunter
|
||||
);
|
||||
}
|
||||
this.associatedStructure = structureId;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// HUNTER ROLE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if this kidnapper is a "hunter" that patrols far from camp.
|
||||
*
|
||||
* @return true if this kidnapper hunts in the wilderness
|
||||
*/
|
||||
public boolean isHunter() {
|
||||
return this.isHunter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this kidnapper is a hunter.
|
||||
*
|
||||
* @param hunter true to make this kidnapper a hunter
|
||||
*/
|
||||
public void setHunter(boolean hunter) {
|
||||
this.isHunter = hunter;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save camp data to NBT.
|
||||
*/
|
||||
public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
if (this.associatedStructure != null) {
|
||||
tag.putUUID("AssociatedStructure", this.associatedStructure);
|
||||
}
|
||||
tag.putBoolean("IsHunter", this.isHunter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load camp data from NBT.
|
||||
*/
|
||||
public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
if (tag.contains("AssociatedStructure")) {
|
||||
this.associatedStructure = tag.getUUID("AssociatedStructure");
|
||||
}
|
||||
if (tag.contains("IsHunter")) {
|
||||
this.isHunter = tag.getBoolean("IsHunter");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,988 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.core.ModConfig;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.EntityDialogueManager;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.PrisonerRecord;
|
||||
import com.tiedup.remake.prison.PrisonerState;
|
||||
import com.tiedup.remake.state.IRestrainable;
|
||||
import com.tiedup.remake.state.ICaptor;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.util.RestraintApplicator;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* KidnapperCaptiveManager - Manages captive lifecycle and ICaptor implementation.
|
||||
* Phase 3.1: Captive management component (~400 lines) - CRITICAL.
|
||||
*
|
||||
* Handles:
|
||||
* - ICaptor interface implementation (10 methods)
|
||||
* - Captive tracking (currentCaptive, pendingCaptiveUUID)
|
||||
* - UUID lazy resolution after chunk reload
|
||||
* - Lifecycle hooks (putBindOn, die, remove, tick)
|
||||
* - Abandon/keep captive logic (solo mode fallback)
|
||||
* - Struggle and attack punishment
|
||||
* - Leash physics (visual positioning)
|
||||
* - NBT serialization (backward compatible SlaveUUID → CaptiveUUID)
|
||||
*
|
||||
* <p><b>VERY HIGH complexity</b> - Critical captive state management with UUID resolution,
|
||||
* leash physics, and punishment flows. Bugs here cause captive loss or state corruption.</p>
|
||||
*/
|
||||
public class KidnapperCaptiveManager {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final ICaptiveHost host;
|
||||
|
||||
/** Entity reference (needed for leash attachment and UUID lookups) */
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
/** Entity data accessor for has-captive sync */
|
||||
private final EntityDataAccessor<Boolean> dataHasCaptive;
|
||||
|
||||
/** Current captive being held */
|
||||
@Nullable
|
||||
private IRestrainable currentCaptive;
|
||||
|
||||
/** UUID of captive to restore after chunk/server reload */
|
||||
@Nullable
|
||||
private UUID pendingCaptiveUUID;
|
||||
|
||||
/** Retry counter for pending captive restoration (ticks) */
|
||||
private int pendingCaptiveRetryTicks = 0;
|
||||
|
||||
/** Max ticks to retry before giving up (30 seconds = 600 ticks) */
|
||||
private static final int MAX_PENDING_CAPTIVE_RETRY_TICKS = 600;
|
||||
|
||||
/** Flag to allow captive transfer (prevents re-capture during transfer) */
|
||||
private boolean allowCaptiveTransferFlag = false;
|
||||
|
||||
/** Target for struggle punishment (shock/tighten) */
|
||||
@Nullable
|
||||
private LivingEntity strugglePunishmentTarget;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperCaptiveManager(
|
||||
EntityKidnapper entity,
|
||||
ICaptiveHost host,
|
||||
EntityDataAccessor<Boolean> dataHasCaptive
|
||||
) {
|
||||
this.entity = entity;
|
||||
this.host = host;
|
||||
this.dataHasCaptive = dataHasCaptive;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// I_KIDNAPPER IMPLEMENTATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Add a captive to this kidnapper.
|
||||
* For NPCs: Attach vanilla leash.
|
||||
* For Players: Leash already attached by PlayerBindState mixin.
|
||||
*/
|
||||
public void addCaptive(IRestrainable captive) {
|
||||
if (captive == null) return;
|
||||
|
||||
// Guard: don't overwrite an existing captive - prevents state corruption
|
||||
if (this.currentCaptive != null && this.currentCaptive != captive) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperCaptiveManager] {} tried to capture {} but already has captive {} - rejecting",
|
||||
host.getNpcName(),
|
||||
captive.getKidnappedName(),
|
||||
this.currentCaptive.getKidnappedName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For Players: Leash is already attached by PlayerBindState.getCapturedBy()
|
||||
// For NPCs: Attach vanilla leash directly
|
||||
LivingEntity captiveEntity = captive.asLivingEntity();
|
||||
if (captiveEntity instanceof Mob mob) {
|
||||
// NPC captive - use vanilla leash
|
||||
mob.setLeashedTo(this.entity, true);
|
||||
}
|
||||
// Player captives have LeashProxyEntity attached via mixin - no action needed here
|
||||
|
||||
this.currentCaptive = captive;
|
||||
host.setEntityData(dataHasCaptive, true);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} captured {}",
|
||||
host.getNpcName(),
|
||||
captive.getKidnappedName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a captive from this kidnapper.
|
||||
* For NPCs: Detach vanilla leash.
|
||||
* For Players: Leash detached by PlayerBindState mixin.
|
||||
*
|
||||
* @param captive The captive to remove
|
||||
* @param transportState true if captive is being transported (no leash drop item)
|
||||
*/
|
||||
public void removeCaptive(IRestrainable captive, boolean transportState) {
|
||||
if (captive == null || captive != this.currentCaptive) return;
|
||||
|
||||
// For Players: Leash detached by PlayerBindState.free()
|
||||
// For NPCs: Detach vanilla leash without dropping item
|
||||
LivingEntity captiveEntity = captive.asLivingEntity();
|
||||
if (captiveEntity instanceof Mob mob && mob.isLeashed()) {
|
||||
mob.dropLeash(false, false); // Don't drop leash item when imprisoning
|
||||
}
|
||||
// Player captives have LeashProxyEntity removed via mixin free() - no action needed here
|
||||
|
||||
String captiveName = this.currentCaptive.getKidnappedName();
|
||||
this.currentCaptive = null;
|
||||
host.setEntityData(dataHasCaptive, false);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} released captive {}",
|
||||
host.getNpcName(),
|
||||
captiveName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this kidnapper can capture a target.
|
||||
* Validates collar ownership if target has collar.
|
||||
* Also checks PrisonerManager state to prevent capturing already-imprisoned players.
|
||||
*/
|
||||
public boolean canCapture(IRestrainable target) {
|
||||
if (target == null) return false;
|
||||
|
||||
// Check if target is already in any non-FREE state
|
||||
// Only FREE players can be targeted by kidnappers
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
PrisonerState state = manager.getState(
|
||||
target.asLivingEntity().getUUID()
|
||||
);
|
||||
|
||||
if (!state.isTargetable()) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperCaptiveManager] {} can't capture {} - not targetable (state={})",
|
||||
host.getNpcName(),
|
||||
target.getKidnappedName(),
|
||||
state
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If target has collar, verify we own it
|
||||
if (target.hasCollar()) {
|
||||
ItemStack collar = target.getEquipment(BodyRegionV2.NECK);
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
// Check if THIS kidnapper is owner
|
||||
if (!collarItem.getOwners(collar).contains(host.getUUID())) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperCaptiveManager] {} can't capture {} - collar owned by someone else",
|
||||
host.getNpcName(),
|
||||
target.getKidnappedName()
|
||||
);
|
||||
return false; // Not our collar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if captive can be released.
|
||||
*/
|
||||
public boolean canRelease(IRestrainable captive) {
|
||||
return captive != null && captive == this.currentCaptive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if captive transfer is allowed.
|
||||
* Used to prevent re-capture during transfer between kidnappers.
|
||||
*/
|
||||
public boolean allowCaptiveTransfer() {
|
||||
// Can transfer if explicitly flagged OR if we're tied up (forced release)
|
||||
return this.allowCaptiveTransferFlag || host.isTiedUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the captive transfer flag.
|
||||
*/
|
||||
public boolean getAllowCaptiveTransferFlag() {
|
||||
return this.allowCaptiveTransferFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the captive transfer flag.
|
||||
*/
|
||||
public void setAllowCaptiveTransferFlag(boolean flag) {
|
||||
this.allowCaptiveTransferFlag = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if multiple captives are allowed.
|
||||
*/
|
||||
public boolean allowMultipleCaptives() {
|
||||
return false; // Kidnappers only hold one captive at a time
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a captive (player) logs out.
|
||||
* Releases the captive gracefully.
|
||||
*/
|
||||
public void onCaptiveLogout(IRestrainable captive) {
|
||||
if (captive == this.currentCaptive) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} captive {} logged out - releasing",
|
||||
host.getNpcName(),
|
||||
captive.getKidnappedName()
|
||||
);
|
||||
removeCaptive(captive, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a captive is released/freed.
|
||||
* Tracks escape, broadcasts alert, and transitions to IDLE.
|
||||
*/
|
||||
public void onCaptiveReleased(IRestrainable releasedCaptive) {
|
||||
if (releasedCaptive != this.currentCaptive) return;
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} captive {} was released",
|
||||
host.getNpcName(),
|
||||
releasedCaptive.getKidnappedName()
|
||||
);
|
||||
|
||||
LivingEntity escapedEntity = releasedCaptive.asLivingEntity();
|
||||
|
||||
// Track escapee for potential recapture (30 seconds)
|
||||
host.getAggressionSystem().onCaptiveEscaped(escapedEntity);
|
||||
|
||||
// Broadcast alert to nearby kidnappers
|
||||
host.broadcastAlert(escapedEntity);
|
||||
|
||||
// Clear captive reference
|
||||
this.currentCaptive = null;
|
||||
host.setEntityData(dataHasCaptive, false);
|
||||
|
||||
// Return to IDLE state
|
||||
host.setCurrentState(KidnapperState.IDLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a captive struggles.
|
||||
* Currently no special handling needed.
|
||||
*/
|
||||
public void onCaptiveStruggle(IRestrainable captive) {
|
||||
// No-op - struggle punishment handled by onStruggleDetected() separately
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this kidnapper has captives.
|
||||
*/
|
||||
public boolean hasCaptives() {
|
||||
return this.currentCaptive != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity implementing ICaptor.
|
||||
*/
|
||||
public LivingEntity getEntity() {
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CAPTIVE ACCESS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the current captive.
|
||||
*/
|
||||
@Nullable
|
||||
public IRestrainable getCaptive() {
|
||||
return this.currentCaptive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer all captives to another kidnapper.
|
||||
* Updates PrisonerManager with new captor.
|
||||
*/
|
||||
public void transferAllCaptivesTo(ICaptor target) {
|
||||
if (!this.hasCaptives() || target == null) return;
|
||||
|
||||
// Update captivity state before transfer
|
||||
if (
|
||||
host.level() instanceof ServerLevel serverLevel &&
|
||||
target.getEntity() != null
|
||||
) {
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
UUID captiveId = this.currentCaptive.asLivingEntity().getUUID();
|
||||
PrisonerRecord record = manager.getRecord(captiveId);
|
||||
// Update captor ID
|
||||
record.setCaptorId(target.getEntity().getUUID());
|
||||
}
|
||||
|
||||
// Transfer captive
|
||||
this.allowCaptiveTransferFlag = true;
|
||||
this.currentCaptive.transferCaptivityTo(target);
|
||||
this.allowCaptiveTransferFlag = false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// UUID RESTORATION (CRITICAL)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Restore captive reference from UUID after chunk/server reload.
|
||||
* Called from customServerAiStep() when pendingCaptiveUUID is set but currentCaptive is null.
|
||||
* This is CRITICAL for maintaining captive state across save/load cycles.
|
||||
*
|
||||
* FIX: Now retries for up to 30 seconds before giving up, to handle:
|
||||
* - Players temporarily offline
|
||||
* - Chunks not yet loaded
|
||||
* - Entity loading delays
|
||||
*/
|
||||
public void restoreCaptiveFromUUID() {
|
||||
if (this.pendingCaptiveUUID == null) return;
|
||||
if (!(host.level() instanceof ServerLevel serverLevel)) return;
|
||||
|
||||
// Find entity by UUID
|
||||
net.minecraft.world.entity.Entity foundEntity = serverLevel.getEntity(
|
||||
this.pendingCaptiveUUID
|
||||
);
|
||||
|
||||
if (foundEntity == null) {
|
||||
// Entity not found - increment retry counter
|
||||
this.pendingCaptiveRetryTicks++;
|
||||
|
||||
if (
|
||||
this.pendingCaptiveRetryTicks >= MAX_PENDING_CAPTIVE_RETRY_TICKS
|
||||
) {
|
||||
// Timeout reached - give up
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperCaptiveManager] {} giving up on captive UUID {} after {} ticks",
|
||||
host.getNpcName(),
|
||||
this.pendingCaptiveUUID,
|
||||
this.pendingCaptiveRetryTicks
|
||||
);
|
||||
this.pendingCaptiveUUID = null;
|
||||
this.pendingCaptiveRetryTicks = 0;
|
||||
} else if (this.pendingCaptiveRetryTicks % 100 == 0) {
|
||||
// Log every 5 seconds at debug level
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperCaptiveManager] {} waiting for captive UUID {} ({}/{})",
|
||||
host.getNpcName(),
|
||||
this.pendingCaptiveUUID,
|
||||
this.pendingCaptiveRetryTicks,
|
||||
MAX_PENDING_CAPTIVE_RETRY_TICKS
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(foundEntity instanceof LivingEntity livingEntity)) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperCaptiveManager] {} found captive entity {} but it's not LivingEntity",
|
||||
host.getNpcName(),
|
||||
foundEntity.getName().getString()
|
||||
);
|
||||
this.pendingCaptiveUUID = null;
|
||||
this.pendingCaptiveRetryTicks = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get kidnapped state
|
||||
IRestrainable kidnappedState = KidnappedHelper.getKidnappedState(
|
||||
livingEntity
|
||||
);
|
||||
if (kidnappedState == null) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperCaptiveManager] {} found captive {} but it doesn't have IRestrainable state",
|
||||
host.getNpcName(),
|
||||
livingEntity.getName().getString()
|
||||
);
|
||||
this.pendingCaptiveUUID = null;
|
||||
this.pendingCaptiveRetryTicks = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify captive is still marked as captured by this kidnapper
|
||||
ICaptor captor = kidnappedState.getCaptor();
|
||||
UUID captorUUID = (captor != null && captor.getEntity() != null)
|
||||
? captor.getEntity().getUUID()
|
||||
: null;
|
||||
if (
|
||||
!kidnappedState.isCaptive() ||
|
||||
captorUUID == null ||
|
||||
!captorUUID.equals(host.getUUID())
|
||||
) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperCaptiveManager] {} found captive {} but it's not captured by us (captive={}, captor={})",
|
||||
host.getNpcName(),
|
||||
livingEntity.getName().getString(),
|
||||
kidnappedState.isCaptive(),
|
||||
captorUUID
|
||||
);
|
||||
this.pendingCaptiveUUID = null;
|
||||
this.pendingCaptiveRetryTicks = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore reference - success!
|
||||
this.currentCaptive = kidnappedState;
|
||||
host.setEntityData(dataHasCaptive, true);
|
||||
this.pendingCaptiveUUID = null;
|
||||
this.pendingCaptiveRetryTicks = 0;
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} restored captive {} from UUID",
|
||||
host.getNpcName(),
|
||||
kidnappedState.getKidnappedName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick processing for UUID restoration and captive validation.
|
||||
* Call this from EntityKidnapper.customServerAiStep().
|
||||
*/
|
||||
public void tick() {
|
||||
// FIX: Check if current captive is still alive (died, disconnected, etc.)
|
||||
// Without this, kidnapper keeps reference to dead captive, blocking all goals
|
||||
if (this.currentCaptive != null) {
|
||||
LivingEntity captiveEntity = this.currentCaptive.asLivingEntity();
|
||||
if (captiveEntity == null || !captiveEntity.isAlive()) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} captive died or was removed - clearing reference",
|
||||
host.getNpcName()
|
||||
);
|
||||
// Clear reference without dropping leash (entity is already gone)
|
||||
this.currentCaptive = null;
|
||||
host.setEntityData(dataHasCaptive, false);
|
||||
host.setCurrentState(KidnapperState.IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore captive from UUID after chunk/server reload
|
||||
if (this.pendingCaptiveUUID != null && this.currentCaptive == null) {
|
||||
restoreCaptiveFromUUID();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LIFECYCLE HOOKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Called when kidnapper gets bound/tied up.
|
||||
* Releases captive and updates PrisonerManager.
|
||||
*/
|
||||
public void onPutBindOn() {
|
||||
if (!this.hasCaptives()) return;
|
||||
|
||||
// Capture local reference - escape() may trigger onCaptiveReleased() which nulls the field
|
||||
IRestrainable captive = this.currentCaptive;
|
||||
if (captive == null) return;
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} got tied up - releasing captive {}",
|
||||
host.getNpcName(),
|
||||
captive.getKidnappedName()
|
||||
);
|
||||
|
||||
// Release prisoner from captivity state
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
UUID captiveUUID = captive.asLivingEntity().getUUID();
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
PrisonerRecord record = manager.getPrisoner(captiveUUID);
|
||||
if (record != null && record.isImprisoned()) {
|
||||
// Clear captivity state - prisoner escaped
|
||||
// Use centralized escape service for complete cleanup
|
||||
com.tiedup.remake.prison.service.PrisonerService.get().escape(
|
||||
serverLevel,
|
||||
captiveUUID,
|
||||
"kidnapper tied up"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Free the captive
|
||||
captive.free();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when kidnapper dies.
|
||||
* Releases captive and updates PrisonerManager.
|
||||
*/
|
||||
public void onDie() {
|
||||
if (!this.hasCaptives()) return;
|
||||
|
||||
// Capture local reference - escape() may trigger onCaptiveReleased() which nulls the field
|
||||
IRestrainable captive = this.currentCaptive;
|
||||
if (captive == null) return;
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} died - releasing captive {}",
|
||||
host.getNpcName(),
|
||||
captive.getKidnappedName()
|
||||
);
|
||||
|
||||
// Release prisoner from captivity state
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
UUID captiveUUID = captive.asLivingEntity().getUUID();
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
PrisonerRecord record = manager.getPrisoner(captiveUUID);
|
||||
if (record != null && record.isImprisoned()) {
|
||||
// Clear captivity state - captor died
|
||||
// Use centralized escape service for complete cleanup
|
||||
com.tiedup.remake.prison.service.PrisonerService.get().escape(
|
||||
serverLevel,
|
||||
captiveUUID,
|
||||
"kidnapper died"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Free the captive
|
||||
captive.free();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when kidnapper is removed (despawn/chunk unload).
|
||||
* Conditional leash drop based on removal reason.
|
||||
*/
|
||||
public void onRemove(
|
||||
net.minecraft.world.entity.Entity.RemovalReason reason
|
||||
) {
|
||||
if (!this.hasCaptives()) return;
|
||||
|
||||
// Graceful chunk unload - don't drop leash item
|
||||
boolean transportState = (reason ==
|
||||
net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_TO_CHUNK);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperCaptiveManager] {} removed (reason={}), transport={}",
|
||||
host.getNpcName(),
|
||||
reason,
|
||||
transportState
|
||||
);
|
||||
|
||||
removeCaptive(this.currentCaptive, transportState);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// SOLO MODE FALLBACK
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Keep captive (solo mode fallback when no buyers available).
|
||||
* DecideNextActionGoal will handle job assignment or cell transport.
|
||||
*/
|
||||
public void keepCaptive() {
|
||||
if (!this.hasCaptives()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperCaptiveManager] {} tried to keep captive but has none",
|
||||
host.getNpcName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} keeping captive {} for themselves",
|
||||
host.getNpcName(),
|
||||
this.currentCaptive.getKidnappedName()
|
||||
);
|
||||
|
||||
// DecideNextActionGoal will take over and assign a job or bring to cell
|
||||
// No need to do anything else here - just don't set getOutState
|
||||
}
|
||||
|
||||
/**
|
||||
* Abandon captive (solo mode fallback when no buyers and no cells).
|
||||
* Blindfolds, teleports to safe position, optionally removes restraints.
|
||||
*/
|
||||
public void abandonCaptive() {
|
||||
if (!this.hasCaptives()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperCaptiveManager] {} tried to abandon captive but has none",
|
||||
host.getNpcName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
LivingEntity captiveEntity = this.currentCaptive.asLivingEntity();
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} abandoning captive {}",
|
||||
host.getNpcName(),
|
||||
this.currentCaptive.getKidnappedName()
|
||||
);
|
||||
|
||||
// Apply blindfold if enabled in config
|
||||
if (
|
||||
com.tiedup.remake.core.ModConfig.SERVER.abandonKeepsBlindfold.get()
|
||||
) {
|
||||
net.minecraft.world.item.ItemStack blindfold = getBlindfoldItem();
|
||||
if (
|
||||
blindfold != null &&
|
||||
!blindfold.isEmpty() &&
|
||||
!this.currentCaptive.isBlindfolded()
|
||||
) {
|
||||
this.currentCaptive.equip(BodyRegionV2.EYES, blindfold.copy());
|
||||
}
|
||||
}
|
||||
|
||||
// Find safe position and teleport
|
||||
BlockPos safePos = findRandomSafePosition();
|
||||
if (safePos != null) {
|
||||
captiveEntity.teleportTo(
|
||||
safePos.getX() + 0.5,
|
||||
safePos.getY(),
|
||||
safePos.getZ() + 0.5
|
||||
);
|
||||
}
|
||||
|
||||
// Remove restraints if NOT configured to keep them
|
||||
boolean keepBinds =
|
||||
com.tiedup.remake.core.ModConfig.SERVER.abandonKeepsBinds.get();
|
||||
if (!keepBinds) {
|
||||
// Full release including binds
|
||||
this.currentCaptive.untie(true);
|
||||
this.currentCaptive.free(true);
|
||||
} else {
|
||||
// Just release from leash, keep binds
|
||||
this.currentCaptive.free(false);
|
||||
}
|
||||
|
||||
// Release from captivity state
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
UUID captiveUUID = captiveEntity.getUUID();
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
PrisonerRecord record = manager.getPrisoner(captiveUUID);
|
||||
if (record != null && record.isImprisoned()) {
|
||||
// Clear captivity state - prisoner freed
|
||||
// Use centralized escape service for complete cleanup
|
||||
com.tiedup.remake.prison.service.PrisonerService.get().escape(
|
||||
serverLevel,
|
||||
captiveUUID,
|
||||
"abandoned by kidnapper"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Enter flee state
|
||||
host.setGetOutState(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blindfold item to use for abandon.
|
||||
*/
|
||||
@Nullable
|
||||
private net.minecraft.world.item.ItemStack getBlindfoldItem() {
|
||||
return this.entity.getBlindfoldItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a random safe position near kidnapper for abandoning captive.
|
||||
*/
|
||||
@Nullable
|
||||
private BlockPos findRandomSafePosition() {
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int offsetX = random.nextInt(21) - 10;
|
||||
int offsetZ = random.nextInt(21) - 10;
|
||||
BlockPos testPos = host.blockPosition().offset(offsetX, 0, offsetZ);
|
||||
if (host.level().getBlockState(testPos.below()).isSolid()) {
|
||||
return testPos;
|
||||
}
|
||||
}
|
||||
return host.blockPosition();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PUNISHMENT SYSTEM
|
||||
// ========================================
|
||||
|
||||
/** Maximum distance to HEAR struggle (through bars, no line of sight needed) */
|
||||
private static final double STRUGGLE_HEARING_RANGE = 6.0;
|
||||
|
||||
/** Maximum distance to SEE struggle (requires line of sight) */
|
||||
private static final double STRUGGLE_VISION_RANGE = 15.0;
|
||||
|
||||
/**
|
||||
* Called when struggle noise is detected from a prisoner.
|
||||
* Uses HYBRID detection: HEARING (close range) + VISION (far range).
|
||||
*
|
||||
* Detection modes:
|
||||
* - Within 6 blocks: Can HEAR through bars/fences (no line of sight needed)
|
||||
* - Within 15 blocks: Can SEE if line of sight is clear
|
||||
*
|
||||
* Validates prisoner belongs to us, then applies shock + tighten punishment.
|
||||
*/
|
||||
public void onStruggleDetected(LivingEntity source, BlockPos strugglePos) {
|
||||
// Ignore if not in GUARD state or already has punishment target
|
||||
if (
|
||||
host.getCurrentState() != KidnapperState.GUARD ||
|
||||
this.strugglePunishmentTarget != null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// HYBRID DETECTION: Hearing (close) + Vision (far)
|
||||
double distance = host.distanceTo(source);
|
||||
|
||||
// HEARING: Close range - can hear through bars/fences (no LOS needed)
|
||||
boolean canHear = distance <= STRUGGLE_HEARING_RANGE;
|
||||
|
||||
// VISION: Longer range - requires clear line of sight
|
||||
boolean canSee =
|
||||
distance <= STRUGGLE_VISION_RANGE && host.hasLineOfSight(source);
|
||||
|
||||
if (!canHear && !canSee) {
|
||||
return; // Can't detect the struggle
|
||||
}
|
||||
|
||||
// Verify this prisoner belongs to a cell we're guarding
|
||||
IRestrainable sourceState = KidnappedHelper.getKidnappedState(source);
|
||||
if (sourceState == null || !sourceState.isCaptive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if prisoner is in a cell
|
||||
CellDataV2 cell = host.findCellContainingPrisoner(source.getUUID());
|
||||
if (cell == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String detectionMethod = canHear ? "heard" : "saw";
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} {} struggle from prisoner {} (distance: {})",
|
||||
host.getNpcName(),
|
||||
detectionMethod,
|
||||
source.getName().getString(),
|
||||
distance
|
||||
);
|
||||
|
||||
// Apply shock punishment
|
||||
sourceState.shockKidnapped(" Stop struggling!", 2.0f);
|
||||
|
||||
// Tighten binds
|
||||
tightenBinds(source);
|
||||
|
||||
// Set punishment target
|
||||
this.strugglePunishmentTarget = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Punish a prisoner for attacking.
|
||||
* Used when prisoner attacks while in cell/camp.
|
||||
* Stronger punishment: shock (4.0f) + tighten.
|
||||
*/
|
||||
public boolean punishAttackingPrisoner(Player player) {
|
||||
if (player == null) return false;
|
||||
|
||||
IRestrainable playerState = KidnappedHelper.getKidnappedState(player);
|
||||
if (playerState == null) return false;
|
||||
|
||||
// Check if captive (leashed) OR imprisoned/working in PrisonerManager
|
||||
if (!playerState.isCaptive()) {
|
||||
if (
|
||||
!(host.level() instanceof
|
||||
net.minecraft.server.level.ServerLevel serverLevel)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
PrisonerManager pm = PrisonerManager.get(serverLevel);
|
||||
PrisonerState pmState = pm.getState(player.getUUID());
|
||||
if (
|
||||
pmState != PrisonerState.IMPRISONED &&
|
||||
pmState != PrisonerState.WORKING
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean shouldPunish = false;
|
||||
|
||||
// Check if prisoner is in one of our cells
|
||||
CellDataV2 cell = host.findCellContainingPrisoner(player.getUUID());
|
||||
if (cell != null) {
|
||||
shouldPunish = true;
|
||||
}
|
||||
|
||||
// Also check camp ownership for camp kidnappers
|
||||
if (!shouldPunish && host.level() instanceof ServerLevel serverLevel) {
|
||||
UUID campId = entity.getAssociatedStructure();
|
||||
if (campId != null) {
|
||||
// Check if this prisoner is in a cell owned by our camp
|
||||
com.tiedup.remake.cells.CellRegistryV2 cellRegistry =
|
||||
com.tiedup.remake.cells.CellRegistryV2.get(serverLevel);
|
||||
java.util.List<CellDataV2> campCells =
|
||||
cellRegistry.getCellsByCamp(campId);
|
||||
|
||||
for (CellDataV2 campCell : campCells) {
|
||||
if (campCell.hasPrisoner(player.getUUID())) {
|
||||
shouldPunish = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldPunish) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCaptiveManager] {} punishing attacking prisoner {}",
|
||||
host.getNpcName(),
|
||||
player.getName().getString()
|
||||
);
|
||||
|
||||
// Stronger shock for attacking
|
||||
playerState.shockKidnapped(" How DARE you!", 4.0f);
|
||||
|
||||
// Tighten binds (also re-applies if player struggled free)
|
||||
tightenBinds(player);
|
||||
|
||||
// Re-apply gag and blindfold if missing
|
||||
RestraintApplicator.applyGagIfMissing(player, entity.getGagItem());
|
||||
RestraintApplicator.applyBlindfoldIfMissing(
|
||||
player,
|
||||
entity.getBlindfoldItem()
|
||||
);
|
||||
|
||||
// Trigger dialogue
|
||||
host.talkToPlayersInRadius(
|
||||
EntityDialogueManager.DialogueCategory.PUNISH,
|
||||
20
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tighten binds on a target.
|
||||
*/
|
||||
private void tightenBinds(LivingEntity target) {
|
||||
IRestrainable targetState = KidnappedHelper.getKidnappedState(target);
|
||||
if (targetState != null) {
|
||||
RestraintApplicator.tightenBind(targetState, target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set struggle punishment target.
|
||||
*/
|
||||
public void setStrugglePunishmentTarget(LivingEntity target) {
|
||||
this.strugglePunishmentTarget = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get struggle punishment target.
|
||||
*/
|
||||
@Nullable
|
||||
public LivingEntity getStrugglePunishmentTarget() {
|
||||
return this.strugglePunishmentTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear struggle punishment target.
|
||||
*/
|
||||
public void clearStrugglePunishmentTarget() {
|
||||
this.strugglePunishmentTarget = null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LEASH PHYSICS (VISUAL)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the position where the rope/leash is held.
|
||||
* Overrides default chest position to use right hand.
|
||||
* Custom visual physics - positions leash at right hand with body rotation.
|
||||
*/
|
||||
public Vec3 getRopeHoldPosition(float partialTicks) {
|
||||
// Interpolate position for smooth rendering
|
||||
double x = entity.xo + (entity.getX() - entity.xo) * partialTicks;
|
||||
double y = entity.yo + (entity.getY() - entity.yo) * partialTicks;
|
||||
double z = entity.zo + (entity.getZ() - entity.zo) * partialTicks;
|
||||
|
||||
// Get body yaw rotation
|
||||
float bodyYaw =
|
||||
entity.yBodyRotO +
|
||||
(entity.yBodyRot - entity.yBodyRotO) * partialTicks;
|
||||
double yawRad = Math.toRadians(bodyYaw);
|
||||
|
||||
// Calculate offset in kidnapper's local space (right hand position)
|
||||
double lateralOffset = 0.4; // Right side
|
||||
double heightOffset = 1.3; // Shoulder height
|
||||
double forwardOffset = 0.2; // Slightly forward
|
||||
|
||||
// Rotate offset by body yaw
|
||||
double offsetX =
|
||||
-Math.sin(yawRad) * forwardOffset +
|
||||
Math.cos(yawRad) * lateralOffset;
|
||||
double offsetZ =
|
||||
Math.cos(yawRad) * forwardOffset + Math.sin(yawRad) * lateralOffset;
|
||||
|
||||
return new Vec3(x + offsetX, y + heightOffset, z + offsetZ);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save captive manager data to NBT.
|
||||
* Saves captive UUID for lazy restoration.
|
||||
* Backward compatible: saves as "CaptiveUUID" (old: "SlaveUUID").
|
||||
*/
|
||||
public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Save captive UUID if we have one
|
||||
if (this.currentCaptive != null) {
|
||||
tag.putUUID(
|
||||
"CaptiveUUID",
|
||||
this.currentCaptive.getKidnappedUniqueId()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load captive manager data from NBT.
|
||||
* Loads captive UUID for lazy restoration.
|
||||
* Backward compatible: reads both "CaptiveUUID" and old "SlaveUUID".
|
||||
*/
|
||||
public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Load captive UUID (will be resolved later in tick)
|
||||
// Support both old "SlaveUUID" and new "CaptiveUUID" for backward compatibility
|
||||
if (tag.contains("CaptiveUUID")) {
|
||||
this.pendingCaptiveUUID = tag.getUUID("CaptiveUUID");
|
||||
} else if (tag.contains("SlaveUUID")) {
|
||||
this.pendingCaptiveUUID = tag.getUUID("SlaveUUID");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.blocks.entity.MarkerBlockEntity;
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.cells.CellRegistryV2;
|
||||
import com.tiedup.remake.cells.MarkerType;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
/**
|
||||
* KidnapperCellManager - Manages cell-related queries and interactions.
|
||||
* Phase 2.3: Cell management component (~125 lines).
|
||||
*
|
||||
* Handles:
|
||||
* - Finding cells containing specific prisoners
|
||||
* - Querying nearby cells with occupants
|
||||
* - Cell boundary detection (isInsideCell)
|
||||
* - Patrol marker discovery
|
||||
* - Cell breach response
|
||||
*
|
||||
* <p><b>Medium complexity</b> - Cell registry integration and boundary calculations.</p>
|
||||
*/
|
||||
public class KidnapperCellManager {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final ICellHost host;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperCellManager(ICellHost host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CELL QUERIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Find the cell containing a specific prisoner.
|
||||
*
|
||||
* @param prisonerId UUID of the prisoner
|
||||
* @return The cell data, or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public CellDataV2 findCellContainingPrisoner(UUID prisonerId) {
|
||||
if (!(host.level() instanceof ServerLevel serverLevel)) return null;
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
return registry.findCellByPrisoner(prisonerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all nearby cells (within 32 blocks) that have prisoners.
|
||||
*
|
||||
* @return List of occupied cells near this kidnapper
|
||||
*/
|
||||
public List<CellDataV2> getNearbyCellsWithPrisoners() {
|
||||
List<CellDataV2> result = new ArrayList<>();
|
||||
if (!(host.level() instanceof ServerLevel serverLevel)) {
|
||||
return result;
|
||||
}
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
List<CellDataV2> nearbyCells = registry.findCellsNear(
|
||||
host.blockPosition(),
|
||||
32
|
||||
);
|
||||
for (CellDataV2 cell : nearbyCells) {
|
||||
if (cell.isOccupied()) {
|
||||
result.add(cell);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CELL BOUNDARY DETECTION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if a position is inside a cell's boundaries.
|
||||
* Uses wall markers to calculate bounds, or defaults to 5-block radius from spawn point.
|
||||
*
|
||||
* @param pos Position to check
|
||||
* @param cell Cell data
|
||||
* @return true if position is inside the cell
|
||||
*/
|
||||
public boolean isInsideCell(BlockPos pos, CellDataV2 cell) {
|
||||
java.util.Set<BlockPos> walls = cell.getWallBlocks();
|
||||
|
||||
if (walls.isEmpty()) {
|
||||
// No wall markers - use default radius from core position
|
||||
BlockPos spawn = cell.getCorePos();
|
||||
double distSq = pos.distSqr(spawn);
|
||||
return distSq <= 25; // 5 block radius default
|
||||
}
|
||||
|
||||
// Calculate bounding box from wall markers
|
||||
int minX = Integer.MAX_VALUE,
|
||||
minY = Integer.MAX_VALUE,
|
||||
minZ = Integer.MAX_VALUE;
|
||||
int maxX = Integer.MIN_VALUE,
|
||||
maxY = Integer.MIN_VALUE,
|
||||
maxZ = Integer.MIN_VALUE;
|
||||
|
||||
for (BlockPos wall : walls) {
|
||||
minX = Math.min(minX, wall.getX());
|
||||
minY = Math.min(minY, wall.getY());
|
||||
minZ = Math.min(minZ, wall.getZ());
|
||||
maxX = Math.max(maxX, wall.getX());
|
||||
maxY = Math.max(maxY, wall.getY());
|
||||
maxZ = Math.max(maxZ, wall.getZ());
|
||||
}
|
||||
|
||||
// Check if position is within bounds (with 1-block tolerance on sides, 3-block tolerance on top)
|
||||
return (
|
||||
pos.getX() >= minX - 1 &&
|
||||
pos.getX() <= maxX + 1 &&
|
||||
pos.getY() >= minY - 1 &&
|
||||
pos.getY() <= maxY + 3 &&
|
||||
pos.getZ() >= minZ - 1 &&
|
||||
pos.getZ() <= maxZ + 1
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PATROL MARKERS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get all patrol markers from nearby cells.
|
||||
*
|
||||
* @param radius Search radius in blocks
|
||||
* @return List of patrol marker positions
|
||||
*/
|
||||
public List<BlockPos> getNearbyPatrolMarkers(int radius) {
|
||||
List<BlockPos> result = new ArrayList<>();
|
||||
if (!(host.level() instanceof ServerLevel serverLevel)) return result;
|
||||
|
||||
BlockPos center = host.blockPosition();
|
||||
int chunkRadius = (radius + 15) >> 4;
|
||||
int centerCX = center.getX() >> 4;
|
||||
int centerCZ = center.getZ() >> 4;
|
||||
|
||||
for (
|
||||
int cx = centerCX - chunkRadius;
|
||||
cx <= centerCX + chunkRadius;
|
||||
cx++
|
||||
) {
|
||||
for (
|
||||
int cz = centerCZ - chunkRadius;
|
||||
cz <= centerCZ + chunkRadius;
|
||||
cz++
|
||||
) {
|
||||
net.minecraft.world.level.chunk.LevelChunk chunk = serverLevel
|
||||
.getChunkSource()
|
||||
.getChunkNow(cx, cz);
|
||||
if (chunk == null) continue;
|
||||
|
||||
for (BlockEntity be : chunk.getBlockEntities().values()) {
|
||||
if (!(be instanceof MarkerBlockEntity marker)) continue;
|
||||
if (marker.getMarkerType() != MarkerType.PATROL) continue;
|
||||
|
||||
BlockPos pos = be.getBlockPos();
|
||||
if (center.distSqr(pos) <= (double) radius * radius) {
|
||||
result.add(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CELL BREACH RESPONSE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Called when a cell wall is breached (broken).
|
||||
* Kidnappers react to breaches in occupied cells or their own camp cells.
|
||||
*
|
||||
* @param breachPos Position where wall was broken
|
||||
* @param cellId UUID of the cell that was breached
|
||||
*/
|
||||
public void onCellBreach(BlockPos breachPos, UUID cellId) {
|
||||
if (cellId == null) return;
|
||||
|
||||
boolean shouldReact = false;
|
||||
CellDataV2 cell = null;
|
||||
|
||||
// Check if this is an occupied cell
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
cell = registry.getCell(cellId);
|
||||
|
||||
if (cell != null && cell.isOccupied()) {
|
||||
shouldReact = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also react if it's our associated structure
|
||||
if (
|
||||
host.getAssociatedStructure() != null &&
|
||||
host.getAssociatedStructure().equals(cellId)
|
||||
) {
|
||||
shouldReact = true;
|
||||
}
|
||||
|
||||
if (shouldReact) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperCellManager] {} detected wall breach at {}, investigating",
|
||||
host.getNpcName(),
|
||||
breachPos.toShortString()
|
||||
);
|
||||
|
||||
// Navigate to breach location
|
||||
host
|
||||
.getNavigation()
|
||||
.moveTo(
|
||||
breachPos.getX() + 0.5,
|
||||
breachPos.getY(),
|
||||
breachPos.getZ() + 0.5,
|
||||
1.2
|
||||
);
|
||||
|
||||
// Enter alert state
|
||||
host.setCurrentState(KidnapperState.ALERT);
|
||||
|
||||
// Trigger dialogue
|
||||
host.talkToPlayersInRadius(
|
||||
com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory.CAPTURE_ESCAPE,
|
||||
20
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save cell manager data to NBT.
|
||||
* Note: Cell manager uses only transient runtime state.
|
||||
*/
|
||||
public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Cell manager uses only transient runtime state
|
||||
// No persistent data needs to be saved
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cell manager data from NBT.
|
||||
* Note: Cell manager uses only transient runtime state.
|
||||
*/
|
||||
public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Cell manager uses only transient runtime state
|
||||
// No persistent data needs to be loaded
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
|
||||
/**
|
||||
* KidnapperDataSerializer - Manages NBT serialization and deserialization.
|
||||
* Phase 4.1: Data serialization component (~65 lines).
|
||||
*
|
||||
* Handles:
|
||||
* - Orchestrating component save/load (appearance, aggression, captive, state, camp, job)
|
||||
* - Direct entity data (KidnappingMode, GetOutState)
|
||||
* - Backward compatibility (AIState → KidnapperState)
|
||||
*
|
||||
* <p><b>Low complexity</b> - Already component-based, minimal orchestration needed.</p>
|
||||
*/
|
||||
public class KidnapperDataSerializer {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final IDataSerializerHost host;
|
||||
|
||||
/** Entity data accessor for kidnapping mode */
|
||||
private final EntityDataAccessor<Boolean> dataKidnappingMode;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperDataSerializer(
|
||||
IDataSerializerHost host,
|
||||
EntityDataAccessor<Boolean> dataKidnappingMode
|
||||
) {
|
||||
this.host = host;
|
||||
this.dataKidnappingMode = dataKidnappingMode;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save all data to NBT.
|
||||
* Orchestrates component saves and direct entity data.
|
||||
*
|
||||
* @param tag The NBT tag to save to
|
||||
*/
|
||||
public void saveToNBT(CompoundTag tag) {
|
||||
// Save appearance data
|
||||
host.getAppearance().saveToNBT(tag);
|
||||
|
||||
// Save aggression system data
|
||||
host.getAggressionSystem().saveToNBT(tag);
|
||||
|
||||
// Save captive manager data
|
||||
host.getCaptiveManager().saveToNBT(tag);
|
||||
|
||||
// Save direct entity data
|
||||
tag.putBoolean(
|
||||
"KidnappingMode",
|
||||
host.getEntityData(dataKidnappingMode)
|
||||
);
|
||||
tag.putBoolean("GetOutState", host.isGetOutState());
|
||||
|
||||
// Save AI state
|
||||
tag.putString(
|
||||
host.getStateManager().getNBTKey(),
|
||||
host.getStateManager().serializeState()
|
||||
);
|
||||
|
||||
// Save camp manager state
|
||||
host.getCampManager().saveToNBT(tag);
|
||||
|
||||
// Save job manager state
|
||||
host.getJobManager().save(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all data from NBT.
|
||||
* Orchestrates component loads and direct entity data with backward compatibility.
|
||||
*
|
||||
* @param tag The NBT tag to load from
|
||||
*/
|
||||
public void loadFromNBT(CompoundTag tag) {
|
||||
// Restore appearance data
|
||||
host.getAppearance().loadFromNBT(tag);
|
||||
|
||||
// Restore aggression system data
|
||||
host.getAggressionSystem().loadFromNBT(tag);
|
||||
|
||||
// Restore captive manager data
|
||||
host.getCaptiveManager().loadFromNBT(tag);
|
||||
|
||||
// Restore direct entity data with null checks
|
||||
if (tag.contains("KidnappingMode")) {
|
||||
host.setEntityData(
|
||||
dataKidnappingMode,
|
||||
tag.getBoolean("KidnappingMode")
|
||||
);
|
||||
}
|
||||
if (tag.contains("GetOutState")) {
|
||||
host.setGetOutState(tag.getBoolean("GetOutState"));
|
||||
}
|
||||
|
||||
// Load AI state with backward compatibility
|
||||
String stateKey = host.getStateManager().getNBTKey();
|
||||
if (tag.contains(stateKey)) {
|
||||
host.getStateManager().deserializeState(tag.getString(stateKey));
|
||||
} else if (tag.contains("AIState")) {
|
||||
// Backward compatibility: old saves used "AIState" instead of "KidnapperState"
|
||||
host.getStateManager().deserializeState(tag.getString("AIState"));
|
||||
}
|
||||
|
||||
// Load camp manager state
|
||||
host.getCampManager().loadFromNBT(tag);
|
||||
|
||||
// Load job manager state
|
||||
host.getJobManager().load(tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityDamselShiny;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.PrisonerRecord;
|
||||
import com.tiedup.remake.state.ICaptor;
|
||||
import com.tiedup.remake.util.tasks.ItemTask;
|
||||
import com.tiedup.remake.util.tasks.SaleLoader;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
/**
|
||||
* KidnapperSaleManager - Manages NPC sale transactions.
|
||||
* Phase 2.4: Sale management component (~115 lines).
|
||||
*
|
||||
* Handles:
|
||||
* - Initiating sales with price calculation (player/shiny multipliers)
|
||||
* - Checking sale status
|
||||
* - Canceling sales
|
||||
* - Completing sales with captive transfer
|
||||
* - PrisonerManager integration
|
||||
*
|
||||
* <p><b>Low complexity</b> - Straightforward state transitions and captive transfer.</p>
|
||||
*/
|
||||
public class KidnapperSaleManager {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final ISaleHost host;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperSaleManager(ISaleHost host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// SALE STATUS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if currently selling a captive.
|
||||
*
|
||||
* @return true if has captive and captive is for sale
|
||||
*/
|
||||
public boolean isSellingCaptive() {
|
||||
return host.hasCaptives() && host.getCaptive().isForSell();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// START SALE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Start selling the current captive with auto-calculated price.
|
||||
* Price is based on SaleLoader random price with multipliers:
|
||||
* - Players: 2x multiplier
|
||||
* - Shiny Damsels: 3x multiplier
|
||||
* - Regular NPCs: 1x multiplier
|
||||
*
|
||||
* @return true if sale started successfully
|
||||
*/
|
||||
public boolean startSale() {
|
||||
if (!host.hasCaptives()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperSaleManager] {} can't start sale - no captive",
|
||||
host.getNpcName()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (host.getCaptive().isForSell()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperSaleManager] {} captive already for sale",
|
||||
host.getNpcName()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get base price
|
||||
ItemTask basePrice = SaleLoader.getRandomSale();
|
||||
|
||||
// Calculate price multiplier based on captive type
|
||||
int multiplier = 1;
|
||||
LivingEntity captiveEntity = host.getCaptive().asLivingEntity();
|
||||
|
||||
if (captiveEntity instanceof Player) {
|
||||
multiplier = 2; // Players cost 2x more
|
||||
} else if (captiveEntity instanceof EntityDamselShiny) {
|
||||
multiplier = 3; // Shiny damsels cost 3x more
|
||||
}
|
||||
|
||||
// Apply multiplier
|
||||
ItemTask finalPrice =
|
||||
multiplier > 1
|
||||
? new ItemTask(
|
||||
basePrice.getItemId(),
|
||||
basePrice.getAmount() * multiplier
|
||||
)
|
||||
: basePrice;
|
||||
|
||||
host.getCaptive().putForSale(finalPrice);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperSaleManager] {} started selling {} for {} ({}x multiplier)",
|
||||
host.getNpcName(),
|
||||
host.getCaptive().getKidnappedName(),
|
||||
finalPrice.toDisplayString(),
|
||||
multiplier
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start selling the current captive with a specific price.
|
||||
*
|
||||
* @param price The price to sell for
|
||||
* @return true if sale started successfully
|
||||
*/
|
||||
public boolean startSale(ItemTask price) {
|
||||
if (!host.hasCaptives() || price == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (host.getCaptive().isForSell()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
host.getCaptive().putForSale(price);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CANCEL SALE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Cancel the current sale.
|
||||
* Removes the for-sale flag from the captive.
|
||||
*/
|
||||
public void cancelSale() {
|
||||
if (host.hasCaptives() && host.getCaptive().isForSell()) {
|
||||
host.getCaptive().cancelSale();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// COMPLETE SALE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Complete a sale by transferring the captive to the buyer.
|
||||
* Also updates PrisonerManager, removes sale flag, and triggers flee state.
|
||||
*
|
||||
* @param buyer The kidnapper purchasing the captive
|
||||
* @return true if sale completed successfully
|
||||
*/
|
||||
public boolean completeSale(ICaptor buyer) {
|
||||
if (!host.hasCaptives() || buyer == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!host.getCaptive().isForSell()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cancel sale flag
|
||||
host.getCaptive().cancelSale();
|
||||
|
||||
// Update PrisonerManager with new captor (the buyer)
|
||||
if (
|
||||
host.level() instanceof ServerLevel serverLevel &&
|
||||
buyer.getEntity() != null
|
||||
) {
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
UUID captiveId = host.getCaptive().asLivingEntity().getUUID();
|
||||
PrisonerRecord record = manager.getRecord(captiveId);
|
||||
record.setCaptorId(buyer.getEntity().getUUID());
|
||||
}
|
||||
|
||||
// Transfer captive to buyer
|
||||
host.setAllowCaptiveTransferFlag(true);
|
||||
host.getCaptive().transferCaptivityTo(buyer);
|
||||
host.setAllowCaptiveTransferFlag(false);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[KidnapperSaleManager] {} sold captive to {}",
|
||||
host.getNpcName(),
|
||||
buyer.getEntity().getName().getString()
|
||||
);
|
||||
|
||||
// Enter flee state after sale
|
||||
host.setGetOutState(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save sale manager data to NBT.
|
||||
* Note: Sale state is stored in captive (IRestrainable), not in this manager.
|
||||
*/
|
||||
public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Sale manager uses only transient runtime state
|
||||
// Sale flag is stored in captive entity
|
||||
// No persistent data needs to be saved
|
||||
}
|
||||
|
||||
/**
|
||||
* Load sale manager data from NBT.
|
||||
* Note: Sale state is loaded from captive (IRestrainable).
|
||||
*/
|
||||
public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Sale manager uses only transient runtime state
|
||||
// Sale flag is loaded from captive entity
|
||||
// No persistent data needs to be loaded
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
|
||||
/**
|
||||
* KidnapperStateManager - Manages the behavioral state of a kidnapper.
|
||||
* Phase 1.1: Simple state management component (26 lines).
|
||||
*
|
||||
* Handles the current KidnapperState enum (IDLE, HUNT, CAPTURE, etc.)
|
||||
* with debug logging for state transitions.
|
||||
*
|
||||
* <p><b>No dependencies</b> - Foundation component.</p>
|
||||
*/
|
||||
public class KidnapperStateManager {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks for logging */
|
||||
private final IStateHost host;
|
||||
|
||||
/** Current behavioral state */
|
||||
private KidnapperState currentState = KidnapperState.IDLE;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperStateManager(IStateHost host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the current behavioral state.
|
||||
*
|
||||
* @return The current state
|
||||
*/
|
||||
public KidnapperState getCurrentState() {
|
||||
return this.currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current behavioral state with debug logging.
|
||||
*
|
||||
* @param state The new state
|
||||
*/
|
||||
public void setCurrentState(KidnapperState state) {
|
||||
if (this.currentState != state) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[KidnapperStateManager] {} state change: {} -> {}",
|
||||
host.getNpcName(),
|
||||
this.currentState,
|
||||
state
|
||||
);
|
||||
this.currentState = state;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the NBT key for this component.
|
||||
*
|
||||
* @return The NBT key
|
||||
*/
|
||||
public String getNBTKey() {
|
||||
return "CurrentState";
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize state to NBT.
|
||||
*
|
||||
* @return The state name as a string
|
||||
*/
|
||||
public String serializeState() {
|
||||
return currentState.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize state from NBT.
|
||||
*
|
||||
* @param stateName The state name string
|
||||
*/
|
||||
public void deserializeState(String stateName) {
|
||||
try {
|
||||
this.currentState = KidnapperState.valueOf(stateName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[KidnapperStateManager] Invalid state name: {}, defaulting to IDLE",
|
||||
stateName
|
||||
);
|
||||
this.currentState = KidnapperState.IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
package com.tiedup.remake.entities.kidnapper.components;
|
||||
|
||||
import com.tiedup.remake.cells.CampOwnership;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.compat.mca.MCACompat;
|
||||
import com.tiedup.remake.entities.EntityDamsel;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.EntityKidnapperElite;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.PrisonerRecord;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* KidnapperTargetSelector - Manages target selection and validation logic.
|
||||
* Phase 2.2: Target selection component (~286 lines) - CRITICAL.
|
||||
*
|
||||
* Handles:
|
||||
* - isSuitableTarget() - Complex targeting logic with 8 categories of checks
|
||||
* - isTargetStillValidForChase() - Mid-chase validation (no line-of-sight)
|
||||
* - isTargetBeingPursuedByOther() - Competition detection
|
||||
* - getClosestSuitableTarget() - Proximity-based targeting
|
||||
* - Target tracking (currentTarget field)
|
||||
*
|
||||
* <p><b>VERY HIGH complexity</b> - Critical logic controlling kidnapper behavior.
|
||||
* Bugs here cause kidnappers to be passive (ignore everyone) or too aggressive
|
||||
* (target protected players). Preserve all conditions exactly.</p>
|
||||
*/
|
||||
public class KidnapperTargetSelector {
|
||||
|
||||
// ========================================
|
||||
// FIELDS
|
||||
// ========================================
|
||||
|
||||
/** Host callbacks */
|
||||
private final ITargetHost host;
|
||||
|
||||
/** Entity reference (needed for type checks and competition detection) */
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
/** Current target being pursued */
|
||||
@Nullable
|
||||
private LivingEntity currentTarget;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTOR
|
||||
// ========================================
|
||||
|
||||
public KidnapperTargetSelector(EntityKidnapper entity, ITargetHost host) {
|
||||
this.entity = entity;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TARGET TRACKING
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the current target being pursued.
|
||||
*
|
||||
* @return The target entity, or null if none
|
||||
*/
|
||||
@Nullable
|
||||
public LivingEntity getTarget() {
|
||||
return this.currentTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current target.
|
||||
*
|
||||
* @param target The target to pursue
|
||||
*/
|
||||
public void setTarget(@Nullable LivingEntity target) {
|
||||
this.currentTarget = target;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TARGET SUITABILITY (CRITICAL METHOD)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if an entity is a suitable target for capture.
|
||||
* This is the CRITICAL method controlling kidnapper behavior.
|
||||
*
|
||||
* Validation categories:
|
||||
* 1. Basic validity (null, alive, self, type)
|
||||
* 2. Kidnapper state (has captive, tied up, waiting for job, fleeing)
|
||||
* 3. Job worker protection
|
||||
* 4. Player-specific (creative/spectator, robbed immunity, grace period, ransom/labor, token)
|
||||
* 5. Kidnapped state (already captive, collar ownership)
|
||||
* 6. Kidnapping mode (whitelist/blacklist)
|
||||
* 7. Competition check (other kidnappers pursuing)
|
||||
* 8. Line of sight (expensive, checked last)
|
||||
*
|
||||
* @param entity The entity to evaluate
|
||||
* @return true if entity is a valid target, false otherwise
|
||||
*/
|
||||
public boolean isSuitableTarget(LivingEntity entity) {
|
||||
if (entity == null) return false;
|
||||
if (!entity.isAlive()) return false;
|
||||
if (entity.isInvisible()) return false;
|
||||
// Note: hasLineOfSight check moved to end (expensive raycast)
|
||||
|
||||
// Can't target self
|
||||
if (entity == this.entity) return false;
|
||||
|
||||
// Can't target other kidnappers
|
||||
if (entity instanceof EntityKidnapper) return false;
|
||||
|
||||
// Can't target labor guards (they're allies, not capture targets)
|
||||
if (
|
||||
entity instanceof com.tiedup.remake.entities.EntityLaborGuard
|
||||
) return false;
|
||||
|
||||
// Can't target if we already have a captive
|
||||
if (host.hasCaptives()) return false;
|
||||
|
||||
// Can't target if we're tied up
|
||||
if (host.isTiedUp()) return false;
|
||||
|
||||
// Can't target if we're waiting for a job to be completed
|
||||
if (host.isWaitingForJobToBeCompleted()) return false;
|
||||
|
||||
// Can't target if we're fleeing (get out state)
|
||||
if (host.isGetOutState()) return false;
|
||||
|
||||
// Can't target the current job worker
|
||||
UUID workerUUID = host.getJobManager().getJobWorkerUUID();
|
||||
if (workerUUID != null && entity.getUUID().equals(workerUUID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIX: Can't target job workers of OTHER kidnappers (wild jobs protection)
|
||||
// Prevents kidnapper B from capturing a player doing a job for kidnapper A
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
AABB searchBox = new AABB(entity.blockPosition()).inflate(64);
|
||||
List<EntityKidnapper> nearbyKidnappers =
|
||||
serverLevel.getEntitiesOfClass(
|
||||
EntityKidnapper.class,
|
||||
searchBox
|
||||
);
|
||||
for (EntityKidnapper otherKidnapper : nearbyKidnappers) {
|
||||
if (otherKidnapper == this.entity) continue;
|
||||
if (
|
||||
otherKidnapper.getJobManager() != null &&
|
||||
entity
|
||||
.getUUID()
|
||||
.equals(
|
||||
otherKidnapper.getJobManager().getJobWorkerUUID()
|
||||
)
|
||||
) {
|
||||
return false; // Protected - active job worker for another kidnapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Player-specific checks
|
||||
if (entity instanceof Player player) {
|
||||
if (player.isCreative() || player.isSpectator()) return false;
|
||||
|
||||
// Robbed immunity check - this kidnapper recently robbed this player
|
||||
if (
|
||||
host.getAggressionSystem().hasRobbedImmunity(player.getUUID())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// UNIFIED CAPTIVITY CHECK - check if player is detained in a camp/cell
|
||||
// This prevents kidnappers from targeting players who are:
|
||||
// 1. Being transported (CAPTURED, BEING_TRANSPORTED)
|
||||
// 2. Imprisoned (in cell, working, returning, resting)
|
||||
// 3. Under post-release protection (grace period)
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
long currentTime = serverLevel.getGameTime();
|
||||
|
||||
// Can't target if captive (transported/imprisoned) OR protected (grace period)
|
||||
if (!manager.isTargetable(player.getUUID(), currentTime)) {
|
||||
return false; // Player is captive or protected
|
||||
}
|
||||
}
|
||||
|
||||
// Token check - only protects from kidnappers LINKED to a camp
|
||||
// Non-linked kidnappers (wild spawns) ignore the token
|
||||
if (
|
||||
hasTokenInInventory(player) &&
|
||||
host.getAssociatedStructure() != null
|
||||
) {
|
||||
return false; // Token protects from camp-linked kidnappers
|
||||
}
|
||||
}
|
||||
|
||||
// Get entity's kidnapped state
|
||||
IBondageState state = KidnappedHelper.getKidnappedState(entity);
|
||||
if (state == null) return false;
|
||||
|
||||
// Can't target if entity is already someone's captive (unless on pole)
|
||||
if (state.isCaptive() && !state.isTiedToPole()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't target if entity has a collar (is a slave) - UNLESS we own that collar
|
||||
// OR the collared player escaped from our camp (same camp kidnappers can recapture)
|
||||
// Self-collared entities (exploit) are treated as uncolllared — kidnappers ignore self-collars
|
||||
if (state.hasCollar()) {
|
||||
ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
java.util.List<UUID> owners = collarItem.getOwners(collar);
|
||||
// Filter out self-collar (owner == wearer = exploit)
|
||||
java.util.List<UUID> realOwners = owners
|
||||
.stream()
|
||||
.filter(id -> !id.equals(entity.getUUID()))
|
||||
.toList();
|
||||
if (
|
||||
!realOwners.isEmpty() &&
|
||||
!realOwners.contains(host.getUUID())
|
||||
) {
|
||||
// Not our collar directly - check if escaped prisoner from our camp
|
||||
boolean sameCampEscapee = false;
|
||||
if (
|
||||
entity instanceof Player collarPlayer &&
|
||||
host.level() instanceof ServerLevel sl
|
||||
) {
|
||||
PrisonerRecord record = PrisonerManager.get(
|
||||
sl
|
||||
).getRecord(collarPlayer.getUUID());
|
||||
UUID prisonerCampId = record.getCampId();
|
||||
UUID ourCampId = host.getAssociatedStructure();
|
||||
if (
|
||||
prisonerCampId != null &&
|
||||
ourCampId != null &&
|
||||
prisonerCampId.equals(ourCampId)
|
||||
) {
|
||||
sameCampEscapee = true; // Escaped from our camp - recapture
|
||||
}
|
||||
}
|
||||
if (!sameCampEscapee) {
|
||||
return false; // Not our collar and not from our camp
|
||||
}
|
||||
}
|
||||
// Our collar, self-collar, or same-camp escapee - continue targeting
|
||||
} else {
|
||||
return false; // Unknown collar type, don't target
|
||||
}
|
||||
}
|
||||
|
||||
// If kidnapping mode is enabled, check blacklist/whitelist (players only)
|
||||
if (entity instanceof Player player && host.isKidnappingModeEnabled()) {
|
||||
if (!host.getCollarConfig().isValidKidnappingTarget(player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if another kidnapper is already pursuing this target (elite kidnappers bypass this)
|
||||
if (isTargetBeingPursuedByOther(entity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expensive raycast check - do this last after all cheap checks pass
|
||||
if (!host.getSensing().hasLineOfSight(entity)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CHASE VALIDATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if target is still valid during active chase.
|
||||
* More lenient than isSuitableTarget - no line-of-sight requirement.
|
||||
* Allows chasing through forests and around obstacles.
|
||||
*
|
||||
* @param entity The target being chased
|
||||
* @return true if chase should continue, false otherwise
|
||||
*/
|
||||
public boolean isTargetStillValidForChase(LivingEntity entity) {
|
||||
if (entity == null) return false;
|
||||
if (!entity.isAlive()) return false;
|
||||
if (entity.isInvisible()) return false;
|
||||
|
||||
// Can't continue if we got tied up
|
||||
if (host.isTiedUp()) return false;
|
||||
|
||||
// Can't continue if we're fleeing
|
||||
if (host.isGetOutState()) return false;
|
||||
|
||||
// Player-specific checks
|
||||
if (entity instanceof Player player) {
|
||||
if (player.isCreative() || player.isSpectator()) return false;
|
||||
|
||||
// UNIFIED CAPTIVITY CHECK - same as isSuitableTarget
|
||||
if (host.level() instanceof ServerLevel serverLevel) {
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
long currentTime = serverLevel.getGameTime();
|
||||
|
||||
// Can't chase if captive (transported/imprisoned) OR protected (grace period)
|
||||
if (!manager.isTargetable(player.getUUID(), currentTime)) {
|
||||
return false; // Player is captive or protected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if captured by someone else
|
||||
IBondageState state = KidnappedHelper.getKidnappedState(entity);
|
||||
if (state != null && state.isCaptive() && !state.isTiedToPole()) {
|
||||
// Captured by another - stop chase
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if collared by a PLAYER (player may have collared during chase)
|
||||
// Other kidnappers' collars are fair game — kidnapper can steal from kidnapper
|
||||
if (state != null && state.hasCollar()) {
|
||||
ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
java.util.List<UUID> owners = collarItem.getOwners(collar);
|
||||
if (!owners.isEmpty() && !owners.contains(host.getUUID())) {
|
||||
// Check if any owner is a DIFFERENT player (not self-collared, not a kidnapper)
|
||||
if (host.level() instanceof ServerLevel sl) {
|
||||
for (UUID ownerId : owners) {
|
||||
// Skip self-collared (exploit prevention: player puts collar on themselves)
|
||||
if (ownerId.equals(entity.getUUID())) continue;
|
||||
if (
|
||||
sl
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(ownerId) !=
|
||||
null
|
||||
) {
|
||||
return false; // Owned by another player — stop chase
|
||||
}
|
||||
}
|
||||
}
|
||||
// All owners are NPCs/kidnappers — continue chase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NO LINE-OF-SIGHT CHECK - allow chasing through forests
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// COMPETITION DETECTION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if another kidnapper is already effectively pursuing this target.
|
||||
* Elite kidnappers bypass this check (always return false).
|
||||
*
|
||||
* Considers a target "pursued" only if:
|
||||
* - Pursuer is in active pursuit state (HUNT/CHASE)
|
||||
* - Pursuer is within 40 blocks
|
||||
* - Pursuer has active navigation OR is very close (< 5 blocks)
|
||||
*
|
||||
* @param target The target entity
|
||||
* @return true if another kidnapper is effectively pursuing, false otherwise
|
||||
*/
|
||||
public boolean isTargetBeingPursuedByOther(LivingEntity target) {
|
||||
if (target == null) return false;
|
||||
|
||||
// Elite kidnappers can always pursue, regardless of other kidnappers
|
||||
if (this.entity instanceof EntityKidnapperElite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(host.level() instanceof ServerLevel serverLevel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check nearby kidnappers (within 50 blocks)
|
||||
List<EntityKidnapper> nearbyKidnappers = serverLevel.getEntitiesOfClass(
|
||||
EntityKidnapper.class,
|
||||
target.getBoundingBox().inflate(50, 20, 50),
|
||||
k -> k != this.entity && k.isAlive()
|
||||
);
|
||||
|
||||
for (EntityKidnapper other : nearbyKidnappers) {
|
||||
LivingEntity otherTarget = other.getTarget();
|
||||
if (otherTarget != null && otherTarget.equals(target)) {
|
||||
// Check if the other kidnapper is actually pursuing effectively
|
||||
KidnapperState otherState = other.getCurrentState();
|
||||
|
||||
// Only consider "pursued" if kidnapper is in active pursuit state
|
||||
if (!otherState.isPursuit()) {
|
||||
continue; // IDLE/GUARD/PATROL with stale target - ignore
|
||||
}
|
||||
|
||||
// Check distance - if pursuer is too far (> 40 blocks), they're not effective
|
||||
double distSq = other.distanceToSqr(target);
|
||||
if (distSq > 1600) {
|
||||
// > 40 blocks squared
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if pursuer has valid navigation path (can reach target)
|
||||
if (!other.getNavigation().isInProgress() && distSq > 25) {
|
||||
// Not moving and far away - probably stuck
|
||||
continue;
|
||||
}
|
||||
|
||||
// This kidnapper is effectively pursuing the target
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PROXIMITY TARGETING
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Find the closest suitable target within a given radius.
|
||||
* Search order: Players → Damsels → MCA villagers (if loaded)
|
||||
*
|
||||
* @param radius Search radius in blocks
|
||||
* @return The closest suitable target, or null if none found
|
||||
*/
|
||||
@Nullable
|
||||
public LivingEntity getClosestSuitableTarget(int radius) {
|
||||
AABB searchBox = host.getBoundingBox().inflate(radius);
|
||||
|
||||
// Search for players first (highest priority)
|
||||
List<Player> players = host
|
||||
.level()
|
||||
.getEntitiesOfClass(Player.class, searchBox);
|
||||
for (Player player : players) {
|
||||
if (this.isSuitableTarget(player)) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
// Then search for damsels
|
||||
List<EntityDamsel> damsels = host
|
||||
.level()
|
||||
.getEntitiesOfClass(EntityDamsel.class, searchBox);
|
||||
for (EntityDamsel damsel : damsels) {
|
||||
// EntityDamsel no longer includes kidnappers (separate hierarchies)
|
||||
if (this.isSuitableTarget(damsel)) {
|
||||
return damsel;
|
||||
}
|
||||
}
|
||||
|
||||
// MCA Compatibility: Search for MCA villagers
|
||||
if (MCACompat.isMCALoaded()) {
|
||||
List<LivingEntity> entities = host
|
||||
.level()
|
||||
.getEntitiesOfClass(
|
||||
LivingEntity.class,
|
||||
searchBox,
|
||||
MCACompat::isMCAVillager // Filter only MCA villagers
|
||||
);
|
||||
for (LivingEntity mcaVillager : entities) {
|
||||
if (this.isSuitableTarget(mcaVillager)) {
|
||||
return mcaVillager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// DISTANCE HELPERS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if close to current target (within 2 blocks).
|
||||
*
|
||||
* @return true if close to target, false otherwise
|
||||
*/
|
||||
public boolean isCloseToTarget() {
|
||||
if (this.currentTarget == null) return false;
|
||||
return host.distanceTo(this.currentTarget) <= 2.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if too far from current target.
|
||||
*
|
||||
* @param radius Maximum acceptable distance
|
||||
* @return true if beyond radius, false otherwise
|
||||
*/
|
||||
public boolean isTooFarFromTarget(int radius) {
|
||||
if (this.currentTarget == null) return false;
|
||||
return host.distanceTo(this.currentTarget) > radius;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// HELPER METHODS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if a player has a protection token in their inventory.
|
||||
* Tokens protect from camp-linked kidnappers only.
|
||||
*
|
||||
* @param player The player to check
|
||||
* @return true if player has token, false otherwise
|
||||
*/
|
||||
public static boolean hasTokenInInventory(Player player) {
|
||||
for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
|
||||
ItemStack stack = player.getInventory().getItem(i);
|
||||
if (!stack.isEmpty() && stack.getItem() == ModItems.TOKEN.get()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Save target selector data to NBT.
|
||||
* Note: currentTarget is transient and not saved (recalculated on load).
|
||||
*/
|
||||
public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Target selector uses only transient runtime state
|
||||
// No persistent data needs to be saved
|
||||
}
|
||||
|
||||
/**
|
||||
* Load target selector data from NBT.
|
||||
* Note: currentTarget is transient and not loaded.
|
||||
*/
|
||||
public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
|
||||
// Target selector uses only transient runtime state
|
||||
// No persistent data needs to be loaded
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import com.tiedup.remake.entities.kidnapper.components.IAIHost;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.goal.GoalSelector;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host implementation for KidnapperAIManager callbacks.
|
||||
* Provides access to entity properties and components needed for AI goal registration.
|
||||
*/
|
||||
public class AIHost implements IAIHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public AIHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NAVIGATION & BASIC
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public PathNavigation getNavigation() {
|
||||
return entity.getNavigation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoalSelector getGoalSelector() {
|
||||
return entity.goalSelector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level getLevel() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public KidnapperState getCurrentState() {
|
||||
return entity.getCurrentState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentState(KidnapperState state) {
|
||||
entity.setCurrentState(state);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TARGET MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public LivingEntity getTarget() {
|
||||
return entity.getTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTarget(@Nullable LivingEntity target) {
|
||||
entity.setTarget(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuitableTarget(LivingEntity entity) {
|
||||
return this.entity.isSuitableTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetStillValidForChase(LivingEntity entity) {
|
||||
return this.entity.isTargetStillValidForChase(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCloseToTarget() {
|
||||
return entity.isCloseToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public LivingEntity getClosestSuitableTarget(int radius) {
|
||||
return entity.getClosestSuitableTarget(radius);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CAPTIVE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public IBondageState getCaptive() {
|
||||
// C6-V2: EntityKidnapper.getCaptive() returns IRestrainable which IS-A IBondageState
|
||||
return entity.getCaptive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCaptives() {
|
||||
return entity.hasCaptives();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// AGGRESSION SYSTEM
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public LivingEntity getLastAttacker() {
|
||||
return entity.getLastAttacker();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public LivingEntity getEscapedTarget() {
|
||||
return entity.getEscapedTarget();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ALERT SYSTEM
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public LivingEntity getAlertTarget() {
|
||||
return entity.getAlertTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlertTarget(@Nullable LivingEntity target) {
|
||||
entity.setAlertTarget(target);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CAMP SYSTEM
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public UUID getAssociatedStructure() {
|
||||
return entity.getAssociatedStructure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHunter() {
|
||||
return entity.isHunter();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CELL SYSTEM
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public List<CellDataV2> getNearbyCellsWithPrisoners() {
|
||||
return entity.getNearbyCellsWithPrisoners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BlockPos> getNearbyPatrolMarkers(int radius) {
|
||||
return entity.getNearbyPatrolMarkers(radius);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// SALE & JOB SYSTEM
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isSellingCaptive() {
|
||||
return entity.isSellingCaptive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWaitingForJobToBeCompleted() {
|
||||
return entity.isWaitingForJobToBeCompleted();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// COLLAR CONFIG
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isKidnappingModeReady() {
|
||||
return entity.isKidnappingModeReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public UUID getCellIdFromCollar() {
|
||||
return entity.getCellIdFromCollar();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CAPTURE EQUIPMENT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void setUpHeldItems() {
|
||||
entity.setUpHeldItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getBindItem() {
|
||||
return entity.getBindItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getGagItem() {
|
||||
return entity.getGagItem();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE FLAGS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isGetOutState() {
|
||||
return entity.isGetOutState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDogwalking() {
|
||||
return entity.isDogwalking();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.dialogue.EntityDialogueManager;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.kidnapper.components.IAggressionHost;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host implementation for AggressionSystem callbacks.
|
||||
* Phase 1.3: Provides access to entity properties for aggression tracking.
|
||||
*/
|
||||
public class AggressionHost implements IAggressionHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public AggressionHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void talkToPlayersInRadius(
|
||||
EntityDialogueManager.DialogueCategory category,
|
||||
int radius
|
||||
) {
|
||||
entity.talkToPlayersInRadius(category, radius);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import com.tiedup.remake.entities.kidnapper.components.IAlertHost;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* Host implementation for KidnapperAlertManager callbacks.
|
||||
* Provides access to entity properties needed for the alert system.
|
||||
*/
|
||||
public class AlertHost implements IAlertHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public AlertHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB getBoundingBox() {
|
||||
return entity.getBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperState getCurrentState() {
|
||||
return entity.getCurrentState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentState(KidnapperState state) {
|
||||
entity.setCurrentState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCaptives() {
|
||||
return entity.hasCaptives();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTiedUp() {
|
||||
return entity.isTiedUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.kidnapper.components.IAppearanceHost;
|
||||
import com.tiedup.remake.entities.skins.Gender;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host implementation for AppearanceManager callbacks.
|
||||
* Phase 1.2: Provides access to entity properties needed for appearance management.
|
||||
*/
|
||||
public class AppearanceHost implements IAppearanceHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public AppearanceHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return entity.getUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource getRandom() {
|
||||
return entity.getRandom();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlimArms(boolean slimArms) {
|
||||
entity.setSlimArms(slimArms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGender(Gender gender) {
|
||||
entity.setGender(gender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNpcName(String name) {
|
||||
entity.setNpcName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityDataString(
|
||||
EntityDataAccessor<String> accessor,
|
||||
String value
|
||||
) {
|
||||
entity.getEntityData().set(accessor, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityDataString(EntityDataAccessor<String> accessor) {
|
||||
return entity.getEntityData().get(accessor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.dialogue.EntityDialogueManager;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.damsel.components.DamselInventoryManager;
|
||||
import com.tiedup.remake.entities.damsel.components.IBondageHost;
|
||||
import com.tiedup.remake.personality.PersonalityState;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host implementation for BondageManager callbacks specific to EntityKidnapper.
|
||||
*
|
||||
* Phase 3 (AbstractTiedUpNpc migration): Created so EntityKidnapper can provide
|
||||
* its own IBondageHost instead of inheriting EntityDamsel's BondageHost.
|
||||
*
|
||||
* Key differences from the Damsel BondageHost:
|
||||
* - getPersonalityState() returns null (kidnappers have no personality system)
|
||||
* - talkToPlayersInRadius() is a no-op (kidnapper dialogue is separate)
|
||||
*
|
||||
* NOTE: This host is created during super() constructor (AbstractTiedUpNpc).
|
||||
* It must only store the entity reference, NOT access any EntityKidnapper fields
|
||||
* that haven't been initialized yet.
|
||||
*/
|
||||
public class BondageHost implements IBondageHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public BondageHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersonalityState getPersonalityState() {
|
||||
// Kidnappers don't have a personality system
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamselInventoryManager getInventory() {
|
||||
return entity.getInventoryManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropItemStack(ItemStack stack) {
|
||||
entity.spawnAtLocation(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(SoundEvent sound) {
|
||||
entity.playSound(sound, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHealth(float health) {
|
||||
entity.setHealth(health);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Entity.RemovalReason reason) {
|
||||
entity.remove(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos blockPosition() {
|
||||
return entity.blockPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return entity.getUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void talkToPlayersInRadius(
|
||||
EntityDialogueManager.DialogueCategory category,
|
||||
int radius
|
||||
) {
|
||||
// Kidnappers use their own dialogue system (KidnapperDialogueTriggerSystem),
|
||||
// not the damsel dialogue categories. No-op here.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.kidnapper.components.ICampHost;
|
||||
import net.minecraft.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Host implementation for CampManager callbacks.
|
||||
* Phase 1.4: Provides random source and name for camp assignment.
|
||||
*/
|
||||
public class CampHost implements ICampHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public CampHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource getRandom() {
|
||||
return entity.getRandom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.dialogue.EntityDialogueManager;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import com.tiedup.remake.entities.kidnapper.components.ICaptiveHost;
|
||||
import com.tiedup.remake.entities.kidnapper.components.KidnapperAggressionSystem;
|
||||
import com.tiedup.remake.entities.kidnapper.components.KidnapperAlertManager;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* Host implementation for KidnapperCaptiveManager callbacks.
|
||||
* Provides access to entity properties needed for captive management.
|
||||
*/
|
||||
public class CaptiveHost implements ICaptiveHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public CaptiveHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return entity.getUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 position() {
|
||||
return entity.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos blockPosition() {
|
||||
return entity.blockPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void setEntityData(EntityDataAccessor<T> accessor, T value) {
|
||||
entity.getEntityData().set(accessor, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTiedUp() {
|
||||
return entity.isTiedUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperState getCurrentState() {
|
||||
return entity.getCurrentState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentState(KidnapperState state) {
|
||||
entity.setCurrentState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGetOutState(boolean state) {
|
||||
entity.setGetOutState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void talkToPlayersInRadius(
|
||||
EntityDialogueManager.DialogueCategory category,
|
||||
int radius
|
||||
) {
|
||||
entity.talkToPlayersInRadius(category, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperAggressionSystem getAggressionSystem() {
|
||||
return entity.getAggressionSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperAlertManager getAlertManager() {
|
||||
return entity.getAlertManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcastAlert(LivingEntity escapee) {
|
||||
entity.broadcastAlert(escapee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLineOfSight(LivingEntity entity) {
|
||||
return this.entity.hasLineOfSight(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float distanceTo(LivingEntity entity) {
|
||||
return this.entity.distanceTo(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CellDataV2 findCellContainingPrisoner(UUID prisonerId) {
|
||||
return entity.findCellContainingPrisoner(prisonerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LivingEntity asLivingEntity() {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||
import com.tiedup.remake.entities.kidnapper.components.ICellHost;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host implementation for KidnapperCellManager callbacks.
|
||||
* Provides access to entity properties needed for cell management.
|
||||
*/
|
||||
public class CellHost implements ICellHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public CellHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos blockPosition() {
|
||||
return entity.blockPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public UUID getAssociatedStructure() {
|
||||
return entity.getAssociatedStructure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathNavigation getNavigation() {
|
||||
return entity.getNavigation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentState(KidnapperState state) {
|
||||
entity.setCurrentState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void talkToPlayersInRadius(
|
||||
com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory category,
|
||||
int radius
|
||||
) {
|
||||
entity.talkToPlayersInRadius(category, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.KidnapperJobManager;
|
||||
import com.tiedup.remake.entities.kidnapper.components.*;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
|
||||
/**
|
||||
* Host implementation for KidnapperDataSerializer callbacks.
|
||||
* Provides access to entity data and components for NBT persistence.
|
||||
*/
|
||||
public class DataSerializerHost implements IDataSerializerHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public DataSerializerHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ENTITY ACCESS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public EntityKidnapper getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ENTITY DATA ACCESS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public <T> T getEntityData(EntityDataAccessor<T> accessor) {
|
||||
return entity.getEntityData().get(accessor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void setEntityData(EntityDataAccessor<T> accessor, T value) {
|
||||
entity.getEntityData().set(accessor, value);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE FLAGS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isGetOutState() {
|
||||
return entity.isGetOutState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGetOutState(boolean state) {
|
||||
entity.setGetOutState(state);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// COMPONENT ACCESS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public KidnapperAppearance getAppearance() {
|
||||
return entity.getAppearanceComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperAggressionSystem getAggressionSystem() {
|
||||
return entity.getAggressionSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperCaptiveManager getCaptiveManager() {
|
||||
return entity.getCaptiveManagerComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperStateManager getStateManager() {
|
||||
return entity.getStateManagerComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperCampManager getCampManager() {
|
||||
return entity.getCampManagerComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperJobManager getJobManager() {
|
||||
return entity.getJobManager();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.kidnapper.components.ISaleHost;
|
||||
import com.tiedup.remake.state.IRestrainable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Host implementation for KidnapperSaleManager callbacks.
|
||||
* Provides access to entity properties needed for sale management.
|
||||
*/
|
||||
public class SaleHost implements ISaleHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public SaleHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCaptives() {
|
||||
return entity.hasCaptives();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public IRestrainable getCaptive() {
|
||||
return entity.getCaptive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGetOutState(boolean state) {
|
||||
entity.setGetOutState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAllowCaptiveTransferFlag() {
|
||||
return entity.getAllowCaptiveTransferFlag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAllowCaptiveTransferFlag(boolean flag) {
|
||||
entity.setAllowCaptiveTransferFlag(flag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.kidnapper.components.IStateHost;
|
||||
|
||||
/**
|
||||
* Host implementation for StateManager callbacks.
|
||||
* Phase 1.1: Simple host providing name access for logging.
|
||||
*/
|
||||
public class StateHost implements IStateHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public StateHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNpcName() {
|
||||
return entity.getNpcName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.tiedup.remake.entities.kidnapper.hosts;
|
||||
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.KidnapperCollarConfig;
|
||||
import com.tiedup.remake.entities.KidnapperJobManager;
|
||||
import com.tiedup.remake.entities.kidnapper.components.ITargetHost;
|
||||
import com.tiedup.remake.entities.kidnapper.components.KidnapperAggressionSystem;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.entity.ai.sensing.Sensing;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* Host implementation for KidnapperTargetSelector callbacks.
|
||||
* Provides access to entity properties needed for target selection and validation.
|
||||
*/
|
||||
public class TargetHost implements ITargetHost {
|
||||
|
||||
private final EntityKidnapper entity;
|
||||
|
||||
public TargetHost(EntityKidnapper entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level level() {
|
||||
return entity.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sensing getSensing() {
|
||||
return entity.getSensing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB getBoundingBox() {
|
||||
return entity.getBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float distanceTo(LivingEntity other) {
|
||||
return entity.distanceTo(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return entity.getUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTiedUp() {
|
||||
return entity.isTiedUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCaptives() {
|
||||
return entity.hasCaptives();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWaitingForJobToBeCompleted() {
|
||||
return entity.isWaitingForJobToBeCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGetOutState() {
|
||||
return entity.isGetOutState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKidnappingModeEnabled() {
|
||||
return entity.isKidnappingModeEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public UUID getAssociatedStructure() {
|
||||
return entity.getAssociatedStructure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathNavigation getNavigation() {
|
||||
return entity.getNavigation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperAggressionSystem getAggressionSystem() {
|
||||
return entity.getAggressionSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperJobManager getJobManager() {
|
||||
return entity.getJobManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KidnapperCollarConfig getCollarConfig() {
|
||||
return entity.getCollarConfig();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user