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,109 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState;
import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.network.PacketRateLimiter;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
/**
* Client to Server packet for continuous struggle key hold state.
*
* Sent every 5 ticks (4 times per second) while the struggle screen is open.
* Reports which direction key the player is holding (or -1 if none).
*/
public class PacketContinuousStruggleHold {
private final UUID sessionId;
private final int heldDirection; // -1 if not holding any direction key
private final boolean isHolding;
public PacketContinuousStruggleHold(
UUID sessionId,
int heldDirection,
boolean isHolding
) {
this.sessionId = sessionId;
this.heldDirection = heldDirection;
this.isHolding = isHolding;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(sessionId);
buf.writeVarInt(heldDirection);
buf.writeBoolean(isHolding);
}
public static PacketContinuousStruggleHold decode(FriendlyByteBuf buf) {
UUID sessionId = buf.readUUID();
int heldDirection = buf.readVarInt();
boolean isHolding = buf.readBoolean();
return new PacketContinuousStruggleHold(
sessionId,
heldDirection,
isHolding
);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
ServerPlayer player = ctx.get().getSender();
if (player == null) {
return;
}
handleServer(player);
});
ctx.get().setPacketHandled(true);
}
private void handleServer(ServerPlayer player) {
// HIGH FIX #18: Rate limiting to prevent packet spam exploit
// Designed for 4/sec, allow up to 12/sec with buffer
if (!PacketRateLimiter.allowPacket(player, "minigame")) {
return;
}
StruggleSessionManager manager = StruggleSessionManager.getInstance();
ContinuousStruggleMiniGameState session =
manager.getContinuousStruggleSession(player.getUUID());
if (session == null) {
TiedUpMod.LOGGER.debug(
"[PacketContinuousStruggleHold] No active session for {}",
player.getName().getString()
);
return;
}
// Validate session ID
if (!session.getSessionId().equals(sessionId)) {
TiedUpMod.LOGGER.debug(
"[PacketContinuousStruggleHold] Session ID mismatch for {}",
player.getName().getString()
);
return;
}
// Update held direction
session.updateHeldDirection(heldDirection, isHolding);
}
// Getters
public UUID getSessionId() {
return sessionId;
}
public int getHeldDirection() {
return heldDirection;
}
public boolean isHolding() {
return isHolding;
}
}

View File

@@ -0,0 +1,177 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.UpdateType;
import com.tiedup.remake.network.base.AbstractClientPacket;
import java.util.UUID;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
/**
* Server to Client packet for continuous struggle mini-game state updates.
*
* Sent when:
* - START: Session begins (opens the GUI)
* - DIRECTION_CHANGE: Direction changed
* - RESISTANCE_UPDATE: Resistance reduced
* - SHOCK: Shock collar triggered
* - ESCAPE: Player escaped successfully
* - END: Session ended (cancelled or completed)
*/
public class PacketContinuousStruggleState extends AbstractClientPacket {
private final UUID sessionId;
private final UpdateType updateType;
private final int currentDirection;
private final int currentResistance;
private final int maxResistance;
private final boolean isLocked;
public PacketContinuousStruggleState(
UUID sessionId,
UpdateType updateType,
int currentDirection,
int currentResistance,
int maxResistance,
boolean isLocked
) {
this.sessionId = sessionId;
this.updateType = updateType;
this.currentDirection = currentDirection;
this.currentResistance = currentResistance;
this.maxResistance = maxResistance;
this.isLocked = isLocked;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(sessionId);
buf.writeVarInt(updateType.ordinal());
buf.writeVarInt(currentDirection);
buf.writeVarInt(currentResistance);
buf.writeVarInt(maxResistance);
buf.writeBoolean(isLocked);
}
public static PacketContinuousStruggleState decode(FriendlyByteBuf buf) {
UUID sessionId = buf.readUUID();
UpdateType updateType = UpdateType.values()[buf.readVarInt()];
int currentDirection = buf.readVarInt();
int currentResistance = buf.readVarInt();
int maxResistance = buf.readVarInt();
boolean isLocked = buf.readBoolean();
return new PacketContinuousStruggleState(
sessionId,
updateType,
currentDirection,
currentResistance,
maxResistance,
isLocked
);
}
@Override
@OnlyIn(Dist.CLIENT)
protected void handleClientImpl() {
ClientHandler.handle(this);
}
@OnlyIn(Dist.CLIENT)
private static class ClientHandler {
private static void handle(PacketContinuousStruggleState pkt) {
net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getInstance();
if (mc.player == null) {
TiedUpMod.LOGGER.warn(
"[PacketContinuousStruggleState] Player is null, cannot handle packet"
);
return;
}
TiedUpMod.LOGGER.info(
"[PacketContinuousStruggleState] Received update: type={}, direction={}, resistance={}/{}",
pkt.updateType,
pkt.currentDirection,
pkt.currentResistance,
pkt.maxResistance
);
switch (pkt.updateType) {
case START -> {
// Open the continuous struggle screen
TiedUpMod.LOGGER.info(
"[PacketContinuousStruggleState] Opening ContinuousStruggleMiniGameScreen"
);
mc.setScreen(
new com.tiedup.remake.client.gui.screens.ContinuousStruggleMiniGameScreen(
pkt.sessionId,
pkt.currentDirection,
pkt.currentResistance,
pkt.maxResistance,
pkt.isLocked
)
);
}
case DIRECTION_CHANGE -> {
if (
mc.screen instanceof com.tiedup.remake.client.gui.screens.ContinuousStruggleMiniGameScreen screen
) {
screen.onDirectionChange(pkt.currentDirection);
}
}
case RESISTANCE_UPDATE -> {
if (
mc.screen instanceof com.tiedup.remake.client.gui.screens.ContinuousStruggleMiniGameScreen screen
) {
screen.onResistanceUpdate(pkt.currentResistance);
}
}
case SHOCK -> {
if (
mc.screen instanceof com.tiedup.remake.client.gui.screens.ContinuousStruggleMiniGameScreen screen
) {
screen.onShock();
}
}
case ESCAPE -> {
if (
mc.screen instanceof com.tiedup.remake.client.gui.screens.ContinuousStruggleMiniGameScreen screen
) {
screen.onEscape();
}
}
case END -> {
if (
mc.screen instanceof com.tiedup.remake.client.gui.screens.ContinuousStruggleMiniGameScreen screen
) {
screen.onEnd();
}
}
}
}
}
// Getters for potential external use
public UUID getSessionId() {
return sessionId;
}
public UpdateType getUpdateType() {
return updateType;
}
public int getCurrentDirection() {
return currentDirection;
}
public int getCurrentResistance() {
return currentResistance;
}
public int getMaxResistance() {
return maxResistance;
}
public boolean isLocked() {
return isLocked;
}
}

View File

@@ -0,0 +1,99 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState;
import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.PlayerBindState;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
/**
* Client to Server packet to stop the continuous struggle session.
*
* Sent when:
* - Player presses ESC to close the screen
* - Player takes damage (detected client-side)
*/
public class PacketContinuousStruggleStop {
private final UUID sessionId;
public PacketContinuousStruggleStop(UUID sessionId) {
this.sessionId = sessionId;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(sessionId);
}
public static PacketContinuousStruggleStop decode(FriendlyByteBuf buf) {
UUID sessionId = buf.readUUID();
return new PacketContinuousStruggleStop(sessionId);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
ServerPlayer player = ctx.get().getSender();
if (player == null) {
return;
}
handleServer(player);
});
ctx.get().setPacketHandled(true);
}
private void handleServer(ServerPlayer player) {
if (!PacketRateLimiter.allowPacket(player, "minigame")) return;
StruggleSessionManager manager = StruggleSessionManager.getInstance();
ContinuousStruggleMiniGameState session =
manager.getContinuousStruggleSession(player.getUUID());
if (session == null) {
TiedUpMod.LOGGER.debug(
"[PacketContinuousStruggleStop] No active session for {}",
player.getName().getString()
);
return;
}
// Validate session ID
if (!session.getSessionId().equals(sessionId)) {
TiedUpMod.LOGGER.debug(
"[PacketContinuousStruggleStop] Session ID mismatch for {}",
player.getName().getString()
);
return;
}
TiedUpMod.LOGGER.info(
"[PacketContinuousStruggleStop] Player {} stopped struggle session",
player.getName().getString()
);
// Cancel the session
session.cancel();
// Clear struggle animation state and sync
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null) {
state.setStruggling(false, 0);
SyncManager.syncStruggleState(player);
}
// End the session in the manager
manager.endContinuousStruggleSession(player.getUUID(), false);
}
// Getter
public UUID getSessionId() {
return sessionId;
}
}

View File

@@ -0,0 +1,447 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemLockpick;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.furniture.EntityFurniture;
import com.tiedup.remake.v2.furniture.FurnitureDefinition;
import com.tiedup.remake.v2.furniture.network.PacketSyncFurnitureState;
import com.tiedup.remake.minigame.LockpickMiniGameState;
import com.tiedup.remake.minigame.LockpickMiniGameState.PickAttemptResult;
import com.tiedup.remake.minigame.LockpickSessionManager;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.PlayerBindState;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
/**
* Packet for lockpick attempt at current position (Client to Server).
*
* Sent when player presses SPACE to attempt picking at current position.
*/
public class PacketLockpickAttempt {
private final UUID sessionId;
public PacketLockpickAttempt(UUID sessionId) {
this.sessionId = sessionId;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(sessionId);
}
public static PacketLockpickAttempt decode(FriendlyByteBuf buf) {
UUID sessionId = buf.readUUID();
return new PacketLockpickAttempt(sessionId);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
ServerPlayer player = ctx.get().getSender();
if (player == null) {
return;
}
handleServer(player);
});
ctx.get().setPacketHandled(true);
}
private void handleServer(ServerPlayer player) {
// Rate limiting to prevent lockpick attempt spam exploit
if (!PacketRateLimiter.allowPacket(player, "minigame")) {
return;
}
LockpickSessionManager manager = LockpickSessionManager.getInstance();
// Validate session
if (!manager.validateLockpickSession(player.getUUID(), sessionId)) {
TiedUpMod.LOGGER.warn(
"[PacketLockpickAttempt] Invalid session {} for player {}",
sessionId.toString().substring(0, 8),
player.getName().getString()
);
return;
}
LockpickMiniGameState session = manager.getLockpickSession(
player.getUUID()
);
if (session == null || session.isComplete()) {
return;
}
// Play sound and notify guards
manager.onLockpickAttempt(player);
// Attempt to pick
PickAttemptResult result = session.attemptPick();
TiedUpMod.LOGGER.debug(
"[PacketLockpickAttempt] Player {} attempted pick, result: {}",
player.getName().getString(),
result
);
// Handle result
switch (result) {
case SUCCESS -> handleSuccess(player, session);
case OUT_OF_PICKS -> handleOutOfPicks(player, session);
case MISSED -> handleMissed(player, session);
default -> {
// Session already complete
}
}
}
private void handleSuccess(
ServerPlayer player,
LockpickMiniGameState session
) {
// Check for furniture lockpick context FIRST — if present, this is a
// furniture seat lockpick, not a body item lockpick. The context tag is
// written by PacketFurnitureEscape.handleLockpick() when starting the session.
CompoundTag furnitureCtx = player.getPersistentData()
.getCompound("tiedup_furniture_lockpick_ctx");
if (furnitureCtx != null && furnitureCtx.contains("furniture_id")) {
// H18: Distance check BEFORE ending session — prevents consuming session
// without reward if player moved away (reviewer H18 RISK-001)
int furnitureId = furnitureCtx.getInt("furniture_id");
Entity furnitureEntity = player.level().getEntity(furnitureId);
if (furnitureEntity == null || player.distanceTo(furnitureEntity) > 10.0) {
return;
}
// Session validated — now end it
LockpickSessionManager.getInstance().endLockpickSession(
player.getUUID(),
true
);
handleFurnitureLockpickSuccess(player, furnitureCtx);
player.getPersistentData().remove("tiedup_furniture_lockpick_ctx");
damageLockpick(player);
// Send result to client
ModNetwork.sendToPlayer(
new PacketLockpickMiniGameResult(
session.getSessionId(),
PacketLockpickMiniGameResult.ResultType.SUCCESS,
0
),
player
);
return;
}
// Body item lockpick — self-targeting, no distance check needed
LockpickSessionManager.getInstance().endLockpickSession(
player.getUUID(),
true
);
// Body item lockpick path: targetSlot stores BodyRegionV2 ordinal
BodyRegionV2 targetRegion =
BodyRegionV2.values()[session.getTargetSlot()];
ItemStack targetStack = V2EquipmentHelper.getInRegion(
player,
targetRegion
);
if (
!targetStack.isEmpty() &&
targetStack.getItem() instanceof ILockable lockable
) {
// Get lock resistance BEFORE clearing it
int lockResistance = lockable.getCurrentLockResistance(targetStack);
// Unlock the item
lockable.setLockedByKeyUUID(targetStack, null);
lockable.clearLockResistance(targetStack);
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Player {} successfully picked lock on {} ({}, resistance was {})",
player.getName().getString(),
targetStack.getDisplayName().getString(),
targetRegion,
lockResistance
);
// Deduct lock resistance from bind resistance
PlayerBindState state = PlayerBindState.getInstance(player);
if (state != null && state.isTiedUp() && lockResistance > 0) {
int currentBindResistance = state.getCurrentBindResistance();
int newBindResistance = Math.max(
0,
currentBindResistance - lockResistance
);
state.setCurrentBindResistance(newBindResistance);
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Deducted {} from bind resistance: {} -> {}",
lockResistance,
currentBindResistance,
newBindResistance
);
// Check if player escaped (resistance = 0)
if (newBindResistance <= 0) {
state.getStruggleBinds().successActionExternal(state);
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Player {} escaped via lockpick!",
player.getName().getString()
);
}
}
}
// Damage lockpick
damageLockpick(player);
// Sync to all players so unlock is visible immediately
SyncManager.syncInventory(player);
// Send result to client
ModNetwork.sendToPlayer(
new PacketLockpickMiniGameResult(
session.getSessionId(),
PacketLockpickMiniGameResult.ResultType.SUCCESS,
0
),
player
);
}
/**
* Handle a successful furniture seat lockpick: unlock the seat, dismount
* the passenger, play the unlock sound, and broadcast the updated state.
*/
private void handleFurnitureLockpickSuccess(
ServerPlayer player,
CompoundTag ctx
) {
int furnitureEntityId = ctx.getInt("furniture_id");
String seatId = ctx.getString("seat_id");
Entity entity = player.level().getEntity(furnitureEntityId);
if (!(entity instanceof EntityFurniture furniture)) {
TiedUpMod.LOGGER.warn(
"[PacketLockpickAttempt] Furniture entity {} not found or wrong type for lockpick success",
furnitureEntityId
);
return;
}
// Unlock the seat
furniture.setSeatLocked(seatId, false);
// Dismount the passenger in that seat
Entity passenger = furniture.findPassengerInSeat(seatId);
if (passenger != null) {
// Clear reconnection tag before dismount
if (passenger instanceof ServerPlayer passengerPlayer) {
passengerPlayer.getPersistentData().remove("tiedup_locked_furniture");
}
passenger.stopRiding();
}
// Play unlock sound from the furniture definition
FurnitureDefinition def = furniture.getDefinition();
if (def != null && def.feedback().unlockSound() != null) {
player.level().playSound(
null,
entity.getX(), entity.getY(), entity.getZ(),
SoundEvent.createVariableRangeEvent(def.feedback().unlockSound()),
SoundSource.BLOCKS, 1.0f, 1.0f
);
}
// Broadcast updated lock/anim state to all tracking clients
PacketSyncFurnitureState.sendToTracking(furniture);
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Player {} picked furniture lock on entity {} seat '{}'",
player.getName().getString(), furnitureEntityId, seatId
);
}
private void handleOutOfPicks(
ServerPlayer player,
LockpickMiniGameState session
) {
LockpickSessionManager.getInstance().endLockpickSession(
player.getUUID(),
false
);
// Destroy the lockpick
ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(player);
if (!lockpickStack.isEmpty()) {
lockpickStack.shrink(1);
}
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Player {} ran out of lockpicks",
player.getName().getString()
);
// Trigger shock if wearing shock collar
triggerShockIfCollar(player);
// Send result to client
ModNetwork.sendToPlayer(
new PacketLockpickMiniGameResult(
session.getSessionId(),
PacketLockpickMiniGameResult.ResultType.OUT_OF_PICKS,
0
),
player
);
}
private void handleMissed(
ServerPlayer player,
LockpickMiniGameState session
) {
// Calculate distance BEFORE damaging (for animation feedback)
float distance = session.getDistanceToSweetSpot();
// Damage lockpick
damageLockpick(player);
// Update remaining uses from actual lockpick
int remainingUses = 0;
ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(player);
if (!lockpickStack.isEmpty()) {
remainingUses =
lockpickStack.getMaxDamage() - lockpickStack.getDamageValue();
}
session.setRemainingUses(remainingUses);
// Check for JAM (5% chance on miss) — only applies to body item lockpick sessions.
// Furniture seat locks do not have a jam mechanic (there is no ILockable item to jam).
boolean jammed = false;
boolean isFurnitureSession = player.getPersistentData()
.getCompound("tiedup_furniture_lockpick_ctx")
.contains("furniture_id");
if (!isFurnitureSession && player.getRandom().nextFloat() < 0.05f) {
int targetSlot = session.getTargetSlot();
if (targetSlot >= 0 && targetSlot < BodyRegionV2.values().length) {
BodyRegionV2 targetRegion = BodyRegionV2.values()[targetSlot];
ItemStack targetStack = V2EquipmentHelper.getInRegion(
player,
targetRegion
);
if (
!targetStack.isEmpty() &&
targetStack.getItem() instanceof ILockable lockable
) {
lockable.setJammed(targetStack, true);
jammed = true;
player.sendSystemMessage(
Component.literal(
"The lock jammed! Only struggle can open it now."
).withStyle(ChatFormatting.RED)
);
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Player {} jammed the lock on {} ({})",
player.getName().getString(),
targetStack.getDisplayName().getString(),
targetRegion
);
// End session since lock is jammed
LockpickSessionManager.getInstance().endLockpickSession(
player.getUUID(),
false
);
// Sync jam state to all players
SyncManager.syncInventory(player);
}
}
}
// Trigger shock if wearing shock collar
triggerShockIfCollar(player);
// Send result to client with distance for animation
if (jammed) {
// Send cancelled result since lock is jammed
ModNetwork.sendToPlayer(
new PacketLockpickMiniGameResult(
session.getSessionId(),
PacketLockpickMiniGameResult.ResultType.CANCELLED,
0,
distance
),
player
);
} else {
ModNetwork.sendToPlayer(
new PacketLockpickMiniGameResult(
session.getSessionId(),
PacketLockpickMiniGameResult.ResultType.MISSED,
remainingUses,
distance
),
player
);
}
}
/**
* Damage the player's lockpick by 1 durability. If durability is exhausted,
* the lockpick item is consumed (shrunk). Shared by handleSuccess, handleMissed,
* and handleFurnitureLockpickSuccess paths.
*/
private void damageLockpick(ServerPlayer player) {
ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(player);
if (!lockpickStack.isEmpty()) {
lockpickStack.setDamageValue(lockpickStack.getDamageValue() + 1);
if (lockpickStack.getDamageValue() >= lockpickStack.getMaxDamage()) {
lockpickStack.shrink(1);
}
}
}
private void triggerShockIfCollar(ServerPlayer player) {
PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null) return;
ItemStack collar = V2EquipmentHelper.getInRegion(
player, BodyRegionV2.NECK
);
if (collar.isEmpty()) return;
if (
collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar
) {
state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Player {} shocked for failed lockpick",
player.getName().getString()
);
}
}
}

View File

@@ -0,0 +1,87 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.minigame.LockpickMiniGameState;
import com.tiedup.remake.minigame.LockpickSessionManager;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
/**
* Phase 2: Packet for player position update during lockpick mini-game (Client to Server).
*
* Contains:
* - Session UUID
* - New position (0.0 to 1.0)
*/
public class PacketLockpickMiniGameMove {
private final UUID sessionId;
private final float newPosition;
public PacketLockpickMiniGameMove(UUID sessionId, float newPosition) {
this.sessionId = sessionId;
this.newPosition = newPosition;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(sessionId);
buf.writeFloat(newPosition);
}
public static PacketLockpickMiniGameMove decode(FriendlyByteBuf buf) {
UUID sessionId = buf.readUUID();
float newPosition = buf.readFloat();
return new PacketLockpickMiniGameMove(sessionId, newPosition);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
ServerPlayer player = ctx.get().getSender();
if (player == null) {
return;
}
// Rate limiting: Prevent lockpick spam
if (
!com.tiedup.remake.network.PacketRateLimiter.allowPacket(
player,
"minigame"
)
) {
return;
}
handleServer(player);
});
ctx.get().setPacketHandled(true);
}
private void handleServer(ServerPlayer player) {
LockpickSessionManager manager = LockpickSessionManager.getInstance();
// Validate session
if (!manager.validateLockpickSession(player.getUUID(), sessionId)) {
TiedUpMod.LOGGER.warn(
"[PacketLockpickMiniGameMove] Invalid session {} for player {}",
sessionId.toString().substring(0, 8),
player.getName().getString()
);
return;
}
LockpickMiniGameState session = manager.getLockpickSession(
player.getUUID()
);
if (session == null || session.isComplete()) {
return;
}
// Update position (server-side validation: clamp to 0-1)
session.setCurrentPosition(Math.max(0.0f, Math.min(1.0f, newPosition)));
}
}

View File

@@ -0,0 +1,154 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.base.AbstractClientPacket;
import java.util.UUID;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
/**
* Phase 2: Packet to send lockpick result to client (Server to Client).
*
* Contains:
* - Session UUID
* - Result type (SUCCESS, MISSED, OUT_OF_PICKS)
* - Remaining uses
*/
public class PacketLockpickMiniGameResult extends AbstractClientPacket {
public enum ResultType {
/**
* Successfully picked the lock
*/
SUCCESS(0),
/**
* Missed the sweet spot
*/
MISSED(1),
/**
* Ran out of lockpick uses
*/
OUT_OF_PICKS(2),
/**
* Session cancelled
*/
CANCELLED(3);
private final int id;
ResultType(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static ResultType fromId(int id) {
return switch (id) {
case 0 -> SUCCESS;
case 1 -> MISSED;
case 2 -> OUT_OF_PICKS;
case 3 -> CANCELLED;
default -> MISSED;
};
}
}
private final UUID sessionId;
private final ResultType resultType;
private final int remainingUses;
private final float distance; // Distance to sweet spot (0.0-1.0), used for MISSED animation
public PacketLockpickMiniGameResult(
UUID sessionId,
ResultType resultType,
int remainingUses
) {
this(sessionId, resultType, remainingUses, 0f);
}
public PacketLockpickMiniGameResult(
UUID sessionId,
ResultType resultType,
int remainingUses,
float distance
) {
this.sessionId = sessionId;
this.resultType = resultType;
this.remainingUses = remainingUses;
this.distance = distance;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(sessionId);
buf.writeVarInt(resultType.getId());
buf.writeVarInt(remainingUses);
buf.writeFloat(distance);
}
public static PacketLockpickMiniGameResult decode(FriendlyByteBuf buf) {
UUID sessionId = buf.readUUID();
ResultType resultType = ResultType.fromId(buf.readVarInt());
int remainingUses = buf.readVarInt();
float distance = buf.readFloat();
return new PacketLockpickMiniGameResult(
sessionId,
resultType,
remainingUses,
distance
);
}
@Override
@OnlyIn(Dist.CLIENT)
protected void handleClientImpl() {
ClientHandler.handle(this);
}
@OnlyIn(Dist.CLIENT)
private static class ClientHandler {
private static void handle(PacketLockpickMiniGameResult pkt) {
net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getInstance();
if (mc.player == null) {
return;
}
TiedUpMod.LOGGER.debug(
"[PacketLockpickMiniGameResult] Received result: type={}, uses={}",
pkt.resultType,
pkt.remainingUses
);
if (mc.screen instanceof com.tiedup.remake.client.gui.screens.LockpickMiniGameScreen screen) {
switch (pkt.resultType) {
case SUCCESS -> screen.onSuccess();
case MISSED -> screen.onMissed(pkt.remainingUses, pkt.distance);
case OUT_OF_PICKS -> screen.onOutOfPicks();
case CANCELLED -> screen.onCancelled();
}
}
}
}
// Getters
public UUID getSessionId() {
return sessionId;
}
public ResultType getResultType() {
return resultType;
}
public int getRemainingUses() {
return remainingUses;
}
public float getDistance() {
return distance;
}
}

View File

@@ -0,0 +1,157 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemLockpick;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.minigame.LockpickMiniGameState;
import com.tiedup.remake.minigame.LockpickSessionManager;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
/**
* Phase 2: Packet to start a Lockpick mini-game session (Client to Server).
*
* Sent when player clicks "Lockpick" on a locked item.
*/
public class PacketLockpickMiniGameStart {
private final BodyRegionV2 targetRegion;
public PacketLockpickMiniGameStart(BodyRegionV2 targetRegion) {
this.targetRegion = targetRegion;
}
public void encode(FriendlyByteBuf buf) {
buf.writeEnum(targetRegion);
}
public static PacketLockpickMiniGameStart decode(FriendlyByteBuf buf) {
return new PacketLockpickMiniGameStart(buf.readEnum(BodyRegionV2.class));
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
ServerPlayer player = ctx.get().getSender();
if (player == null) {
return;
}
handleServer(player);
});
ctx.get().setPacketHandled(true);
}
private void handleServer(ServerPlayer player) {
if (!PacketRateLimiter.allowPacket(player, "minigame")) return;
PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null) {
return;
}
// Check for mittens
if (state.hasMittens()) {
TiedUpMod.LOGGER.debug(
"[PacketLockpickMiniGameStart] Player {} has mittens, cannot lockpick",
player.getName().getString()
);
return;
}
// Find lockpick in inventory
ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(player);
if (lockpickStack.isEmpty()) {
TiedUpMod.LOGGER.debug(
"[PacketLockpickMiniGameStart] Player {} has no lockpick",
player.getName().getString()
);
return;
}
// Get target item via V2 equipment system
ItemStack targetStack = V2EquipmentHelper.getInRegion(
player,
targetRegion
);
if (
targetStack.isEmpty() ||
!(targetStack.getItem() instanceof ILockable lockable)
) {
TiedUpMod.LOGGER.debug(
"[PacketLockpickMiniGameStart] Target region {} is not lockable",
targetRegion
);
return;
}
if (!lockable.isLocked(targetStack)) {
TiedUpMod.LOGGER.debug(
"[PacketLockpickMiniGameStart] Target region {} is not locked",
targetRegion
);
return;
}
if (lockable.isJammed(targetStack)) {
TiedUpMod.LOGGER.debug(
"[PacketLockpickMiniGameStart] Target region {} is jammed",
targetRegion
);
return;
}
// Determine sweet spot width based on lockpick type
float sweetSpotWidth = 0.03f; // 3% - Very difficult sweet spot (Skyrim-style)
// Get remaining uses
int remainingUses =
lockpickStack.getMaxDamage() - lockpickStack.getDamageValue();
// Start session
LockpickSessionManager manager = LockpickSessionManager.getInstance();
LockpickMiniGameState session = manager.startLockpickSession(
player,
targetRegion.ordinal(),
sweetSpotWidth
);
if (session == null) {
TiedUpMod.LOGGER.warn(
"[PacketLockpickMiniGameStart] Failed to create lockpick session for {}",
player.getName().getString()
);
return;
}
session.setRemainingUses(remainingUses);
TiedUpMod.LOGGER.info(
"[PacketLockpickMiniGameStart] Started lockpick session for {} (region: {}, uses: {})",
player.getName().getString(),
targetRegion,
remainingUses
);
// Send initial state to client
ModNetwork.sendToPlayer(
new PacketLockpickMiniGameState(
session.getSessionId(),
session.getSweetSpotCenter(),
session.getSweetSpotWidth(),
session.getCurrentPosition(),
session.getRemainingUses()
),
player
);
}
}

View File

@@ -0,0 +1,119 @@
package com.tiedup.remake.network.minigame;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.base.AbstractClientPacket;
import java.util.UUID;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
/**
* Phase 2: Packet to send lockpick initial state to client (Server to Client).
*
* Contains:
* - Session UUID
* - Sweet spot center and width (server-authoritative)
* - Current position
* - Remaining lockpick uses
*/
public class PacketLockpickMiniGameState extends AbstractClientPacket {
private final UUID sessionId;
private final float sweetSpotCenter;
private final float sweetSpotWidth;
private final float currentPosition;
private final int remainingUses;
public PacketLockpickMiniGameState(
UUID sessionId,
float sweetSpotCenter,
float sweetSpotWidth,
float currentPosition,
int remainingUses
) {
this.sessionId = sessionId;
this.sweetSpotCenter = sweetSpotCenter;
this.sweetSpotWidth = sweetSpotWidth;
this.currentPosition = currentPosition;
this.remainingUses = remainingUses;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(sessionId);
buf.writeFloat(sweetSpotCenter);
buf.writeFloat(sweetSpotWidth);
buf.writeFloat(currentPosition);
buf.writeVarInt(remainingUses);
}
public static PacketLockpickMiniGameState decode(FriendlyByteBuf buf) {
UUID sessionId = buf.readUUID();
float sweetSpotCenter = buf.readFloat();
float sweetSpotWidth = buf.readFloat();
float currentPosition = buf.readFloat();
int remainingUses = buf.readVarInt();
return new PacketLockpickMiniGameState(
sessionId,
sweetSpotCenter,
sweetSpotWidth,
currentPosition,
remainingUses
);
}
@Override
@OnlyIn(Dist.CLIENT)
protected void handleClientImpl() {
ClientHandler.handle(this);
}
@OnlyIn(Dist.CLIENT)
private static class ClientHandler {
private static void handle(PacketLockpickMiniGameState pkt) {
net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getInstance();
if (mc.player == null) {
return;
}
TiedUpMod.LOGGER.debug(
"[PacketLockpickMiniGameState] Received state: sweet={}(w={}), pos={}, uses={}",
pkt.sweetSpotCenter,
pkt.sweetSpotWidth,
pkt.currentPosition,
pkt.remainingUses
);
// Open the lockpick mini-game screen
mc.setScreen(
new com.tiedup.remake.client.gui.screens.LockpickMiniGameScreen(
pkt.sessionId,
pkt.sweetSpotCenter,
pkt.sweetSpotWidth,
pkt.currentPosition,
pkt.remainingUses
)
);
}
}
// Getters
public UUID getSessionId() {
return sessionId;
}
public float getSweetSpotCenter() {
return sweetSpotCenter;
}
public float getSweetSpotWidth() {
return sweetSpotWidth;
}
public float getCurrentPosition() {
return currentPosition;
}
public int getRemainingUses() {
return remainingUses;
}
}