Strip all Phase references, TODO/FUTURE roadmap notes, and internal planning comments from the codebase. Run Prettier for consistent formatting across all Java files.
442 lines
16 KiB
Java
442 lines
16 KiB
Java
package com.tiedup.remake.util;
|
|
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import com.tiedup.remake.state.IPlayerLeashAccess;
|
|
import java.util.UUID;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.world.effect.MobEffectInstance;
|
|
import net.minecraft.world.effect.MobEffects;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.Mob;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.FenceBlock;
|
|
import net.minecraft.world.level.block.WallBlock;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
/**
|
|
*
|
|
* Manages attribute modifiers for movement speed reduction and other effects.
|
|
*/
|
|
public class RestraintEffectUtils {
|
|
|
|
// UUID for the movement speed modifier (must be consistent)
|
|
private static final UUID BIND_SPEED_MODIFIER_UUID = UUID.fromString(
|
|
"7f3c7c8e-9d4e-4c7a-8e5f-1a2b3c4d5e6f"
|
|
);
|
|
private static final String BIND_SPEED_MODIFIER_NAME = "tiedup.bind_speed";
|
|
|
|
// Speed reduction: -0.09 (90% reduction) when tied up
|
|
// Player base speed is 0.10, so this reduces them to 0.01 (10% speed)
|
|
private static final double BIND_SPEED_REDUCTION = -0.09;
|
|
|
|
// Full immobilization: -0.10 (100% reduction) for WRAP/LATEX_SACK
|
|
// Player can only move by jumping
|
|
private static final double FULL_IMMOBILIZATION_REDUCTION = -0.10;
|
|
|
|
private static final boolean DEBUG = false;
|
|
|
|
/**
|
|
* Apply movement speed reduction to a tied entity.
|
|
*
|
|
* @param entity The living entity to apply the effect to
|
|
* @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager}
|
|
* which handles speed via tick-based resolution. This method remains for NPC entities
|
|
* (Damsel, MCA villagers) that are not managed by MovementStyleManager.
|
|
* Scheduled for removal once NPC movement is migrated to V2.
|
|
*/
|
|
@Deprecated(forRemoval = true)
|
|
public static void applyBindSpeedReduction(LivingEntity entity) {
|
|
applyBindSpeedReduction(entity, false);
|
|
}
|
|
|
|
/**
|
|
* Apply movement speed reduction to a tied entity.
|
|
*
|
|
* @param entity The living entity to apply the effect to
|
|
* @param fullImmobilization If true, applies 100% speed reduction (for WRAP/LATEX_SACK)
|
|
* @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager}
|
|
* which handles speed via tick-based resolution. This method remains for NPC entities
|
|
* (Damsel, MCA villagers) that are not managed by MovementStyleManager.
|
|
* Scheduled for removal once NPC movement is migrated to V2.
|
|
*/
|
|
@Deprecated(forRemoval = true)
|
|
public static void applyBindSpeedReduction(
|
|
LivingEntity entity,
|
|
boolean fullImmobilization
|
|
) {
|
|
if (entity == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[RESTRAINT-UTIL] Cannot apply speed reduction - entity is null"
|
|
);
|
|
return;
|
|
}
|
|
|
|
AttributeInstance movementSpeed = entity.getAttribute(
|
|
Attributes.MOVEMENT_SPEED
|
|
);
|
|
if (movementSpeed == null) {
|
|
TiedUpMod.LOGGER.error(
|
|
"[RESTRAINT-UTIL] Entity {} has no MOVEMENT_SPEED attribute!",
|
|
entity.getName().getString()
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Remove existing modifier if present (to avoid duplicates)
|
|
removeBindSpeedReduction(entity);
|
|
|
|
// Choose reduction amount based on immobilization type
|
|
double reduction = fullImmobilization
|
|
? FULL_IMMOBILIZATION_REDUCTION
|
|
: BIND_SPEED_REDUCTION;
|
|
|
|
// Create and apply new modifier
|
|
AttributeModifier modifier = new AttributeModifier(
|
|
BIND_SPEED_MODIFIER_UUID,
|
|
BIND_SPEED_MODIFIER_NAME,
|
|
reduction,
|
|
AttributeModifier.Operation.ADDITION
|
|
);
|
|
|
|
movementSpeed.addPermanentModifier(modifier);
|
|
|
|
if (DEBUG) {
|
|
TiedUpMod.LOGGER.info(
|
|
"[RESTRAINT-UTIL] Applied speed reduction to {} (base: {}, modified: {}, full: {})",
|
|
entity.getName().getString(),
|
|
movementSpeed.getBaseValue(),
|
|
movementSpeed.getValue(),
|
|
fullImmobilization
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove movement speed reduction from an entity.
|
|
*
|
|
* @param entity The living entity to remove the effect from
|
|
* @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager}
|
|
* which handles speed cleanup via tick-based resolution. This method remains for
|
|
* NPC entities (Damsel, MCA villagers) that are not managed by MovementStyleManager.
|
|
* Scheduled for removal once NPC movement is migrated to V2.
|
|
*/
|
|
@Deprecated(forRemoval = true)
|
|
public static void removeBindSpeedReduction(LivingEntity entity) {
|
|
if (entity == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[RESTRAINT-UTIL] Cannot remove speed reduction - entity is null"
|
|
);
|
|
return;
|
|
}
|
|
|
|
AttributeInstance movementSpeed = entity.getAttribute(
|
|
Attributes.MOVEMENT_SPEED
|
|
);
|
|
if (movementSpeed == null) {
|
|
TiedUpMod.LOGGER.error(
|
|
"[RESTRAINT-UTIL] Entity {} has no MOVEMENT_SPEED attribute!",
|
|
entity.getName().getString()
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Remove modifier if present
|
|
if (movementSpeed.getModifier(BIND_SPEED_MODIFIER_UUID) != null) {
|
|
movementSpeed.removeModifier(BIND_SPEED_MODIFIER_UUID);
|
|
|
|
if (DEBUG) {
|
|
TiedUpMod.LOGGER.info(
|
|
"[RESTRAINT-UTIL] Removed speed reduction from {} (restored speed: {})",
|
|
entity.getName().getString(),
|
|
movementSpeed.getValue()
|
|
);
|
|
}
|
|
} else {
|
|
if (DEBUG) {
|
|
TiedUpMod.LOGGER.debug(
|
|
"[RESTRAINT-UTIL] No speed modifier found on {} (already removed or never applied)",
|
|
entity.getName().getString()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if an entity currently has the bind speed reduction applied.
|
|
*
|
|
* @param entity The living entity to check
|
|
* @return true if the modifier is active
|
|
* @deprecated For players, movement style is tracked by {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager}.
|
|
* Scheduled for removal once NPC movement is migrated to V2.
|
|
*/
|
|
@Deprecated(forRemoval = true)
|
|
public static boolean hasBindSpeedReduction(LivingEntity entity) {
|
|
if (entity == null) return false;
|
|
|
|
AttributeInstance movementSpeed = entity.getAttribute(
|
|
Attributes.MOVEMENT_SPEED
|
|
);
|
|
if (movementSpeed == null) return false;
|
|
|
|
return movementSpeed.getModifier(BIND_SPEED_MODIFIER_UUID) != null;
|
|
}
|
|
|
|
/**
|
|
* Re-apply speed reduction if needed (called on login/respawn).
|
|
*
|
|
* @param entity The living entity
|
|
* @param shouldBeSlowed Whether the entity should have reduced speed
|
|
* @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager}
|
|
* which re-resolves on every tick. Scheduled for removal once NPC movement is migrated to V2.
|
|
*/
|
|
@Deprecated(forRemoval = true)
|
|
public static void updateBindSpeedReduction(
|
|
LivingEntity entity,
|
|
boolean shouldBeSlowed
|
|
) {
|
|
updateBindSpeedReduction(entity, shouldBeSlowed, false);
|
|
}
|
|
|
|
/**
|
|
* Re-apply speed reduction if needed (called on login/respawn).
|
|
*
|
|
* @param entity The living entity
|
|
* @param shouldBeSlowed Whether the entity should have reduced speed
|
|
* @param fullImmobilization If true, applies 100% speed reduction (for WRAP/LATEX_SACK)
|
|
* @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager}
|
|
* which re-resolves on every tick. Scheduled for removal once NPC movement is migrated to V2.
|
|
*/
|
|
@Deprecated(forRemoval = true)
|
|
public static void updateBindSpeedReduction(
|
|
LivingEntity entity,
|
|
boolean shouldBeSlowed,
|
|
boolean fullImmobilization
|
|
) {
|
|
boolean currentlySlowed = hasBindSpeedReduction(entity);
|
|
|
|
if (shouldBeSlowed && !currentlySlowed) {
|
|
// Need to apply
|
|
applyBindSpeedReduction(entity, fullImmobilization);
|
|
if (DEBUG) {
|
|
TiedUpMod.LOGGER.info(
|
|
"[RESTRAINT-UTIL] Re-applied speed reduction to {} on login/respawn (full={})",
|
|
entity.getName().getString(),
|
|
fullImmobilization
|
|
);
|
|
}
|
|
} else if (shouldBeSlowed && currentlySlowed) {
|
|
// Already slowed - but might need to change immobilization level
|
|
// Re-apply with correct level
|
|
applyBindSpeedReduction(entity, fullImmobilization);
|
|
} else if (!shouldBeSlowed && currentlySlowed) {
|
|
// Need to remove
|
|
removeBindSpeedReduction(entity);
|
|
if (DEBUG) {
|
|
TiedUpMod.LOGGER.info(
|
|
"[RESTRAINT-UTIL] Removed speed reduction from {} on login/respawn",
|
|
entity.getName().getString()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// POLE BINDING UTILITIES
|
|
|
|
/**
|
|
* Find the closest fence or wall block within a radius.
|
|
*
|
|
* @param level The level to search in
|
|
* @param center The center position to search from
|
|
* @param radius The search radius in blocks
|
|
* @return The position of the closest fence/wall, or null if none found
|
|
*/
|
|
@Nullable
|
|
public static BlockPos findClosestFence(
|
|
Level level,
|
|
BlockPos center,
|
|
int radius
|
|
) {
|
|
if (level == null || center == null) return null;
|
|
|
|
BlockPos closestFence = null;
|
|
double closestDistance = Double.MAX_VALUE;
|
|
|
|
for (int x = -radius; x <= radius; x++) {
|
|
for (int y = -radius; y <= radius; y++) {
|
|
for (int z = -radius; z <= radius; z++) {
|
|
BlockPos checkPos = center.offset(x, y, z);
|
|
BlockState state = level.getBlockState(checkPos);
|
|
|
|
if (
|
|
state.getBlock() instanceof FenceBlock ||
|
|
state.getBlock() instanceof WallBlock
|
|
) {
|
|
double dist = center.distSqr(checkPos);
|
|
if (dist < closestDistance) {
|
|
closestDistance = dist;
|
|
closestFence = checkPos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return closestFence;
|
|
}
|
|
|
|
/**
|
|
* Tie an entity to the closest fence or wall block.
|
|
* Works for both Players (via LeashProxy) and NPCs (via vanilla leash).
|
|
*
|
|
* @param entity The entity to tie
|
|
* @param searchRadius The search radius for fence blocks
|
|
* @return true if successfully tied, false otherwise
|
|
*/
|
|
public static boolean tieToClosestPole(
|
|
LivingEntity entity,
|
|
int searchRadius
|
|
) {
|
|
if (entity == null || entity.level().isClientSide) return false;
|
|
if (!(entity.level() instanceof ServerLevel serverLevel)) return false;
|
|
|
|
BlockPos entityPos = entity.blockPosition();
|
|
BlockPos closestFence = findClosestFence(
|
|
serverLevel,
|
|
entityPos,
|
|
searchRadius
|
|
);
|
|
|
|
if (closestFence == null) {
|
|
if (DEBUG) {
|
|
TiedUpMod.LOGGER.debug(
|
|
"[RESTRAINT-UTIL] No fence found within {} blocks of {}",
|
|
searchRadius,
|
|
entity.getName().getString()
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Get or create a LeashFenceKnotEntity at the fence position
|
|
LeashFenceKnotEntity fenceKnot = LeashFenceKnotEntity.getOrCreateKnot(
|
|
serverLevel,
|
|
closestFence
|
|
);
|
|
|
|
if (fenceKnot == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[RESTRAINT-UTIL] Failed to create fence knot at {}",
|
|
closestFence
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// Handle differently based on entity type
|
|
if (entity instanceof Player player) {
|
|
// Player: use LeashProxy system
|
|
if (player instanceof IPlayerLeashAccess access) {
|
|
access.tiedup$attachLeash(fenceKnot);
|
|
TiedUpMod.LOGGER.debug(
|
|
"[RESTRAINT-UTIL] Tied player {} to pole at {}",
|
|
player.getName().getString(),
|
|
closestFence
|
|
);
|
|
return true;
|
|
} else {
|
|
TiedUpMod.LOGGER.error(
|
|
"[RESTRAINT-UTIL] Player {} does not implement IPlayerLeashAccess!",
|
|
player.getName().getString()
|
|
);
|
|
return false;
|
|
}
|
|
} else if (entity instanceof Mob mob) {
|
|
// NPC (Mob): use vanilla leash mechanics
|
|
mob.setLeashedTo(fenceKnot, true);
|
|
TiedUpMod.LOGGER.debug(
|
|
"[RESTRAINT-UTIL] Tied mob {} to pole at {}",
|
|
mob.getName().getString(),
|
|
closestFence
|
|
);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// CHLOROFORM UTILITIES
|
|
|
|
/**
|
|
* Apply chloroform effects to an entity.
|
|
* Effects: Slowness, Mining Fatigue, Blindness, Jump Boost (all at max amplifier).
|
|
*
|
|
* @param entity The entity to affect
|
|
* @param durationSeconds Duration in seconds
|
|
*/
|
|
public static void applyChloroformEffects(
|
|
LivingEntity entity,
|
|
int durationSeconds
|
|
) {
|
|
if (entity == null || entity.level().isClientSide) return;
|
|
|
|
int tickDuration = durationSeconds * GameConstants.TICKS_PER_SECOND;
|
|
|
|
entity.addEffect(
|
|
new MobEffectInstance(
|
|
MobEffects.MOVEMENT_SLOWDOWN,
|
|
tickDuration,
|
|
GameConstants.CHLOROFORM_SLOWDOWN_AMPLIFIER,
|
|
false,
|
|
false
|
|
)
|
|
);
|
|
entity.addEffect(
|
|
new MobEffectInstance(
|
|
MobEffects.DIG_SLOWDOWN,
|
|
tickDuration,
|
|
GameConstants.CHLOROFORM_DIG_SLOWDOWN_AMPLIFIER,
|
|
false,
|
|
false
|
|
)
|
|
);
|
|
entity.addEffect(
|
|
new MobEffectInstance(
|
|
MobEffects.BLINDNESS,
|
|
tickDuration,
|
|
GameConstants.CHLOROFORM_BLINDNESS_AMPLIFIER,
|
|
false,
|
|
false
|
|
)
|
|
);
|
|
entity.addEffect(
|
|
new MobEffectInstance(
|
|
MobEffects.JUMP,
|
|
tickDuration,
|
|
GameConstants.CHLOROFORM_JUMP_AMPLIFIER,
|
|
false,
|
|
false
|
|
)
|
|
);
|
|
|
|
// Stop navigation for mobs
|
|
if (entity instanceof Mob mob) {
|
|
mob.getNavigation().stop();
|
|
}
|
|
|
|
if (DEBUG) {
|
|
TiedUpMod.LOGGER.debug(
|
|
"[RESTRAINT-UTIL] Applied chloroform to {} for {} seconds",
|
|
entity.getName().getString(),
|
|
durationSeconds
|
|
);
|
|
}
|
|
}
|
|
}
|