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:
NotEvil
2026-04-12 00:51:22 +02:00
parent 2e7a1d403b
commit f6466360b6
1947 changed files with 238025 additions and 1 deletions

View File

@@ -0,0 +1,842 @@
package com.tiedup.remake.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemShockCollar;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.TickResult;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.UpdateType;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.minigame.PacketContinuousStruggleState;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.PlayerBindState;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
/**
* Manages continuous struggle mini-game sessions.
*
* <p>Extracted from {@link MiniGameSessionManager} (M15 split). Handles all
* struggle variants: bind struggle, accessory struggle, V2 region struggle,
* and furniture escape struggle.
*
* <p>Singleton, thread-safe via ConcurrentHashMap.
*/
public class StruggleSessionManager {
private static final StruggleSessionManager INSTANCE =
new StruggleSessionManager();
/**
* Active continuous struggle mini-game sessions by player UUID
*/
private final Map<
UUID,
ContinuousStruggleMiniGameState
> continuousSessions = new ConcurrentHashMap<>();
/**
* Mapping from legacy V1 slot indices to V2 BodyRegionV2.
* Used to convert V1 session targetSlot ordinals to V2 regions.
* Index: 0=ARMS, 1=MOUTH, 2=EYES, 3=EARS, 4=NECK, 5=TORSO, 6=HANDS.
*/
private static final BodyRegionV2[] SLOT_TO_REGION = {
BodyRegionV2.ARMS, // 0 = BIND
BodyRegionV2.MOUTH, // 1 = GAG
BodyRegionV2.EYES, // 2 = BLINDFOLD
BodyRegionV2.EARS, // 3 = EARPLUGS
BodyRegionV2.NECK, // 4 = COLLAR
BodyRegionV2.TORSO, // 5 = CLOTHES
BodyRegionV2.HANDS, // 6 = MITTENS
};
private StruggleSessionManager() {}
public static StruggleSessionManager getInstance() {
return INSTANCE;
}
// ==================== SESSION START VARIANTS ====================
/**
* Start a new continuous struggle session for a player.
*
* @param player The server player
* @param targetResistance Current bind resistance
* @param isLocked Whether the bind is locked
* @return The new session
*/
public ContinuousStruggleMiniGameState startContinuousStruggleSession(
ServerPlayer player,
int targetResistance,
boolean isLocked
) {
UUID playerId = player.getUUID();
// Remove any existing continuous session
ContinuousStruggleMiniGameState existing = continuousSessions.get(
playerId
);
if (existing != null) {
TiedUpMod.LOGGER.debug(
"[StruggleSessionManager] Replacing existing continuous struggle session for {}",
player.getName().getString()
);
continuousSessions.remove(playerId);
}
// Create new session with configurable rate
int ticksPerResistance =
com.tiedup.remake.core.SettingsAccessor.getStruggleContinuousRate(
player.level().getGameRules()
);
ContinuousStruggleMiniGameState session =
new ContinuousStruggleMiniGameState(
playerId,
targetResistance,
isLocked,
null,
ticksPerResistance
);
continuousSessions.put(playerId, session);
TiedUpMod.LOGGER.info(
"[StruggleSessionManager] Started continuous struggle session {} for {} (resistance: {}, locked: {})",
session.getSessionId().toString().substring(0, 8),
player.getName().getString(),
targetResistance,
isLocked
);
// Set struggle animation state and sync to client
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.setStruggling(true, player.level().getGameTime());
SyncManager.syncStruggleState(player);
}
// Notify nearby kidnappers
GuardNotificationHelper.notifyNearbyKidnappersOfStruggle(player);
return session;
}
/**
* Start a new continuous struggle session for an accessory.
*
* @param player The server player
* @param targetSlot Target accessory slot ordinal
* @param lockResistance Current lock resistance
* @return The new session
*/
public ContinuousStruggleMiniGameState startContinuousAccessoryStruggleSession(
ServerPlayer player,
int targetSlot,
int lockResistance
) {
UUID playerId = player.getUUID();
// Remove any existing session
ContinuousStruggleMiniGameState existing = continuousSessions.get(
playerId
);
if (existing != null) {
continuousSessions.remove(playerId);
}
// Create new session with target slot and configurable rate
int ticksPerResistance =
com.tiedup.remake.core.SettingsAccessor.getStruggleContinuousRate(
player.level().getGameRules()
);
ContinuousStruggleMiniGameState session =
new ContinuousStruggleMiniGameState(
playerId,
lockResistance,
true,
targetSlot,
ticksPerResistance
);
continuousSessions.put(playerId, session);
TiedUpMod.LOGGER.info(
"[StruggleSessionManager] Started continuous accessory struggle session {} for {} (slot: {}, resistance: {})",
session.getSessionId().toString().substring(0, 8),
player.getName().getString(),
targetSlot,
lockResistance
);
// Set struggle animation state and sync to client
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.setStruggling(true, player.level().getGameTime());
SyncManager.syncStruggleState(player);
}
// Notify nearby kidnappers
GuardNotificationHelper.notifyNearbyKidnappersOfStruggle(player);
return session;
}
/**
* Start a new continuous struggle session for a V2 bondage item.
*
* @param player The server player
* @param targetRegion V2 body region to struggle against
* @param targetResistance Current resistance value
* @param isLocked Whether the item is locked
* @return The new session, or null if creation failed
*/
public ContinuousStruggleMiniGameState startV2StruggleSession(
ServerPlayer player,
com.tiedup.remake.v2.BodyRegionV2 targetRegion,
int targetResistance,
boolean isLocked
) {
UUID playerId = player.getUUID();
// RISK-001 fix: reject if an active session already exists (prevents direction re-roll exploit)
ContinuousStruggleMiniGameState existing = continuousSessions.get(
playerId
);
if (existing != null) {
TiedUpMod.LOGGER.debug(
"[StruggleSessionManager] Rejected V2 session: active session already exists for {}",
player.getName().getString()
);
return null;
}
int ticksPerResistance =
com.tiedup.remake.core.SettingsAccessor.getStruggleContinuousRate(
player.level().getGameRules()
);
ContinuousStruggleMiniGameState session =
new ContinuousStruggleMiniGameState(
playerId,
targetResistance,
isLocked,
null,
ticksPerResistance
);
session.setTargetRegion(targetRegion);
continuousSessions.put(playerId, session);
TiedUpMod.LOGGER.info(
"[StruggleSessionManager] Started V2 struggle session {} for {} (region: {}, resistance: {}, locked: {})",
session.getSessionId().toString().substring(0, 8),
player.getName().getString(),
targetRegion.name(),
targetResistance,
isLocked
);
// Set struggle animation state and sync to client
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.setStruggling(true, player.level().getGameTime());
SyncManager.syncStruggleState(player);
}
GuardNotificationHelper.notifyNearbyKidnappersOfStruggle(player);
return session;
}
/**
* Start a new continuous struggle session for a furniture seat escape.
*
* <p>The session behaves identically to a V2 struggle (direction-hold to
* reduce resistance) but on completion it unlocks the seat and dismounts
* the player instead of removing a bondage item.</p>
*
* @param player The server player (must be seated and locked)
* @param furnitureEntityId Entity ID of the furniture
* @param seatId The locked seat ID
* @param totalDifficulty Combined resistance (seat base + item bonus)
* @return The new session, or null if creation failed
*/
public ContinuousStruggleMiniGameState startFurnitureStruggleSession(
ServerPlayer player,
int furnitureEntityId,
String seatId,
int totalDifficulty
) {
UUID playerId = player.getUUID();
// Reject if an active session already exists
ContinuousStruggleMiniGameState existing = continuousSessions.get(playerId);
if (existing != null) {
TiedUpMod.LOGGER.debug(
"[StruggleSessionManager] Rejected furniture session: active session already exists for {}",
player.getName().getString()
);
return null;
}
int ticksPerResistance =
com.tiedup.remake.core.SettingsAccessor.getStruggleContinuousRate(
player.level().getGameRules()
);
ContinuousStruggleMiniGameState session =
new ContinuousStruggleMiniGameState(
playerId,
totalDifficulty,
true, // furniture seats are always "locked" in context
null,
ticksPerResistance
);
session.setFurnitureContext(furnitureEntityId, seatId);
continuousSessions.put(playerId, session);
TiedUpMod.LOGGER.info(
"[StruggleSessionManager] Started furniture struggle session {} for {} (entity: {}, seat: '{}', difficulty: {})",
session.getSessionId().toString().substring(0, 8),
player.getName().getString(),
furnitureEntityId,
seatId,
totalDifficulty
);
// Set struggle animation state and sync to client
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.setStruggling(true, player.level().getGameTime());
SyncManager.syncStruggleState(player);
}
// Play struggle loop sound from furniture definition (plays once on start;
// true looping sound would require client-side sound management -- future scope)
net.minecraft.world.entity.Entity furnitureEntity = player.level().getEntity(furnitureEntityId);
if (furnitureEntity instanceof com.tiedup.remake.v2.furniture.EntityFurniture furniture) {
com.tiedup.remake.v2.furniture.FurnitureDefinition def = furniture.getDefinition();
if (def != null && def.feedback().struggleLoopSound() != null) {
player.level().playSound(null, player.getX(), player.getY(), player.getZ(),
net.minecraft.sounds.SoundEvent.createVariableRangeEvent(def.feedback().struggleLoopSound()),
SoundSource.PLAYERS, 0.6f, 1.0f);
}
}
GuardNotificationHelper.notifyNearbyKidnappersOfStruggle(player);
return session;
}
// ==================== SESSION QUERY ====================
/**
* Get active continuous struggle session for a player.
*
* @param playerId The player UUID
* @return The session, or null if none active
*/
@Nullable
public ContinuousStruggleMiniGameState getContinuousStruggleSession(
UUID playerId
) {
ContinuousStruggleMiniGameState session = continuousSessions.get(
playerId
);
if (session != null && session.isExpired()) {
continuousSessions.remove(playerId);
return null;
}
return session;
}
/**
* Validate a continuous struggle session.
*
* @param playerId The player UUID
* @param sessionId The session UUID to validate
* @return true if session is valid and active
*/
public boolean validateContinuousStruggleSession(
UUID playerId,
UUID sessionId
) {
ContinuousStruggleMiniGameState session = getContinuousStruggleSession(
playerId
);
if (session == null) {
return false;
}
return session.getSessionId().equals(sessionId);
}
/**
* End a continuous struggle session.
*
* @param playerId The player UUID
* @param success Whether the session ended in success (escape)
*/
public void endContinuousStruggleSession(UUID playerId, boolean success) {
ContinuousStruggleMiniGameState session = continuousSessions.remove(
playerId
);
if (session != null) {
TiedUpMod.LOGGER.info(
"[StruggleSessionManager] Ended continuous struggle session for player {} (success: {})",
playerId.toString().substring(0, 8),
success
);
// Clear struggle animation state
// We need to find the player to clear the animation
// This will be handled by the caller if they have access to the player
}
}
// ==================== TICK ====================
/**
* Tick all continuous struggle sessions.
* Should be called every server tick.
*
* @param server The server instance for player lookups
* @param currentTick Current game tick
*/
public void tickContinuousSessions(
net.minecraft.server.MinecraftServer server,
long currentTick
) {
if (continuousSessions.isEmpty()) return;
// Collect sessions to process (avoid ConcurrentModificationException)
List<Map.Entry<UUID, ContinuousStruggleMiniGameState>> toProcess =
new ArrayList<>(continuousSessions.entrySet());
for (Map.Entry<
UUID,
ContinuousStruggleMiniGameState
> entry : toProcess) {
UUID playerId = entry.getKey();
ContinuousStruggleMiniGameState session = entry.getValue();
// Get player
ServerPlayer player = server.getPlayerList().getPlayer(playerId);
if (player == null) {
// Player disconnected, clean up
continuousSessions.remove(playerId);
continue;
}
// Check for expired/completed sessions
if (session.isExpired() || session.isComplete()) {
continuousSessions.remove(playerId);
clearStruggleAnimation(player);
continue;
}
// Tick the session
TickResult result = session.tick(currentTick);
// Handle tick result
switch (result) {
case DIRECTION_CHANGE -> {
sendContinuousStruggleUpdate(
player,
session,
UpdateType.DIRECTION_CHANGE
);
}
case RESISTANCE_UPDATE -> {
// Update actual bind resistance
updateBindResistance(player, session);
// Send update to client immediately for real-time feedback
sendContinuousStruggleUpdate(
player,
session,
UpdateType.RESISTANCE_UPDATE
);
}
case ESCAPED -> {
handleStruggleEscape(player, session);
}
case NO_CHANGE -> {
// No action needed
}
default -> {
}
}
// Check shock collar (separate from tick result)
if (
shouldCheckShockCollar(player) &&
session.shouldTriggerShock(currentTick)
) {
handleShockCollar(player, session);
}
// Check kidnapper notification
if (session.shouldNotifyKidnappers(currentTick)) {
GuardNotificationHelper.notifyNearbyKidnappersOfStruggle(player);
}
// Play struggle sound periodically
if (session.shouldPlayStruggleSound(currentTick)) {
playStruggleSound(player);
}
}
}
// ==================== PRIVATE HELPERS ====================
/**
* Play struggle sound for player and nearby entities.
*/
private void playStruggleSound(ServerPlayer player) {
// Play leather creaking sound - audible to player and nearby
player
.serverLevel()
.playSound(
null, // null = all players can hear
player.getX(),
player.getY(),
player.getZ(),
SoundEvents.ARMOR_EQUIP_LEATHER,
SoundSource.PLAYERS,
0.8f, // volume
0.9f + player.getRandom().nextFloat() * 0.2f // slight pitch variation
);
}
/**
* Check if shock collar check is applicable for this player.
*/
private boolean shouldCheckShockCollar(ServerPlayer player) {
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
if (collar.isEmpty()) return false;
// Only shock collars can trigger during struggle
if (!(collar.getItem() instanceof ItemShockCollar)) return false;
// Must be locked
if (collar.getItem() instanceof ItemCollar collarItem) {
return collarItem.isLocked(collar);
}
return false;
}
/**
* Handle shock collar trigger.
*/
private void handleShockCollar(
ServerPlayer player,
ContinuousStruggleMiniGameState session
) {
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.shockKidnapped(" (Your struggle was interrupted!)", 2.0f);
}
session.triggerShock();
sendContinuousStruggleUpdate(player, session, UpdateType.SHOCK);
TiedUpMod.LOGGER.debug(
"[StruggleSessionManager] Shock collar triggered for {} during struggle",
player.getName().getString()
);
}
/**
* Update the actual bind resistance based on session state.
*/
private void updateBindResistance(
ServerPlayer player,
ContinuousStruggleMiniGameState session
) {
// V2 region-based resistance update
if (session.isV2Struggle()) {
com.tiedup.remake.v2.BodyRegionV2 region =
session.getTargetRegion();
ItemStack stack =
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper
.getInRegion(player, region);
if (stack.isEmpty()) return;
if (
stack.getItem() instanceof
com.tiedup.remake.items.base.IHasResistance resistanceItem
) {
resistanceItem.setCurrentResistance(
stack,
session.getCurrentResistance()
);
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(
player
);
}
return;
}
if (session.isAccessoryStruggle()) {
// Handle accessory resistance update (V1 path -- slot index is legacy ordinal)
Integer slotIndex = session.getTargetSlot();
if (slotIndex == null || slotIndex < 0 || slotIndex >= SLOT_TO_REGION.length) return;
BodyRegionV2 region = SLOT_TO_REGION[slotIndex];
ItemStack accessoryStack = V2EquipmentHelper.getInRegion(player, region);
if (accessoryStack.isEmpty()) return;
if (
accessoryStack.getItem() instanceof
com.tiedup.remake.items.base.ILockable lockable
) {
// Update the lock resistance to match session state
lockable.setCurrentLockResistance(
accessoryStack,
session.getCurrentResistance()
);
// Sync V2 equipment state
V2EquipmentHelper.sync(player);
}
return;
}
// Update bind resistance
ItemStack bindStack = V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
if (
bindStack.isEmpty() ||
!(bindStack.getItem() instanceof ItemBind bind)
) {
return;
}
bind.setCurrentResistance(bindStack, session.getCurrentResistance());
}
/**
* Handle successful escape from struggle.
*/
private void handleStruggleEscape(
ServerPlayer player,
ContinuousStruggleMiniGameState session
) {
TiedUpMod.LOGGER.info(
"[StruggleSessionManager] Player {} escaped from struggle!",
player.getName().getString()
);
// Send escape update to client
sendContinuousStruggleUpdate(player, session, UpdateType.ESCAPE);
// Clear animation state
clearStruggleAnimation(player);
// Furniture escape: unlock seat + dismount
if (session.isFurnitureStruggle()) {
handleFurnitureEscape(player, session);
continuousSessions.remove(player.getUUID());
return;
}
// V2 region-based escape
if (session.isV2Struggle()) {
com.tiedup.remake.v2.BodyRegionV2 region =
session.getTargetRegion();
// BUG-001 fix: break lock before unequip (consistent with V1 accessory escape)
ItemStack stackInRegion =
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper
.getInRegion(player, region);
if (!stackInRegion.isEmpty()
&& stackInRegion.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
lockable.breakLock(stackInRegion);
}
ItemStack removed =
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper
.unequipFromRegion(player, region, true);
if (!removed.isEmpty()) {
if (!player.getInventory().add(removed)) {
player.drop(removed, false);
}
}
continuousSessions.remove(player.getUUID());
return;
}
// Remove the bind
if (!session.isAccessoryStruggle()) {
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
ItemStack bind = state.unequip(BodyRegionV2.ARMS);
if (!bind.isEmpty()) {
state.kidnappedDropItem(bind);
}
}
} else {
// Handle accessory escape (V1 path -- slot index is legacy ordinal)
Integer slotIndex = session.getTargetSlot();
if (slotIndex != null && slotIndex >= 0 && slotIndex < SLOT_TO_REGION.length) {
BodyRegionV2 region = SLOT_TO_REGION[slotIndex];
ItemStack accessoryStack = V2EquipmentHelper.getInRegion(player, region);
if (!accessoryStack.isEmpty()) {
// Break the lock on the accessory
if (
accessoryStack.getItem() instanceof
com.tiedup.remake.items.base.ILockable lockable
) {
lockable.breakLock(accessoryStack);
}
// Remove the accessory from the region and drop it
ItemStack removed = V2EquipmentHelper.unequipFromRegion(
player, region, true
);
if (!removed.isEmpty()) {
// Drop the item at player's feet
player.drop(removed, true);
}
}
}
}
// Remove session
continuousSessions.remove(player.getUUID());
}
/**
* Handle successful furniture escape: unlock the seat, dismount the player,
* play sounds, and broadcast state to tracking clients.
*/
private void handleFurnitureEscape(
ServerPlayer player,
ContinuousStruggleMiniGameState session
) {
int furnitureEntityId = session.getFurnitureEntityId();
String seatId = session.getFurnitureSeatId();
net.minecraft.world.entity.Entity entity = player.level().getEntity(furnitureEntityId);
if (entity == null || entity.isRemoved()) {
TiedUpMod.LOGGER.warn(
"[StruggleSessionManager] Furniture entity {} no longer exists for escape",
furnitureEntityId
);
// Player still needs to be freed even if entity is gone
player.stopRiding();
return;
}
if (!(entity instanceof com.tiedup.remake.v2.furniture.ISeatProvider provider)) {
TiedUpMod.LOGGER.warn(
"[StruggleSessionManager] Entity {} is not an ISeatProvider",
furnitureEntityId
);
player.stopRiding();
return;
}
// Unlock the seat
provider.setSeatLocked(seatId, false);
// Clear persistent data tag (reconnection system)
net.minecraft.nbt.CompoundTag persistentData = player.getPersistentData();
persistentData.remove("tiedup_locked_furniture");
// Dismount the player
player.stopRiding();
// Play escape sound: prefer furniture-specific sound, fall back to CHAIN_BREAK
net.minecraft.sounds.SoundEvent escapeSound = net.minecraft.sounds.SoundEvents.CHAIN_BREAK;
if (entity instanceof com.tiedup.remake.v2.furniture.EntityFurniture furniture) {
com.tiedup.remake.v2.furniture.FurnitureDefinition def = furniture.getDefinition();
if (def != null && def.feedback().escapeSound() != null) {
escapeSound = net.minecraft.sounds.SoundEvent.createVariableRangeEvent(
def.feedback().escapeSound()
);
}
}
player.serverLevel().playSound(
null,
player.getX(), player.getY(), player.getZ(),
escapeSound,
net.minecraft.sounds.SoundSource.PLAYERS,
1.0f, 1.0f
);
// Broadcast updated furniture state to all tracking clients
if (entity instanceof com.tiedup.remake.v2.furniture.EntityFurniture furniture) {
com.tiedup.remake.v2.furniture.network.PacketSyncFurnitureState.sendToTracking(furniture);
}
TiedUpMod.LOGGER.info(
"[StruggleSessionManager] {} escaped furniture {} seat '{}'",
player.getName().getString(), furnitureEntityId, seatId
);
}
/**
* Send a continuous struggle state update to the client.
*/
private void sendContinuousStruggleUpdate(
ServerPlayer player,
ContinuousStruggleMiniGameState session,
UpdateType updateType
) {
ModNetwork.sendToPlayer(
new PacketContinuousStruggleState(
session.getSessionId(),
updateType,
session.getCurrentDirectionIndex(),
session.getCurrentResistance(),
session.getMaxResistance(),
session.isLocked()
),
player
);
}
/**
* Clear struggle animation state for a player and sync to clients.
*/
private void clearStruggleAnimation(ServerPlayer player) {
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.setStruggling(false, 0);
SyncManager.syncStruggleState(player);
}
}
// ==================== CLEANUP ====================
/**
* Clean up all struggle sessions for a player (called on disconnect).
*
* @param playerId The player UUID
*/
public void cleanupPlayer(UUID playerId) {
continuousSessions.remove(playerId);
}
/**
* Periodic cleanup of expired struggle sessions.
* Should be called from server tick handler.
*/
public void tickCleanup(long currentTick) {
// Only run every 100 ticks (5 seconds)
if (currentTick % 100 != 0) {
return;
}
continuousSessions
.entrySet()
.removeIf(entry -> entry.getValue().isExpired());
}
/**
* Get count of active struggle sessions (for debugging).
*/
public int getActiveSessionCount() {
return continuousSessions.size();
}
}