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:
537
src/main/java/com/tiedup/remake/items/ItemCommandWand.java
Normal file
537
src/main/java/com/tiedup/remake/items/ItemCommandWand.java
Normal file
@@ -0,0 +1,537 @@
|
||||
package com.tiedup.remake.items;
|
||||
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.cells.CellRegistryV2;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityDamsel;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.personality.PacketOpenCommandWandScreen;
|
||||
import com.tiedup.remake.personality.HomeType;
|
||||
import com.tiedup.remake.personality.JobExperience;
|
||||
import com.tiedup.remake.personality.NpcCommand;
|
||||
import com.tiedup.remake.personality.NpcNeeds;
|
||||
import com.tiedup.remake.personality.PersonalityState;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.ChestBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* Command Wand - Used to give commands to collared NPCs.
|
||||
*
|
||||
* Personality System Phase E: Command Wand Item
|
||||
*
|
||||
* <p><b>Usage:</b></p>
|
||||
* <ul>
|
||||
* <li>Right-click on a collared NPC you own to open command GUI</li>
|
||||
* <li>Shows personality info, needs, and available commands</li>
|
||||
* <li>Only works on NPCs wearing a collar you own</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ItemCommandWand extends Item {
|
||||
|
||||
// NBT tags for selection mode
|
||||
private static final String TAG_SELECTING_FOR = "SelectingFor";
|
||||
private static final String TAG_JOB_TYPE = "JobType";
|
||||
// Two-click selection for TRANSFER
|
||||
private static final String TAG_SELECTING_STEP = "SelectingStep"; // 1 = chest A, 2 = chest B
|
||||
private static final String TAG_FIRST_CHEST_POS = "FirstChestPos"; // BlockPos of chest A
|
||||
|
||||
public ItemCommandWand() {
|
||||
super(new Item.Properties().stacksTo(1));
|
||||
}
|
||||
|
||||
// ========== Selection Mode Methods ==========
|
||||
|
||||
/**
|
||||
* Check if wand is in selection mode (waiting for chest click).
|
||||
*/
|
||||
public static boolean isInSelectionMode(ItemStack stack) {
|
||||
return stack.hasTag() && stack.getTag().contains(TAG_SELECTING_FOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter selection mode - waiting for player to click a chest.
|
||||
*
|
||||
* HIGH FIX: Now uses UUID instead of entity ID for persistence across restarts.
|
||||
*
|
||||
* @param stack The wand item stack
|
||||
* @param entityUUID The NPC entity UUID (persistent)
|
||||
* @param job The job command to assign
|
||||
*/
|
||||
public static void enterSelectionMode(
|
||||
ItemStack stack,
|
||||
java.util.UUID entityUUID,
|
||||
NpcCommand job
|
||||
) {
|
||||
stack.getOrCreateTag().putUUID(TAG_SELECTING_FOR, entityUUID);
|
||||
stack.getOrCreateTag().putString(TAG_JOB_TYPE, job.name());
|
||||
// For TRANSFER, we need two clicks
|
||||
if (job == NpcCommand.TRANSFER) {
|
||||
stack.getOrCreateTag().putInt(TAG_SELECTING_STEP, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit selection mode.
|
||||
*/
|
||||
public static void exitSelectionMode(ItemStack stack) {
|
||||
if (stack.hasTag()) {
|
||||
stack.getTag().remove(TAG_SELECTING_FOR);
|
||||
stack.getTag().remove(TAG_JOB_TYPE);
|
||||
stack.getTag().remove(TAG_SELECTING_STEP);
|
||||
stack.getTag().remove(TAG_FIRST_CHEST_POS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current selection step (1 = first chest, 2 = second chest).
|
||||
* Returns 0 if not in multi-step selection.
|
||||
*/
|
||||
public static int getSelectingStep(ItemStack stack) {
|
||||
return stack.hasTag() ? stack.getTag().getInt(TAG_SELECTING_STEP) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first chest position (for TRANSFER).
|
||||
*/
|
||||
@Nullable
|
||||
public static BlockPos getFirstChestPos(ItemStack stack) {
|
||||
if (!stack.hasTag() || !stack.getTag().contains(TAG_FIRST_CHEST_POS)) {
|
||||
return null;
|
||||
}
|
||||
return BlockPos.of(stack.getTag().getLong(TAG_FIRST_CHEST_POS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the first chest position and advance to step 2.
|
||||
*/
|
||||
public static void setFirstChestAndAdvance(ItemStack stack, BlockPos pos) {
|
||||
stack.getOrCreateTag().putLong(TAG_FIRST_CHEST_POS, pos.asLong());
|
||||
stack.getOrCreateTag().putInt(TAG_SELECTING_STEP, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity UUID being selected for.
|
||||
*
|
||||
* HIGH FIX: Returns UUID instead of entity ID for persistence.
|
||||
*/
|
||||
@Nullable
|
||||
public static java.util.UUID getSelectingForEntity(ItemStack stack) {
|
||||
if (!stack.hasTag() || !stack.getTag().hasUUID(TAG_SELECTING_FOR)) {
|
||||
return null;
|
||||
}
|
||||
return stack.getTag().getUUID(TAG_SELECTING_FOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job type being assigned.
|
||||
*/
|
||||
@Nullable
|
||||
public static NpcCommand getSelectingJobType(ItemStack stack) {
|
||||
if (!stack.hasTag() || !stack.getTag().contains(TAG_JOB_TYPE)) {
|
||||
return null;
|
||||
}
|
||||
return NpcCommand.fromString(stack.getTag().getString(TAG_JOB_TYPE));
|
||||
}
|
||||
|
||||
// ========== Block Interaction (Chest Selection) ==========
|
||||
|
||||
@Override
|
||||
public InteractionResult useOn(UseOnContext context) {
|
||||
ItemStack stack = context.getItemInHand();
|
||||
Player player = context.getPlayer();
|
||||
|
||||
if (player == null) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
BlockPos clickedPos = context.getClickedPos();
|
||||
Level level = context.getLevel();
|
||||
BlockState blockState = level.getBlockState(clickedPos);
|
||||
|
||||
// Selection mode for job commands
|
||||
if (!isInSelectionMode(stack)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// Must click on a chest
|
||||
if (!(blockState.getBlock() instanceof ChestBlock)) {
|
||||
if (!level.isClientSide) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
"You must click on a chest to set the work zone!"
|
||||
);
|
||||
}
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// Server-side: directly apply the command (we're already on server)
|
||||
if (!level.isClientSide) {
|
||||
// HIGH FIX: Use UUID instead of entity ID for persistence
|
||||
java.util.UUID entityUUID = getSelectingForEntity(stack);
|
||||
NpcCommand command = getSelectingJobType(stack);
|
||||
int selectingStep = getSelectingStep(stack);
|
||||
|
||||
if (command != null && entityUUID != null) {
|
||||
// TRANSFER requires two chests
|
||||
if (command == NpcCommand.TRANSFER) {
|
||||
if (selectingStep == 1) {
|
||||
// First click: store source chest A
|
||||
setFirstChestAndAdvance(stack, clickedPos);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Source chest set at " +
|
||||
clickedPos.toShortString() +
|
||||
". Now click on the DESTINATION chest."
|
||||
);
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (selectingStep == 2) {
|
||||
// Second click: apply command with both chests
|
||||
BlockPos sourceChest = getFirstChestPos(stack);
|
||||
if (sourceChest == null) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
"Error: Source chest not set. Try again."
|
||||
);
|
||||
exitSelectionMode(stack);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// Prevent selecting same chest twice
|
||||
if (sourceChest.equals(clickedPos)) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
"Destination must be different from source chest!"
|
||||
);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// Find the NPC entity (HIGH FIX: lookup by UUID)
|
||||
net.minecraft.world.entity.Entity entity = (
|
||||
(net.minecraft.server.level.ServerLevel) level
|
||||
).getEntity(entityUUID);
|
||||
if (entity instanceof EntityDamsel damsel) {
|
||||
// Give TRANSFER command with source (commandTarget) and dest (commandTarget2)
|
||||
boolean success = damsel.giveCommandWithTwoTargets(
|
||||
player,
|
||||
command,
|
||||
sourceChest, // Source chest A
|
||||
clickedPos // Destination chest B
|
||||
);
|
||||
|
||||
if (success) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
damsel.getNpcName() +
|
||||
" will transfer items from " +
|
||||
sourceChest.toShortString() +
|
||||
" to " +
|
||||
clickedPos.toShortString()
|
||||
);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[ItemCommandWand] {} set TRANSFER for {} from {} to {}",
|
||||
player.getName().getString(),
|
||||
damsel.getNpcName(),
|
||||
sourceChest,
|
||||
clickedPos
|
||||
);
|
||||
} else {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
damsel.getNpcName() + " refused the job!"
|
||||
);
|
||||
}
|
||||
}
|
||||
exitSelectionMode(stack);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// Standard single-chest job commands (FARM, COOK, SHEAR, etc.) (HIGH FIX: lookup by UUID)
|
||||
net.minecraft.world.entity.Entity entity = (
|
||||
(net.minecraft.server.level.ServerLevel) level
|
||||
).getEntity(entityUUID);
|
||||
if (entity instanceof EntityDamsel damsel) {
|
||||
// Give command directly (already validated acceptance in PacketNpcCommand)
|
||||
boolean success = damsel.giveCommand(
|
||||
player,
|
||||
command,
|
||||
clickedPos
|
||||
);
|
||||
|
||||
if (success) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Work zone set! " +
|
||||
damsel.getNpcName() +
|
||||
" will " +
|
||||
command.name() +
|
||||
" at " +
|
||||
clickedPos.toShortString()
|
||||
);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[ItemCommandWand] {} set work zone for {} at {}",
|
||||
player.getName().getString(),
|
||||
damsel.getNpcName(),
|
||||
clickedPos
|
||||
);
|
||||
} else {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
damsel.getNpcName() + " refused the job!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit selection mode
|
||||
exitSelectionMode(stack);
|
||||
}
|
||||
}
|
||||
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
// ========== Entity Interaction ==========
|
||||
|
||||
@Override
|
||||
public InteractionResult interactLivingEntity(
|
||||
ItemStack stack,
|
||||
Player player,
|
||||
LivingEntity target,
|
||||
InteractionHand hand
|
||||
) {
|
||||
// Only works on EntityDamsel (and subclasses like EntityKidnapper)
|
||||
if (!(target instanceof EntityDamsel damsel)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// Server-side only
|
||||
if (player.level().isClientSide) {
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
// Must have collar
|
||||
if (!damsel.hasCollar()) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
damsel.getNpcName() + " is not wearing a collar!"
|
||||
);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// Get collar and verify ownership
|
||||
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
|
||||
if (!(collar.getItem() instanceof ItemCollar collarItem)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
if (!collarItem.getOwners(collar).contains(player.getUUID())) {
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
"You don't own " + damsel.getNpcName() + "'s collar!"
|
||||
);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// Get personality data
|
||||
PersonalityState state = damsel.getPersonalityState();
|
||||
if (state == null) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[ItemCommandWand] No personality state for {}",
|
||||
damsel.getNpcName()
|
||||
);
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// Always show personality (discovery system removed)
|
||||
String personalityName = state.getPersonality().name();
|
||||
|
||||
// Get home type
|
||||
String homeType = state.getHomeType().name();
|
||||
|
||||
// Cell info for GUI
|
||||
String cellName = "";
|
||||
String cellQualityName = "";
|
||||
if (
|
||||
state.getCellId() != null &&
|
||||
player.level() instanceof net.minecraft.server.level.ServerLevel sl
|
||||
) {
|
||||
CellDataV2 cell = CellRegistryV2.get(sl).getCell(state.getCellId());
|
||||
if (cell != null) {
|
||||
cellName =
|
||||
cell.getName() != null
|
||||
? cell.getName()
|
||||
: "Cell " + cell.getId().toString().substring(0, 8);
|
||||
}
|
||||
}
|
||||
cellQualityName =
|
||||
state.getCellQuality() != null ? state.getCellQuality().name() : "";
|
||||
|
||||
// Get needs
|
||||
NpcNeeds needs = state.getNeeds();
|
||||
|
||||
// Get job experience for active command
|
||||
JobExperience jobExp = state.getJobExperience();
|
||||
NpcCommand activeCmd = state.getActiveCommand();
|
||||
String activeJobLevelName = "";
|
||||
int activeJobXp = 0;
|
||||
int activeJobXpMax = 10;
|
||||
if (activeCmd.isActiveJob()) {
|
||||
JobExperience.JobLevel level = jobExp.getJobLevel(activeCmd);
|
||||
activeJobLevelName = level.name();
|
||||
activeJobXp = jobExp.getExperience(activeCmd);
|
||||
activeJobXpMax = level.maxExp;
|
||||
}
|
||||
|
||||
// Send packet to open GUI (HIGH FIX: pass UUID instead of entity ID)
|
||||
if (player instanceof ServerPlayer sp) {
|
||||
ModNetwork.sendToPlayer(
|
||||
new PacketOpenCommandWandScreen(
|
||||
damsel.getUUID(),
|
||||
damsel.getNpcName(),
|
||||
personalityName,
|
||||
activeCmd.name(),
|
||||
needs.getHunger(),
|
||||
needs.getRest(),
|
||||
state.getMood(),
|
||||
state.getFollowDistance().name(),
|
||||
homeType,
|
||||
state.isAutoRestEnabled(),
|
||||
cellName,
|
||||
cellQualityName,
|
||||
activeJobLevelName,
|
||||
activeJobXp,
|
||||
activeJobXpMax
|
||||
),
|
||||
sp
|
||||
);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[ItemCommandWand] {} opened command wand for {}",
|
||||
player.getName().getString(),
|
||||
damsel.getNpcName()
|
||||
);
|
||||
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(
|
||||
ItemStack stack,
|
||||
@Nullable Level level,
|
||||
List<Component> tooltip,
|
||||
TooltipFlag flag
|
||||
) {
|
||||
if (isInSelectionMode(stack)) {
|
||||
NpcCommand job = getSelectingJobType(stack);
|
||||
int step = getSelectingStep(stack);
|
||||
tooltip.add(
|
||||
Component.literal("SELECTION MODE").withStyle(
|
||||
ChatFormatting.GOLD,
|
||||
ChatFormatting.BOLD
|
||||
)
|
||||
);
|
||||
// Different messages for TRANSFER two-step selection
|
||||
if (job == NpcCommand.TRANSFER) {
|
||||
if (step == 1) {
|
||||
tooltip.add(
|
||||
Component.literal("Click SOURCE chest (1/2)").withStyle(
|
||||
ChatFormatting.YELLOW
|
||||
)
|
||||
);
|
||||
} else if (step == 2) {
|
||||
BlockPos source = getFirstChestPos(stack);
|
||||
tooltip.add(
|
||||
Component.literal(
|
||||
"Click DESTINATION chest (2/2)"
|
||||
).withStyle(ChatFormatting.YELLOW)
|
||||
);
|
||||
if (source != null) {
|
||||
tooltip.add(
|
||||
Component.literal(
|
||||
"Source: " + source.toShortString()
|
||||
).withStyle(ChatFormatting.GRAY)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tooltip.add(
|
||||
Component.literal(
|
||||
"Click a chest to set work zone"
|
||||
).withStyle(ChatFormatting.YELLOW)
|
||||
);
|
||||
}
|
||||
if (job != null) {
|
||||
tooltip.add(
|
||||
Component.literal("Job: " + job.name()).withStyle(
|
||||
ChatFormatting.AQUA
|
||||
)
|
||||
);
|
||||
}
|
||||
tooltip.add(Component.literal(""));
|
||||
tooltip.add(
|
||||
Component.literal("Right-click empty to cancel").withStyle(
|
||||
ChatFormatting.RED
|
||||
)
|
||||
);
|
||||
} else {
|
||||
tooltip.add(
|
||||
Component.literal("Right-click a collared NPC").withStyle(
|
||||
ChatFormatting.GRAY
|
||||
)
|
||||
);
|
||||
tooltip.add(
|
||||
Component.literal("to give commands").withStyle(
|
||||
ChatFormatting.GRAY
|
||||
)
|
||||
);
|
||||
tooltip.add(Component.literal(""));
|
||||
tooltip.add(
|
||||
Component.literal("Commands: FOLLOW, STAY, HEEL...").withStyle(
|
||||
ChatFormatting.DARK_PURPLE
|
||||
)
|
||||
);
|
||||
tooltip.add(
|
||||
Component.literal("Jobs: FARM, COOK, STORE").withStyle(
|
||||
ChatFormatting.GREEN
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFoil(ItemStack stack) {
|
||||
// Enchantment glint when in selection mode
|
||||
return isInSelectionMode(stack);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user