package com.tiedup.remake.items; import com.tiedup.remake.core.ModSounds; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.items.base.ItemOwnerTarget; import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.v2.BodyRegionV2; import java.util.ArrayList; import java.util.List; import java.util.UUID; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; public class ItemShockerController extends ItemOwnerTarget { private static final String NBT_BROADCAST = "broadcast"; private static final String NBT_RADIUS = "radius"; public ItemShockerController() { super(new net.minecraft.world.item.Item.Properties().stacksTo(1)); } @Override public void appendHoverText( ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag ) { super.appendHoverText(stack, level, tooltip, flag); appendOwnerTooltip(stack, tooltip, "Right-click a player"); if (isBroadcastEnabled(stack)) { tooltip.add( Component.literal("MODE: BROADCAST").withStyle( ChatFormatting.DARK_RED ) ); tooltip.add( Component.literal("(Affects ALL your slaves in radius)") .withStyle(ChatFormatting.GRAY) .withStyle(ChatFormatting.ITALIC) ); } else { tooltip.add( Component.literal("MODE: TARGETED").withStyle( ChatFormatting.BLUE ) ); if (hasTarget(stack)) { String displayName = getTargetName(stack); boolean isDisconnected = true; if (level != null) { Player target = level.getPlayerByUUID(getTargetId(stack)); if (target != null) { IRestrainable targetState = KidnappedHelper.getKidnappedState(target); if (targetState != null && targetState.hasCollar()) { isDisconnected = false; ItemStack collar = targetState.getEquipment( BodyRegionV2.NECK ); if ( CollarHelper.isCollar(collar) && CollarHelper.hasNickname(collar) ) { displayName = CollarHelper.getNickname(collar) + " (" + displayName + ")"; } } } } MutableComponent targetComp = Component.literal(" > ") .withStyle(ChatFormatting.BLUE) .append( Component.literal(displayName).withStyle( isDisconnected ? ChatFormatting.STRIKETHROUGH : ChatFormatting.WHITE ) ); if (isDisconnected) { targetComp.append( Component.literal(" [FREED]") .withStyle(ChatFormatting.RED) .withStyle(ChatFormatting.BOLD) ); } tooltip.add(targetComp); } else { tooltip.add( Component.literal(" > No target connected").withStyle( ChatFormatting.GRAY ) ); } } tooltip.add( Component.literal("Radius: " + getRadius(stack) + "m").withStyle( ChatFormatting.GREEN ) ); tooltip.add( Component.literal("Shift + Right-click to toggle Broadcast mode") .withStyle(ChatFormatting.DARK_GRAY) .withStyle(ChatFormatting.ITALIC) ); } @Override public InteractionResultHolder use( Level level, Player player, InteractionHand hand ) { ItemStack stack = player.getItemInHand(hand); if (player.isShiftKeyDown()) { if (!level.isClientSide) { if (hasOwner(stack) && !isOwner(stack, player)) { SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.SHOCKER_NOT_OWNER ); return InteractionResultHolder.fail(stack); } boolean newState = !isBroadcastEnabled(stack); setBroadcastEnabled(stack, newState); player.setItemInHand(hand, stack); SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.SHOCKER_MODE_SET, (newState ? "BROADCAST" : "TARGETED") ); } return InteractionResultHolder.sidedSuccess( stack, level.isClientSide() ); } if (level.isClientSide) return InteractionResultHolder.success(stack); IRestrainable playerState = KidnappedHelper.getKidnappedState(player); if ( playerState != null && playerState.isTiedUp() ) return InteractionResultHolder.fail(stack); if (!hasOwner(stack)) { SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.ERROR, "You must claim this shocker first! (Right-click a player)" ); return InteractionResultHolder.fail(stack); } if (!isOwner(stack, player)) { SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.SHOCKER_NOT_OWNER ); return InteractionResultHolder.fail(stack); } List nearbyTargets = getNearbyKidnappedTargets( level, player, stack ); if (isBroadcastEnabled(stack)) { for (LivingEntity target : nearbyTargets) { IRestrainable targetState = KidnappedHelper.getKidnappedState( target ); if (targetState != null) targetState.shockKidnapped(); } if (nearbyTargets.isEmpty()) { SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.ERROR, "No valid targets in range!" ); } else { SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.INFO, "Broadcast shock triggered! (" + nearbyTargets.size() + " targets)" ); playTriggerSound(player); } } else if (hasTarget(stack)) { Player target = level.getPlayerByUUID(getTargetId(stack)); IRestrainable targetState = target != null ? KidnappedHelper.getKidnappedState(target) : null; if ( target != null && targetState != null && targetState.hasCollar() && nearbyTargets.contains(target) ) { targetState.shockKidnapped(); String name = target.getName().getString(); SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.SHOCKER_TRIGGERED, name ); playTriggerSound(player); } else { String error = (target == null) ? "Target is out of range or in another dimension!" : (!targetState.hasCollar() ? "Target is no longer wearing a collar!" : "Target is out of range!"); SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.ERROR, error ); } } else { SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.ERROR, "No target set and broadcast is disabled!" ); } return InteractionResultHolder.success(stack); } /** */ @Override public InteractionResult interactLivingEntity( ItemStack stack, Player player, LivingEntity target, InteractionHand hand ) { if (player.level().isClientSide) return InteractionResult.SUCCESS; IRestrainable playerState = KidnappedHelper.getKidnappedState(player); if ( playerState != null && playerState.isTiedUp() ) return InteractionResult.FAIL; // Claim shocker if unclaimed if (!hasOwner(stack)) { setOwner(stack, player); SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.SHOCKER_CLAIMED ); } else if (!isOwner(stack, player)) { SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.SHOCKER_NOT_OWNER ); return InteractionResult.FAIL; } // Connect to target (works with any LivingEntity that can be kidnapped) IRestrainable targetState = KidnappedHelper.getKidnappedState(target); if (targetState != null) { setTarget(stack, target); player.setItemInHand(hand, stack); SystemMessageManager.sendChatToPlayer( player, "Connected to " + target.getName().getString(), ChatFormatting.GREEN ); return InteractionResult.SUCCESS; } return InteractionResult.PASS; } /** * Returns all kidnappable entities in range wearing shock collars owned by the shocker owner or in public mode. */ private List getNearbyKidnappedTargets( Level level, Player source, ItemStack stack ) { double radius = getRadius(stack); UUID ownerId = getOwnerId(stack); List targets = new ArrayList<>(); // Check all living entities in range for (LivingEntity entity : level.getEntitiesOfClass( LivingEntity.class, source.getBoundingBox().inflate(radius) )) { if (entity == source) continue; IRestrainable state = KidnappedHelper.getKidnappedState(entity); if (state != null && state.hasCollar()) { ItemStack collarStack = state.getEquipment(BodyRegionV2.NECK); if (CollarHelper.canShock(collarStack)) { if ( CollarHelper.isOwner(collarStack, ownerId) || CollarHelper.isPublicShock(collarStack) ) { targets.add(entity); } } } } return targets; } private void playTriggerSound(Player player) { player .level() .playSound( null, player.blockPosition(), ModSounds.SHOCKER_ACTIVATED.get(), net.minecraft.sounds.SoundSource.PLAYERS, 0.5f, 1.0f ); } public boolean isBroadcastEnabled(ItemStack stack) { CompoundTag tag = stack.getTag(); return tag != null && tag.getBoolean(NBT_BROADCAST); } public void setBroadcastEnabled(ItemStack stack, boolean enabled) { stack.getOrCreateTag().putBoolean(NBT_BROADCAST, enabled); } public int getRadius(ItemStack stack) { CompoundTag tag = stack.getTag(); return (tag != null && tag.contains(NBT_RADIUS)) ? tag.getInt(NBT_RADIUS) : com.tiedup.remake.core.SettingsAccessor.getShockerControllerRadius( null ); } public void setRadius(ItemStack stack, int radius) { stack.getOrCreateTag().putInt(NBT_RADIUS, radius); } public static ItemStack mergeShockers(List stacks) { if (stacks == null || stacks.size() <= 1) return ItemStack.EMPTY; int totalRadius = 0; for (ItemStack s : stacks) { if (s.getItem() instanceof ItemShockerController sc) { totalRadius += sc.getRadius(s); } } ItemStack result = new ItemStack(stacks.get(0).getItem()); ((ItemShockerController) result.getItem()).setRadius( result, totalRadius ); return result; } }