Phase 1 (state): PlayerBindState, PlayerCaptorManager, PlayerEquipment, PlayerDataRetrieval, PlayerLifecycle, PlayerShockCollar, StruggleAccessory Phase 2 (client): AnimationTickHandler, NpcAnimationTickHandler, 5 render handlers, DamselModel, 3 client mixins, SelfBondageInputHandler, SlaveManagementScreen, ActionPanel, SlaveEntryWidget, ModKeybindings Phase 3 (entities): 28 entity/AI files migrated to CollarHelper, BindModeHelper, PoseTypeHelper, createStack() Phase 4 (network): PacketSlaveAction, PacketMasterEquip, PacketAssignCellToCollar, PacketNpcCommand, PacketFurnitureForcemount Phase 5 (events): RestraintTaskTickHandler, PetPlayRestrictionHandler, PlayerEnslavementHandler, ChatEventHandler, LaborAttackPunishmentHandler Phase 6 (commands): BondageSubCommand, CollarCommand, NPCCommand, KidnapSetCommand Phase 7 (compat): MCAKidnappedAdapter, MCA mixins Phase 8 (misc): GagTalkManager, PetRequestManager, HangingCagePiece, BondageItemBlockEntity, TrappedChestBlockEntity, DispenserBehaviors, BondageItemLoaderUtility, RestraintApplicator, StruggleSessionManager, MovementStyleResolver, CampLifecycleManager Some files retain dual V1/V2 checks (instanceof V1 || V2Helper) for coexistence — V1-only branches removed in Branch D.
492 lines
15 KiB
Java
492 lines
15 KiB
Java
package com.tiedup.remake.entities;
|
|
|
|
import com.tiedup.remake.items.base.*;
|
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
|
import java.util.Random;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
/**
|
|
* Helper class for selecting themed items for kidnappers.
|
|
* Handles probability-based item selection and color application.
|
|
*
|
|
* Item selection order (most to least common):
|
|
* 1. Bind (arms) - 100% (always)
|
|
* 2. Gag - 50%
|
|
* 3. Mittens - 40%
|
|
* 4. Earplugs - 30%
|
|
* 5. Blindfold - 20% (last, most restrictive)
|
|
*
|
|
* Elite kidnappers get significant bonuses to all probabilities.
|
|
*/
|
|
public class KidnapperItemSelector {
|
|
|
|
private static final Random RANDOM = new Random();
|
|
|
|
/** NBT key for item color */
|
|
public static final String NBT_ITEM_COLOR = "ItemColor";
|
|
|
|
// === BASE PROBABILITIES (Regular Kidnapper) ===
|
|
public static final double PROB_BIND = 1.0; // 100%
|
|
public static final double PROB_GAG = 0.5; // 50%
|
|
public static final double PROB_MITTENS = 0.4; // 40%
|
|
public static final double PROB_EARPLUGS = 0.3; // 30%
|
|
public static final double PROB_BLINDFOLD = 0.2; // 20%
|
|
|
|
// === ELITE KIDNAPPER BONUSES ===
|
|
public static final double ELITE_GAG_BONUS = 0.5; // +50% = 100%
|
|
public static final double ELITE_MITTENS_BONUS = 0.4; // +40% = 80%
|
|
public static final double ELITE_EARPLUGS_BONUS = 0.3; // +30% = 60%
|
|
public static final double ELITE_BLINDFOLD_BONUS = 0.2; // +20% = 40%
|
|
|
|
// === ARCHER KIDNAPPER PENALTIES (relies on arrows) ===
|
|
public static final double ARCHER_GAG_PENALTY = 0.2; // -20% = 30%
|
|
public static final double ARCHER_MITTENS_PENALTY = 0.2; // -20% = 20%
|
|
public static final double ARCHER_EARPLUGS_PENALTY = 0.15; // -15% = 15%
|
|
public static final double ARCHER_BLINDFOLD_PENALTY = 0.1; // -10% = 10%
|
|
|
|
/**
|
|
* Result of item selection for a kidnapper.
|
|
* Contains theme, color, and all selected items.
|
|
*/
|
|
public static class SelectionResult {
|
|
|
|
public final KidnapperTheme theme;
|
|
public final ItemColor color; // null if theme doesn't support colors
|
|
public final ItemStack bind;
|
|
public final ItemStack gag;
|
|
public final ItemStack mittens;
|
|
public final ItemStack earplugs;
|
|
public final ItemStack blindfold;
|
|
|
|
public SelectionResult(
|
|
KidnapperTheme theme,
|
|
ItemColor color,
|
|
ItemStack bind,
|
|
ItemStack gag,
|
|
ItemStack mittens,
|
|
ItemStack earplugs,
|
|
ItemStack blindfold
|
|
) {
|
|
this.theme = theme;
|
|
this.color = color;
|
|
this.bind = bind;
|
|
this.gag = gag;
|
|
this.mittens = mittens;
|
|
this.earplugs = earplugs;
|
|
this.blindfold = blindfold;
|
|
}
|
|
|
|
/**
|
|
* Check if this selection has a gag.
|
|
*/
|
|
public boolean hasGag() {
|
|
return !gag.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Check if this selection has mittens.
|
|
*/
|
|
public boolean hasMittens() {
|
|
return !mittens.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Check if this selection has earplugs.
|
|
*/
|
|
public boolean hasEarplugs() {
|
|
return !earplugs.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Check if this selection has a blindfold.
|
|
*/
|
|
public boolean hasBlindfold() {
|
|
return !blindfold.isEmpty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select items for a regular kidnapper.
|
|
*/
|
|
public static SelectionResult selectForKidnapper() {
|
|
return selectItems(false, false);
|
|
}
|
|
|
|
/**
|
|
* Select items for an elite kidnapper (higher probabilities).
|
|
*/
|
|
public static SelectionResult selectForEliteKidnapper() {
|
|
return selectItems(true, false);
|
|
}
|
|
|
|
/**
|
|
* Select items for an archer kidnapper (lower probabilities - relies on arrows).
|
|
*/
|
|
public static SelectionResult selectForArcherKidnapper() {
|
|
return selectItems(false, true);
|
|
}
|
|
|
|
/**
|
|
* Calculate adjusted probability based on kidnapper type.
|
|
*
|
|
* @param baseProb Base probability for the item
|
|
* @param eliteBonus Bonus probability for elite kidnappers
|
|
* @param archerPenalty Penalty probability for archer kidnappers
|
|
* @param isElite Whether the kidnapper is elite
|
|
* @param isArcher Whether the kidnapper is an archer
|
|
* @return Adjusted probability
|
|
*/
|
|
private static double getAdjustedProbability(
|
|
double baseProb,
|
|
double eliteBonus,
|
|
double archerPenalty,
|
|
boolean isElite,
|
|
boolean isArcher
|
|
) {
|
|
double prob = baseProb;
|
|
if (isElite) prob += eliteBonus;
|
|
if (isArcher) prob -= archerPenalty;
|
|
return prob;
|
|
}
|
|
|
|
/**
|
|
* Internal item selection logic.
|
|
*/
|
|
private static SelectionResult selectItems(
|
|
boolean isElite,
|
|
boolean isArcher
|
|
) {
|
|
// 1. Select random theme
|
|
KidnapperTheme theme = KidnapperTheme.getRandomWeighted();
|
|
|
|
// 2. Select color (if theme supports it)
|
|
// Filter out colors that don't have textures for this theme's bind
|
|
ItemColor color = theme.supportsColor()
|
|
? getValidColorForBind(theme.getBind())
|
|
: null;
|
|
|
|
// 3. Create bind (always)
|
|
ItemStack bind = createBind(theme.getBind(), color);
|
|
|
|
// 4. Roll for gag (randomly selected from theme's compatible gags)
|
|
ItemStack gag = ItemStack.EMPTY;
|
|
double gagProb = getAdjustedProbability(
|
|
PROB_GAG,
|
|
ELITE_GAG_BONUS,
|
|
ARCHER_GAG_PENALTY,
|
|
isElite,
|
|
isArcher
|
|
);
|
|
if (RANDOM.nextDouble() < gagProb) {
|
|
gag = createGag(theme.getRandomGag(), color);
|
|
}
|
|
|
|
// 5. Roll for mittens (same for all themes)
|
|
ItemStack mittens = ItemStack.EMPTY;
|
|
double mittensProb = getAdjustedProbability(
|
|
PROB_MITTENS,
|
|
ELITE_MITTENS_BONUS,
|
|
ARCHER_MITTENS_PENALTY,
|
|
isElite,
|
|
isArcher
|
|
);
|
|
if (RANDOM.nextDouble() < mittensProb) {
|
|
mittens = createMittens();
|
|
}
|
|
|
|
// 6. Roll for earplugs (same for all themes)
|
|
ItemStack earplugs = ItemStack.EMPTY;
|
|
double earplugsProb = getAdjustedProbability(
|
|
PROB_EARPLUGS,
|
|
ELITE_EARPLUGS_BONUS,
|
|
ARCHER_EARPLUGS_PENALTY,
|
|
isElite,
|
|
isArcher
|
|
);
|
|
if (RANDOM.nextDouble() < earplugsProb) {
|
|
earplugs = createEarplugs();
|
|
}
|
|
|
|
// 7. Roll for blindfold (last, most restrictive - randomly selected)
|
|
ItemStack blindfold = ItemStack.EMPTY;
|
|
double blindfoldProb = getAdjustedProbability(
|
|
PROB_BLINDFOLD,
|
|
ELITE_BLINDFOLD_BONUS,
|
|
ARCHER_BLINDFOLD_PENALTY,
|
|
isElite,
|
|
isArcher
|
|
);
|
|
if (theme.hasBlindfolds() && RANDOM.nextDouble() < blindfoldProb) {
|
|
blindfold = createBlindfold(theme.getRandomBlindfold(), color);
|
|
}
|
|
|
|
return new SelectionResult(
|
|
theme,
|
|
color,
|
|
bind,
|
|
gag,
|
|
mittens,
|
|
earplugs,
|
|
blindfold
|
|
);
|
|
}
|
|
|
|
// ITEM CREATION METHODS
|
|
|
|
/**
|
|
* Create a bind ItemStack with optional color.
|
|
*/
|
|
public static ItemStack createBind(
|
|
BindVariant variant,
|
|
@Nullable ItemColor color
|
|
) {
|
|
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
new ResourceLocation("tiedup", variant.getRegistryName())
|
|
);
|
|
if (color != null && variant.supportsColor()) {
|
|
applyColor(stack, color);
|
|
}
|
|
return stack;
|
|
}
|
|
|
|
/**
|
|
* Create a gag ItemStack with optional color.
|
|
* Validates that the color has a texture for this gag variant.
|
|
*/
|
|
public static ItemStack createGag(
|
|
GagVariant variant,
|
|
@Nullable ItemColor color
|
|
) {
|
|
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
new ResourceLocation("tiedup", variant.getRegistryName())
|
|
);
|
|
if (
|
|
color != null &&
|
|
variant.supportsColor() &&
|
|
isColorValidForGag(color, variant)
|
|
) {
|
|
applyColor(stack, color);
|
|
}
|
|
return stack;
|
|
}
|
|
|
|
/**
|
|
* Create a blindfold ItemStack with optional color.
|
|
* Validates that the color has a texture for this blindfold variant.
|
|
*/
|
|
public static ItemStack createBlindfold(
|
|
BlindfoldVariant variant,
|
|
@Nullable ItemColor color
|
|
) {
|
|
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
new ResourceLocation("tiedup", variant.getRegistryName())
|
|
);
|
|
if (
|
|
color != null &&
|
|
variant.supportsColor() &&
|
|
isColorValidForBlindfold(color, variant)
|
|
) {
|
|
applyColor(stack, color);
|
|
}
|
|
return stack;
|
|
}
|
|
|
|
/**
|
|
* Create mittens ItemStack.
|
|
* Mittens don't have color variants.
|
|
*/
|
|
public static ItemStack createMittens() {
|
|
return DataDrivenBondageItem.createStack(
|
|
new ResourceLocation("tiedup", "leather_mittens")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create earplugs ItemStack.
|
|
* Earplugs don't have color variants.
|
|
*/
|
|
public static ItemStack createEarplugs() {
|
|
return DataDrivenBondageItem.createStack(
|
|
new ResourceLocation("tiedup", "classic_earplugs")
|
|
);
|
|
}
|
|
|
|
// COLOR METHODS
|
|
|
|
/** NBT key for CustomModelData (used for model overrides) */
|
|
public static final String NBT_CUSTOM_MODEL_DATA = "CustomModelData";
|
|
|
|
/**
|
|
* Apply color NBT to an ItemStack.
|
|
* Sets both the ItemColor name and CustomModelData for model selection.
|
|
*/
|
|
public static void applyColor(ItemStack stack, ItemColor color) {
|
|
if (stack.isEmpty() || color == null) return;
|
|
CompoundTag tag = stack.getOrCreateTag();
|
|
tag.putString(NBT_ITEM_COLOR, color.getName());
|
|
tag.putInt(NBT_CUSTOM_MODEL_DATA, color.getModelId());
|
|
}
|
|
|
|
/**
|
|
* Get color from an ItemStack.
|
|
* @return The color, or null if no color is set
|
|
*/
|
|
@Nullable
|
|
public static ItemColor getColor(ItemStack stack) {
|
|
if (stack.isEmpty()) return null;
|
|
CompoundTag tag = stack.getTag();
|
|
if (tag == null || !tag.contains(NBT_ITEM_COLOR)) return null;
|
|
return ItemColor.fromName(tag.getString(NBT_ITEM_COLOR));
|
|
}
|
|
|
|
/**
|
|
* Check if an ItemStack has a color applied.
|
|
*/
|
|
public static boolean hasColor(ItemStack stack) {
|
|
return getColor(stack) != null;
|
|
}
|
|
|
|
/**
|
|
* Get the texture suffix for an item's color.
|
|
* Example: "ropes" + "_red" = "ropes_red"
|
|
* @return The color suffix (e.g., "_red"), or empty string if no color
|
|
*/
|
|
public static String getColorSuffix(ItemStack stack) {
|
|
ItemColor color = getColor(stack);
|
|
return color != null ? "_" + color.getName() : "";
|
|
}
|
|
|
|
// COLOR VALIDATION
|
|
|
|
/**
|
|
* Get a random color that has a texture for the given bind variant.
|
|
* Excludes colors that don't have textures for specific variants.
|
|
*/
|
|
public static ItemColor getValidColorForBind(BindVariant variant) {
|
|
ItemColor color;
|
|
int attempts = 0;
|
|
do {
|
|
color = ItemColor.getRandomStandard();
|
|
attempts++;
|
|
// Prevent infinite loop
|
|
if (attempts > 50) break;
|
|
} while (!isColorValidForBind(color, variant));
|
|
return color;
|
|
}
|
|
|
|
/**
|
|
* Check if a color has a texture for the given bind variant.
|
|
* Returns false for colors without textures.
|
|
*/
|
|
public static boolean isColorValidForBind(
|
|
ItemColor color,
|
|
BindVariant variant
|
|
) {
|
|
if (color == null || variant == null) return true;
|
|
|
|
// BROWN doesn't have textures for ROPES and SHIBARI
|
|
if (
|
|
color == ItemColor.BROWN &&
|
|
(variant == BindVariant.ROPES || variant == BindVariant.SHIBARI)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// GRAY doesn't have texture for DUCT_TAPE
|
|
if (color == ItemColor.GRAY && variant == BindVariant.DUCT_TAPE) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if a color has a texture for the given gag variant.
|
|
*/
|
|
public static boolean isColorValidForGag(
|
|
ItemColor color,
|
|
GagVariant variant
|
|
) {
|
|
if (color == null || variant == null) return true;
|
|
|
|
// GRAY doesn't have texture for TAPE_GAG
|
|
if (color == ItemColor.GRAY && variant == GagVariant.TAPE_GAG) {
|
|
return false;
|
|
}
|
|
|
|
// WHITE doesn't have texture for CLOTH_GAG and CLEAVE_GAG
|
|
if (
|
|
color == ItemColor.WHITE &&
|
|
(variant == GagVariant.CLOTH_GAG ||
|
|
variant == GagVariant.CLEAVE_GAG)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// RED doesn't have texture for BALL_GAG and BALL_GAG_STRAP
|
|
if (
|
|
color == ItemColor.RED &&
|
|
(variant == GagVariant.BALL_GAG ||
|
|
variant == GagVariant.BALL_GAG_STRAP)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if a color has a texture for the given blindfold variant.
|
|
*/
|
|
public static boolean isColorValidForBlindfold(
|
|
ItemColor color,
|
|
BlindfoldVariant variant
|
|
) {
|
|
if (color == null || variant == null) return true;
|
|
|
|
// BLACK doesn't have texture for CLASSIC or MASK blindfolds
|
|
if (
|
|
color == ItemColor.BLACK &&
|
|
(variant == BlindfoldVariant.CLASSIC ||
|
|
variant == BlindfoldVariant.MASK)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get a random color that has a texture for the given gag variant.
|
|
*/
|
|
public static ItemColor getValidColorForGag(GagVariant variant) {
|
|
ItemColor color;
|
|
int attempts = 0;
|
|
do {
|
|
color = ItemColor.getRandomStandard();
|
|
attempts++;
|
|
if (attempts > 50) break;
|
|
} while (!isColorValidForGag(color, variant));
|
|
return color;
|
|
}
|
|
|
|
/**
|
|
* Get a random color that has a texture for the given blindfold variant.
|
|
*/
|
|
public static ItemColor getValidColorForBlindfold(
|
|
BlindfoldVariant variant
|
|
) {
|
|
ItemColor color;
|
|
int attempts = 0;
|
|
do {
|
|
color = ItemColor.getRandomStandard();
|
|
attempts++;
|
|
if (attempts > 50) break;
|
|
} while (!isColorValidForBlindfold(color, variant));
|
|
return color;
|
|
}
|
|
}
|