D1: ThreadLocal alert suppression moved from ItemCollar to CollarHelper.
onCollarRemoved() logic (kidnapper alert) moved to CollarHelper.
D2+D3: Deleted 17 V1 item classes + 4 V1-only interfaces:
ItemBind, ItemGag, ItemBlindfold, ItemCollar, ItemEarplugs, ItemMittens,
ItemColor, ItemClassicCollar, ItemShockCollar, ItemShockCollarAuto,
ItemGpsCollar, ItemChokeCollar, ItemHood, ItemMedicalGag,
IBondageItem, IHasGaggingEffect, IHasBlindingEffect, IAdjustable
D4: KidnapperTheme/KidnapperItemSelector/DispenserBehaviors migrated
from variant enums to string-based DataDrivenItemRegistry IDs.
D5: Deleted 11 variant enums + Generic* factories + ItemBallGag3D:
BindVariant, GagVariant, BlindfoldVariant, EarplugsVariant, MittensVariant,
GenericBind, GenericGag, GenericBlindfold, GenericEarplugs, GenericMittens
D6: ModItems cleaned — all V1 bondage registrations removed.
D7: ModCreativeTabs rewritten — iterates DataDrivenItemRegistry.
D8+D9: All V2 helpers cleaned (V1 fallbacks removed), orphan imports removed.
Zero V1 bondage code references remain (only Javadoc comments).
All bondage items are now data-driven via 47 JSON definitions.
398 lines
14 KiB
Java
398 lines
14 KiB
Java
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<Component> 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<ItemStack> 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<LivingEntity> 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<LivingEntity> getNearbyKidnappedTargets(
|
|
Level level,
|
|
Player source,
|
|
ItemStack stack
|
|
) {
|
|
double radius = getRadius(stack);
|
|
UUID ownerId = getOwnerId(stack);
|
|
List<LivingEntity> 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<ItemStack> 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;
|
|
}
|
|
}
|