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:
661
src/main/java/com/tiedup/remake/entities/EntityLaborGuard.java
Normal file
661
src/main/java/com/tiedup/remake/entities/EntityLaborGuard.java
Normal file
@@ -0,0 +1,661 @@
|
||||
package com.tiedup.remake.entities;
|
||||
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.cells.CellRegistryV2;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.SpeakerType;
|
||||
import com.tiedup.remake.entities.ai.guard.GuardFightBackGoal;
|
||||
import com.tiedup.remake.entities.ai.guard.GuardFollowPrisonerGoal;
|
||||
import com.tiedup.remake.entities.ai.guard.GuardHuntMonstersGoal;
|
||||
import com.tiedup.remake.entities.ai.guard.GuardMonitorGoal;
|
||||
import com.tiedup.remake.entities.skins.LaborGuardSkinManager;
|
||||
import com.tiedup.remake.labor.LaborTask;
|
||||
import com.tiedup.remake.prison.LaborRecord;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.service.PrisonerService;
|
||||
import com.tiedup.remake.state.IRestrainable;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.util.MessageDispatcher;
|
||||
import com.tiedup.remake.util.RestraintApplicator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
||||
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||||
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Labor Guard entity - Physical guard that follows and monitors a prisoner during labor.
|
||||
*
|
||||
* Extends EntityDamsel for the player model, skin system, and IRestrainable interface
|
||||
* (the guard itself can be captured/tied up as an escape mechanic).
|
||||
*
|
||||
* Spawned by MaidExtractGoal after extraction, despawned by MaidReturnGoal when
|
||||
* the prisoner returns to cell.
|
||||
*/
|
||||
public class EntityLaborGuard extends EntityDamsel {
|
||||
|
||||
// ==================== CONSTANTS ====================
|
||||
|
||||
/** Distance at which prisoner gets warning + escape countdown starts */
|
||||
public static final double WARNING_RADIUS = 20.0;
|
||||
|
||||
/** Ticks before escape after leaving warning radius (15 seconds) */
|
||||
public static final int ESCAPE_COUNTDOWN_TICKS = 300;
|
||||
|
||||
/** Distance at which guard teleports to prisoner */
|
||||
public static final double TELEPORT_DISTANCE = 32.0;
|
||||
|
||||
/** Guard name color — steel blue */
|
||||
public static final int GUARD_NAME_COLOR = 0x4682B4;
|
||||
|
||||
// ==================== SERVER FIELDS ====================
|
||||
|
||||
@Nullable
|
||||
private UUID prisonerUUID;
|
||||
|
||||
@Nullable
|
||||
private UUID campId;
|
||||
|
||||
@Nullable
|
||||
private UUID spawnerMaidId;
|
||||
|
||||
/** Prevents registerGoals from being called twice by parent constructor */
|
||||
private boolean goalsRegistered = false;
|
||||
|
||||
/** Prevents triggerEscapeOnIncapacitated from firing multiple times */
|
||||
private boolean escapeTriggered = false;
|
||||
|
||||
/** Cooldown to prevent double-speak from Forge's dual interaction packets */
|
||||
private long lastInteractTick = -100;
|
||||
|
||||
/** Set by GuardMonitorGoal when prisoner needs physical punishment */
|
||||
private boolean needsWhip = false;
|
||||
|
||||
// ==================== CONSTRUCTOR ====================
|
||||
|
||||
public EntityLaborGuard(
|
||||
EntityType<? extends EntityLaborGuard> type,
|
||||
Level level
|
||||
) {
|
||||
super(type, level);
|
||||
}
|
||||
|
||||
// ==================== ATTRIBUTES ====================
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return Mob.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, 30.0)
|
||||
.add(Attributes.MOVEMENT_SPEED, 0.30)
|
||||
.add(Attributes.ATTACK_DAMAGE, 6.0)
|
||||
.add(Attributes.FOLLOW_RANGE, 40.0)
|
||||
.add(Attributes.KNOCKBACK_RESISTANCE, 0.5);
|
||||
}
|
||||
|
||||
// ==================== AI GOALS ====================
|
||||
|
||||
/**
|
||||
* Override registerGoals to set up guard-specific AI.
|
||||
* Complete override - does not use DamselAIController goals.
|
||||
*
|
||||
* IMPORTANT: This is called twice during construction:
|
||||
* 1. By Mob() constructor (via super chain)
|
||||
* 2. By EntityDamsel() constructor at line 455
|
||||
* We use a flag to ensure goals are only registered once.
|
||||
*/
|
||||
@Override
|
||||
protected void registerGoals() {
|
||||
if (this.goalsRegistered) {
|
||||
return;
|
||||
}
|
||||
// Also skip if called during Mob's constructor before goalSelector is set
|
||||
// (shouldn't happen, but safety check)
|
||||
if (this.goalSelector == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.goalSelector.addGoal(0, new FloatGoal(this));
|
||||
this.goalSelector.addGoal(1, new GuardHuntMonstersGoal(this));
|
||||
this.goalSelector.addGoal(2, new GuardFightBackGoal(this));
|
||||
this.goalSelector.addGoal(3, new MeleeAttackGoal(this, 1.2, false));
|
||||
this.goalSelector.addGoal(4, new GuardFollowPrisonerGoal(this));
|
||||
this.goalSelector.addGoal(
|
||||
5,
|
||||
new com.tiedup.remake.entities.ai.guard.GuardWhipGoal(this)
|
||||
);
|
||||
this.goalSelector.addGoal(6, new GuardMonitorGoal(this));
|
||||
this.goalSelector.addGoal(
|
||||
8,
|
||||
new LookAtPlayerGoal(this, Player.class, 8.0F)
|
||||
);
|
||||
this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));
|
||||
|
||||
// Register command goals so the guard responds to player commands
|
||||
com.tiedup.remake.entities.damsel.components.DamselAIController.registerCommandGoals(
|
||||
this.goalSelector,
|
||||
this,
|
||||
7
|
||||
);
|
||||
|
||||
this.goalsRegistered = true;
|
||||
}
|
||||
|
||||
// ==================== SKIN TEXTURE ====================
|
||||
|
||||
/**
|
||||
* Override to use guard/ texture folder instead of damsel/.
|
||||
* DamselAppearance.getSkinTexture() hardcodes "textures/entity/damsel/",
|
||||
* so we must override to point to "textures/entity/guard/".
|
||||
*/
|
||||
@Override
|
||||
public ResourceLocation getSkinTexture() {
|
||||
String variantId = this.getVariantId();
|
||||
if (!variantId.isEmpty()) {
|
||||
return ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup",
|
||||
"textures/entity/guard/" + variantId + ".png"
|
||||
);
|
||||
}
|
||||
// Fallback to first registered guard skin
|
||||
DamselVariant fallback = LaborGuardSkinManager.CORE.getVariantForEntity(
|
||||
this.getUUID()
|
||||
);
|
||||
if (fallback != null) {
|
||||
return fallback.texture();
|
||||
}
|
||||
return ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup",
|
||||
"textures/entity/guard/feifei.png"
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== VARIANT SYSTEM ====================
|
||||
|
||||
/**
|
||||
* Override to use LaborGuardSkinManager instead of DamselSkinManager.
|
||||
*/
|
||||
@Override
|
||||
public void onAddedToWorld() {
|
||||
super.onAddedToWorld();
|
||||
|
||||
// Override variant with guard skin (server-side only)
|
||||
if (
|
||||
!this.level().isClientSide &&
|
||||
(this.getVariantId().isEmpty() ||
|
||||
LaborGuardSkinManager.CORE.getVariant(this.getVariantId()) ==
|
||||
null)
|
||||
) {
|
||||
DamselVariant variant =
|
||||
LaborGuardSkinManager.CORE.getVariantForEntity(this.getUUID());
|
||||
if (variant != null) {
|
||||
this.setVariant(variant);
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: ensure guard always has a name (belt-and-suspenders for variant loading failures)
|
||||
if (!this.level().isClientSide && this.getNpcName().isEmpty()) {
|
||||
this.setNpcName("Guard");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DISPLAY ====================
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return Component.literal(this.getNpcName()).withStyle(
|
||||
Style.EMPTY.withColor(GUARD_NAME_COLOR)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== DIALOGUE (IDialogueSpeaker overrides) ====================
|
||||
|
||||
@Override
|
||||
public SpeakerType getSpeakerType() {
|
||||
return SpeakerType.GUARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public com.tiedup.remake.personality.PersonalityType getSpeakerPersonality() {
|
||||
return com.tiedup.remake.personality.PersonalityType.FIERCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpeakerMood() {
|
||||
return 50; // Neutral
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getTargetRelation(Player player) {
|
||||
if (prisonerUUID != null && player.getUUID().equals(prisonerUUID)) {
|
||||
return "prisoner";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== TICK ====================
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
|
||||
if (
|
||||
!this.level().isClientSide &&
|
||||
this.level() instanceof ServerLevel level
|
||||
) {
|
||||
// Auto-cleanup: if tied up, trigger escape (guard is incapacitated)
|
||||
if (this.isTiedUp() && !escapeTriggered) {
|
||||
triggerEscapeOnIncapacitated(level);
|
||||
return;
|
||||
}
|
||||
|
||||
// Orphan/duplication check every 5 seconds
|
||||
if (prisonerUUID != null && tickCount % 100 == 0) {
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
LaborRecord labor = manager.getLaborRecord(prisonerUUID);
|
||||
UUID assignedGuard = labor.getGuardId();
|
||||
|
||||
if (assignedGuard == null) {
|
||||
// Guard reference was cleared (escape, reset, etc.) - orphan, discard self
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[EntityLaborGuard] Orphan guard detected (guardId=null), discarding self {}",
|
||||
this.getUUID().toString().substring(0, 8)
|
||||
);
|
||||
this.discard();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assignedGuard.equals(this.getUUID())) {
|
||||
// Different guard is assigned - duplicate, discard self
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[EntityLaborGuard] Duplicate guard detected, discarding self (assigned={}, self={})",
|
||||
assignedGuard.toString().substring(0, 8),
|
||||
this.getUUID().toString().substring(0, 8)
|
||||
);
|
||||
this.discard();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DAMAGE HANDLING ====================
|
||||
|
||||
/**
|
||||
* Override hurt to:
|
||||
* 1. Reduce monster damage by 50%
|
||||
* 2. Punish prisoner if they attack the guard
|
||||
*/
|
||||
@Override
|
||||
public boolean hurt(DamageSource source, float amount) {
|
||||
if (!this.level().isClientSide) {
|
||||
// 50% damage reduction from monsters
|
||||
if (
|
||||
source.getEntity() instanceof
|
||||
net.minecraft.world.entity.monster.Monster
|
||||
) {
|
||||
amount *= 0.5f;
|
||||
}
|
||||
|
||||
// If attacked by the prisoner, punish them (don't fight back)
|
||||
if (
|
||||
source.getEntity() instanceof ServerPlayer player &&
|
||||
prisonerUUID != null &&
|
||||
player.getUUID().equals(prisonerUUID)
|
||||
) {
|
||||
punishPrisonerAttack(player);
|
||||
return false; // Guard ignores prisoner damage
|
||||
}
|
||||
}
|
||||
|
||||
return super.hurt(source, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Punish prisoner for attacking the guard.
|
||||
*/
|
||||
private void punishPrisonerAttack(ServerPlayer prisoner) {
|
||||
IRestrainable cap = KidnappedHelper.getKidnappedState(prisoner);
|
||||
if (cap != null) {
|
||||
cap.shockKidnapped("Don't touch your guard!", 2.0f);
|
||||
RestraintApplicator.tightenBind(cap, prisoner);
|
||||
}
|
||||
|
||||
prisoner.sendSystemMessage(
|
||||
Component.literal("Attacking your guard is punished!").withStyle(
|
||||
ChatFormatting.DARK_RED
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== INTERACTION ====================
|
||||
|
||||
@Override
|
||||
protected InteractionResult mobInteract(
|
||||
Player player,
|
||||
InteractionHand hand
|
||||
) {
|
||||
if (hand != InteractionHand.MAIN_HAND) {
|
||||
return super.mobInteract(player, hand);
|
||||
}
|
||||
|
||||
if (
|
||||
!this.level().isClientSide &&
|
||||
player instanceof ServerPlayer serverPlayer &&
|
||||
prisonerUUID != null &&
|
||||
player.getUUID().equals(prisonerUUID) &&
|
||||
this.level() instanceof ServerLevel serverLevel
|
||||
) {
|
||||
// Cooldown: Forge sends INTERACT_AT + INTERACT per right-click
|
||||
long currentTick = this.tickCount;
|
||||
if (currentTick - lastInteractTick < 5) {
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
lastInteractTick = currentTick;
|
||||
|
||||
PrisonerManager manager = PrisonerManager.get(serverLevel);
|
||||
LaborRecord labor = manager.getLaborRecord(prisonerUUID);
|
||||
LaborTask task = labor.getTask();
|
||||
LaborRecord.WorkPhase phase = labor.getPhase();
|
||||
|
||||
if (phase == LaborRecord.WorkPhase.WORKING) {
|
||||
if (task != null) {
|
||||
// Refresh progress before showing
|
||||
task.checkProgress(serverPlayer, serverLevel);
|
||||
|
||||
int progress = task.getProgress();
|
||||
int quota = task.getQuota();
|
||||
int percent = task.getProgressPercent();
|
||||
guardSay(
|
||||
serverPlayer,
|
||||
"Task: " +
|
||||
task.getDescription() +
|
||||
" — " +
|
||||
progress +
|
||||
"/" +
|
||||
quota +
|
||||
" (" +
|
||||
percent +
|
||||
"%)"
|
||||
);
|
||||
} else {
|
||||
guardSay(serverPlayer, "Get to work!");
|
||||
}
|
||||
|
||||
showCampDirection(serverPlayer, serverLevel);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
if (phase == LaborRecord.WorkPhase.PENDING_RETURN) {
|
||||
guardSay(
|
||||
serverPlayer,
|
||||
"guard.labor.pending_return",
|
||||
"Walk back to camp. Follow me."
|
||||
);
|
||||
showCampDirection(serverPlayer, serverLevel);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
// Catch-all for other phases (EXTRACTING, RETURNING, etc.)
|
||||
guardSay(serverPlayer, "Follow me.");
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
return super.mobInteract(player, hand);
|
||||
}
|
||||
|
||||
// ==================== HELPERS ====================
|
||||
|
||||
/**
|
||||
* Send a formatted guard speech message to a player.
|
||||
* Tries JSON dialogue first, falls back to provided message.
|
||||
*/
|
||||
public void guardSay(
|
||||
ServerPlayer player,
|
||||
String dialogueId,
|
||||
String fallback
|
||||
) {
|
||||
String text = com.tiedup.remake.dialogue.DialogueBridge.getDialogue(
|
||||
this,
|
||||
player,
|
||||
dialogueId
|
||||
);
|
||||
if (text == null) {
|
||||
text = fallback;
|
||||
}
|
||||
MessageDispatcher.talkTo(this, player, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a formatted guard speech message (no dialogue ID, direct message).
|
||||
*/
|
||||
public void guardSay(ServerPlayer player, String message) {
|
||||
MessageDispatcher.talkTo(this, player, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show camp direction to a player.
|
||||
*/
|
||||
private void showCampDirection(ServerPlayer player, ServerLevel level) {
|
||||
if (campId == null) return;
|
||||
List<CellDataV2> campCells = CellRegistryV2.get(level).getCellsByCamp(
|
||||
campId
|
||||
);
|
||||
if (campCells.isEmpty()) return;
|
||||
BlockPos campCenter = campCells.get(0).getCorePos();
|
||||
String direction = getCardinalDirection(
|
||||
player.blockPosition(),
|
||||
campCenter
|
||||
);
|
||||
int distance = (int) Math.sqrt(
|
||||
player.blockPosition().distSqr(campCenter)
|
||||
);
|
||||
guardSay(
|
||||
player,
|
||||
"Camp is " + distance + " blocks to the " + direction + "."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cardinal direction from one position to another.
|
||||
*/
|
||||
private static String getCardinalDirection(BlockPos from, BlockPos to) {
|
||||
int dx = to.getX() - from.getX();
|
||||
int dz = to.getZ() - from.getZ();
|
||||
|
||||
// In Minecraft: -Z = north, +Z = south, +X = east, -X = west
|
||||
String ns = "";
|
||||
String ew = "";
|
||||
if (Math.abs(dz) > 2) ns = dz < 0 ? "north" : "south";
|
||||
if (Math.abs(dx) > 2) ew = dx > 0 ? "east" : "west";
|
||||
|
||||
if (!ns.isEmpty() && !ew.isEmpty()) return ns + "-" + ew;
|
||||
if (!ns.isEmpty()) return ns;
|
||||
if (!ew.isEmpty()) return ew;
|
||||
return "nearby";
|
||||
}
|
||||
|
||||
// ==================== DEATH / REMOVAL ====================
|
||||
|
||||
/**
|
||||
* Shared cleanup: clear guard reference, trigger prisoner escape, notify.
|
||||
* Guarded by escapeTriggered to prevent double-fire from die() + remove().
|
||||
*/
|
||||
private void performPrisonerCleanup(String reason) {
|
||||
if (escapeTriggered) return;
|
||||
if (this.level().isClientSide) return;
|
||||
if (!(this.level() instanceof ServerLevel level)) return;
|
||||
if (prisonerUUID == null) return;
|
||||
|
||||
escapeTriggered = true;
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[EntityLaborGuard] Guard {} - triggering escape for prisoner {}",
|
||||
reason,
|
||||
prisonerUUID.toString().substring(0, 8)
|
||||
);
|
||||
|
||||
// Clear guard reference
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
LaborRecord labor = manager.getLaborRecord(prisonerUUID);
|
||||
labor.setGuardId(null);
|
||||
|
||||
// Trigger escape
|
||||
PrisonerService.get().escape(level, prisonerUUID, reason);
|
||||
|
||||
// Notify prisoner
|
||||
ServerPlayer prisoner = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(prisonerUUID);
|
||||
if (prisoner != null) {
|
||||
prisoner.sendSystemMessage(
|
||||
Component.literal(
|
||||
"Your guard has been eliminated! You are free!"
|
||||
).withStyle(ChatFormatting.GREEN, ChatFormatting.BOLD)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the guard dies, trigger IMMEDIATE escape for the prisoner.
|
||||
*/
|
||||
@Override
|
||||
public void die(DamageSource source) {
|
||||
performPrisonerCleanup("guard eliminated");
|
||||
super.die(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle non-death removal (e.g. /kill, chunk unload, dimension change).
|
||||
* DISCARDED is intentional (orphan self-cleanup) — already handled by tick check.
|
||||
*/
|
||||
@Override
|
||||
public void remove(RemovalReason reason) {
|
||||
if (reason != RemovalReason.DISCARDED) {
|
||||
performPrisonerCleanup("guard removed (" + reason.name() + ")");
|
||||
}
|
||||
super.remove(reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger escape when the guard is incapacitated (tied up / captured).
|
||||
*/
|
||||
private void triggerEscapeOnIncapacitated(ServerLevel level) {
|
||||
performPrisonerCleanup("guard captured");
|
||||
// Discard the guard after triggering escape
|
||||
this.discard();
|
||||
}
|
||||
|
||||
// ==================== GETTERS/SETTERS ====================
|
||||
|
||||
@Nullable
|
||||
public UUID getPrisonerUUID() {
|
||||
return prisonerUUID;
|
||||
}
|
||||
|
||||
public void setPrisonerUUID(@Nullable UUID prisonerUUID) {
|
||||
this.prisonerUUID = prisonerUUID;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UUID getCampId() {
|
||||
return campId;
|
||||
}
|
||||
|
||||
public void setCampId(@Nullable UUID campId) {
|
||||
this.campId = campId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UUID getSpawnerMaidId() {
|
||||
return spawnerMaidId;
|
||||
}
|
||||
|
||||
public void setSpawnerMaidId(@Nullable UUID spawnerMaidId) {
|
||||
this.spawnerMaidId = spawnerMaidId;
|
||||
}
|
||||
|
||||
public boolean needsWhip() {
|
||||
return needsWhip;
|
||||
}
|
||||
|
||||
public void setNeedsWhip(boolean needsWhip) {
|
||||
this.needsWhip = needsWhip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prisoner player entity (server-side).
|
||||
*
|
||||
* @return The prisoner player, or null if offline or no prisoner assigned
|
||||
*/
|
||||
@Nullable
|
||||
public ServerPlayer getPrisoner() {
|
||||
if (prisonerUUID == null) return null;
|
||||
if (!(this.level() instanceof ServerLevel level)) return null;
|
||||
return level.getServer().getPlayerList().getPlayer(prisonerUUID);
|
||||
}
|
||||
|
||||
// ==================== NBT PERSISTENCE ====================
|
||||
|
||||
@Override
|
||||
public void addAdditionalSaveData(CompoundTag tag) {
|
||||
super.addAdditionalSaveData(tag);
|
||||
|
||||
if (prisonerUUID != null) {
|
||||
tag.putUUID("PrisonerUUID", prisonerUUID);
|
||||
}
|
||||
if (campId != null) {
|
||||
tag.putUUID("CampId", campId);
|
||||
}
|
||||
if (spawnerMaidId != null) {
|
||||
tag.putUUID("SpawnerMaidId", spawnerMaidId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAdditionalSaveData(CompoundTag tag) {
|
||||
super.readAdditionalSaveData(tag);
|
||||
|
||||
if (tag.contains("PrisonerUUID")) {
|
||||
this.prisonerUUID = tag.getUUID("PrisonerUUID");
|
||||
}
|
||||
if (tag.contains("CampId")) {
|
||||
this.campId = tag.getUUID("CampId");
|
||||
}
|
||||
if (tag.contains("SpawnerMaidId")) {
|
||||
this.spawnerMaidId = tag.getUUID("SpawnerMaidId");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DESPAWN PROTECTION ====================
|
||||
|
||||
@Override
|
||||
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistenceRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user