Strip all Phase references, TODO/FUTURE roadmap notes, and internal planning comments from the codebase. Run Prettier for consistent formatting across all Java files.
468 lines
16 KiB
Java
468 lines
16 KiB
Java
package com.tiedup.remake.client;
|
|
|
|
import com.mojang.blaze3d.platform.InputConstants;
|
|
import com.tiedup.remake.client.gui.screens.AdjustmentScreen;
|
|
import com.tiedup.remake.client.gui.screens.UnifiedBondageScreen;
|
|
import com.tiedup.remake.core.ModConfig;
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import com.tiedup.remake.items.base.ILockable;
|
|
import com.tiedup.remake.items.base.ItemCollar;
|
|
import com.tiedup.remake.network.ModNetwork;
|
|
import com.tiedup.remake.network.action.PacketForceSeatModifier;
|
|
import com.tiedup.remake.network.action.PacketStruggle;
|
|
import com.tiedup.remake.network.action.PacketTighten;
|
|
import com.tiedup.remake.network.bounty.PacketRequestBounties;
|
|
import com.tiedup.remake.state.PlayerBindState;
|
|
import com.tiedup.remake.v2.BodyRegionV2;
|
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
|
import com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.client.KeyMapping;
|
|
import net.minecraft.client.Minecraft;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
|
import net.minecraftforge.event.TickEvent;
|
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
|
import net.minecraftforge.fml.common.Mod;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
/**
|
|
*
|
|
* Manages key mappings and sends packets to server when keys are pressed.
|
|
*
|
|
* Based on original KeyBindings from 1.12.2
|
|
*/
|
|
@Mod.EventBusSubscriber(
|
|
modid = TiedUpMod.MOD_ID,
|
|
bus = Mod.EventBusSubscriber.Bus.FORGE,
|
|
value = Dist.CLIENT
|
|
)
|
|
public class ModKeybindings {
|
|
|
|
/**
|
|
* Key category for TiedUp keybindings
|
|
*/
|
|
private static final String CATEGORY = "key.categories.tiedup";
|
|
|
|
/**
|
|
* Struggle keybinding - Press to struggle against binds
|
|
* Default: R key
|
|
*/
|
|
public static final KeyMapping STRUGGLE_KEY = new KeyMapping(
|
|
"key.tiedup.struggle", // Translation key
|
|
InputConstants.Type.KEYSYM,
|
|
InputConstants.KEY_R, // Default key: R
|
|
CATEGORY
|
|
);
|
|
|
|
/**
|
|
* Adjustment screen keybinding - Open item adjustment screen
|
|
* Default: K key
|
|
*/
|
|
public static final KeyMapping ADJUSTMENT_KEY = new KeyMapping(
|
|
"key.tiedup.adjustment_screen",
|
|
InputConstants.Type.KEYSYM,
|
|
InputConstants.KEY_K, // Default key: K
|
|
CATEGORY
|
|
);
|
|
|
|
/**
|
|
* Bondage inventory keybinding - Open bondage inventory screen
|
|
* Default: J key
|
|
*/
|
|
public static final KeyMapping INVENTORY_KEY = new KeyMapping(
|
|
"key.tiedup.bondage_inventory",
|
|
InputConstants.Type.KEYSYM,
|
|
InputConstants.KEY_J, // Default key: J
|
|
CATEGORY
|
|
);
|
|
|
|
/**
|
|
* Slave management keybinding - Open slave management dashboard
|
|
* Default: L key
|
|
*/
|
|
public static final KeyMapping SLAVE_MANAGEMENT_KEY = new KeyMapping(
|
|
"key.tiedup.slave_management",
|
|
InputConstants.Type.KEYSYM,
|
|
InputConstants.KEY_L, // Default key: L
|
|
CATEGORY
|
|
);
|
|
|
|
/**
|
|
* Bounty list keybinding - Open bounty list screen
|
|
* Default: B key
|
|
*/
|
|
public static final KeyMapping BOUNTY_KEY = new KeyMapping(
|
|
"key.tiedup.bounties",
|
|
InputConstants.Type.KEYSYM,
|
|
InputConstants.KEY_B, // Default key: B
|
|
CATEGORY
|
|
);
|
|
|
|
/**
|
|
* Force seat keybinding - Hold to force captive on/off vehicles
|
|
* Default: Left ALT key
|
|
*/
|
|
public static final KeyMapping FORCE_SEAT_KEY = new KeyMapping(
|
|
"key.tiedup.force_seat",
|
|
InputConstants.Type.KEYSYM,
|
|
InputConstants.KEY_LALT, // Default key: Left ALT
|
|
CATEGORY
|
|
);
|
|
|
|
/**
|
|
* Tighten bind keybinding - Tighten binds on looked-at target
|
|
* Default: T key
|
|
*/
|
|
public static final KeyMapping TIGHTEN_KEY = new KeyMapping(
|
|
"key.tiedup.tighten",
|
|
InputConstants.Type.KEYSYM,
|
|
InputConstants.KEY_T, // Default key: T
|
|
CATEGORY
|
|
);
|
|
|
|
/** Track last sent state to avoid spamming packets */
|
|
private static boolean lastForceSeatState = false;
|
|
|
|
/**
|
|
* Check if Force Seat key is currently pressed.
|
|
*/
|
|
public static boolean isForceSeatPressed() {
|
|
return FORCE_SEAT_KEY.isDown();
|
|
}
|
|
|
|
/**
|
|
* Register keybindings.
|
|
* Called during mod initialization (MOD bus).
|
|
*
|
|
* @param event The registration event
|
|
*/
|
|
public static void register(RegisterKeyMappingsEvent event) {
|
|
event.register(STRUGGLE_KEY);
|
|
event.register(ADJUSTMENT_KEY);
|
|
event.register(INVENTORY_KEY);
|
|
event.register(SLAVE_MANAGEMENT_KEY);
|
|
event.register(BOUNTY_KEY);
|
|
event.register(FORCE_SEAT_KEY);
|
|
event.register(TIGHTEN_KEY);
|
|
TiedUpMod.LOGGER.info("Registered {} keybindings", 7);
|
|
}
|
|
|
|
// ==================== STRUGGLE MINI-GAME (uses vanilla movement keys) ====================
|
|
|
|
/**
|
|
* Get the vanilla movement keybind for a given direction index.
|
|
* Uses Minecraft's movement keys so AZERTY/QWERTY is already configured.
|
|
* @param index 0=FORWARD, 1=LEFT, 2=BACK, 3=RIGHT
|
|
* @return The keybind or null if invalid index
|
|
*/
|
|
public static KeyMapping getStruggleDirectionKey(int index) {
|
|
Minecraft mc = Minecraft.getInstance();
|
|
if (mc.options == null) return null;
|
|
return switch (index) {
|
|
case 0 -> mc.options.keyUp; // Forward (W/Z)
|
|
case 1 -> mc.options.keyLeft; // Strafe Left (A/Q)
|
|
case 2 -> mc.options.keyDown; // Back (S)
|
|
case 3 -> mc.options.keyRight; // Strafe Right (D)
|
|
default -> null;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if a keycode matches any vanilla movement keybind.
|
|
* @param keyCode The GLFW key code
|
|
* @return The direction index (0-3) or -1 if not a movement key
|
|
*/
|
|
public static int getStruggleDirectionFromKeyCode(int keyCode) {
|
|
Minecraft mc = Minecraft.getInstance();
|
|
if (mc.options == null) return -1;
|
|
if (mc.options.keyUp.matches(keyCode, 0)) return 0;
|
|
if (mc.options.keyLeft.matches(keyCode, 0)) return 1;
|
|
if (mc.options.keyDown.matches(keyCode, 0)) return 2;
|
|
if (mc.options.keyRight.matches(keyCode, 0)) return 3;
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get the display name of a vanilla movement key.
|
|
* Shows the actual bound key (W for QWERTY, Z for AZERTY, etc.)
|
|
* @param index 0=FORWARD, 1=LEFT, 2=BACK, 3=RIGHT
|
|
* @return The key's display name
|
|
*/
|
|
public static String getStruggleDirectionKeyName(int index) {
|
|
KeyMapping key = getStruggleDirectionKey(index);
|
|
if (key == null) return "?";
|
|
return key.getTranslatedKeyMessage().getString().toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* Handle key presses on client tick.
|
|
* Called every client tick (FORGE bus).
|
|
*
|
|
* @param event The tick event
|
|
*/
|
|
@SubscribeEvent
|
|
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
|
// Only run at end of tick
|
|
if (event.phase != TickEvent.Phase.END) {
|
|
return;
|
|
}
|
|
|
|
Minecraft mc = Minecraft.getInstance();
|
|
if (mc.player == null || mc.level == null) {
|
|
return;
|
|
}
|
|
|
|
// Sync Force Seat keybind state to server (only send on change)
|
|
boolean currentForceSeatState = isForceSeatPressed();
|
|
if (currentForceSeatState != lastForceSeatState) {
|
|
lastForceSeatState = currentForceSeatState;
|
|
ModNetwork.sendToServer(
|
|
new PacketForceSeatModifier(currentForceSeatState)
|
|
);
|
|
}
|
|
|
|
// Check struggle key - Flow based on bind/accessories
|
|
while (STRUGGLE_KEY.consumeClick()) {
|
|
handleStruggleKey();
|
|
}
|
|
|
|
// Check adjustment screen key
|
|
while (ADJUSTMENT_KEY.consumeClick()) {
|
|
// Only open if not already in a screen and player has adjustable items
|
|
if (mc.screen == null && AdjustmentScreen.canOpen()) {
|
|
mc.setScreen(new AdjustmentScreen());
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] Adjustment key pressed - opening screen"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Check bondage inventory key - opens UnifiedBondageScreen in SELF or MASTER mode
|
|
while (INVENTORY_KEY.consumeClick()) {
|
|
if (mc.screen == null) {
|
|
LivingEntity masterTarget = findOwnedCollarTarget(mc.player);
|
|
if (masterTarget != null) {
|
|
mc.setScreen(new UnifiedBondageScreen(masterTarget));
|
|
} else {
|
|
mc.setScreen(new UnifiedBondageScreen());
|
|
}
|
|
}
|
|
}
|
|
|
|
// SLAVE_MANAGEMENT_KEY: now handled by [J] with master mode detection (see above)
|
|
while (SLAVE_MANAGEMENT_KEY.consumeClick()) {
|
|
// consumed but no-op — kept registered to avoid key conflict during transition
|
|
}
|
|
|
|
// Check bounty list key
|
|
while (BOUNTY_KEY.consumeClick()) {
|
|
// Request bounty list from server (server will open the screen)
|
|
if (mc.screen == null) {
|
|
ModNetwork.sendToServer(new PacketRequestBounties());
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] Bounty key pressed - requesting bounty list"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Check tighten key
|
|
while (TIGHTEN_KEY.consumeClick()) {
|
|
// Send tighten packet to server (server finds target)
|
|
if (mc.screen == null) {
|
|
ModNetwork.sendToServer(new PacketTighten());
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] Tighten key pressed - sending tighten request"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Flow:
|
|
* 1. If bind equipped: Send PacketStruggle to server (struggle against bind)
|
|
* 2. If no bind: Check for locked accessories
|
|
* - If locked accessories exist: Open StruggleChoiceScreen
|
|
* - If no locked accessories: Show "Nothing to struggle" message
|
|
*/
|
|
private static void handleStruggleKey() {
|
|
Minecraft mc = Minecraft.getInstance();
|
|
Player player = mc.player;
|
|
if (player == null || mc.screen != null) {
|
|
return;
|
|
}
|
|
|
|
// V2 path: check if player has V2 equipment to struggle against
|
|
if (
|
|
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.hasAnyEquipment(
|
|
player
|
|
)
|
|
) {
|
|
handleV2Struggle(player);
|
|
return;
|
|
}
|
|
|
|
PlayerBindState state = PlayerBindState.getInstance(player);
|
|
if (state == null) {
|
|
return;
|
|
}
|
|
|
|
// Check if player has bind equipped
|
|
if (state.isTiedUp()) {
|
|
// Has bind - struggle against it
|
|
if (ModConfig.SERVER.struggleMiniGameEnabled.get()) {
|
|
// New: Start struggle mini-game
|
|
ModNetwork.sendToServer(
|
|
new PacketV2StruggleStart(BodyRegionV2.ARMS)
|
|
);
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] Struggle key pressed - starting V2 struggle mini-game"
|
|
);
|
|
} else {
|
|
// Legacy: Probability-based struggle
|
|
ModNetwork.sendToServer(new PacketStruggle());
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] Struggle key pressed - legacy struggle against bind"
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// No bind - check for locked accessories
|
|
boolean hasLockedAccessories = hasAnyLockedAccessory(player);
|
|
|
|
if (hasLockedAccessories) {
|
|
// Open UnifiedBondageScreen in self mode
|
|
mc.setScreen(new UnifiedBondageScreen());
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] Struggle key pressed - opening unified bondage screen"
|
|
);
|
|
} else {
|
|
// No locked accessories - show message
|
|
player.displayClientMessage(
|
|
Component.translatable("tiedup.struggle.nothing").withStyle(
|
|
ChatFormatting.GRAY
|
|
),
|
|
true
|
|
);
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] Struggle key pressed - nothing to struggle"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle struggle key for V2 equipment.
|
|
* Auto-targets the highest posePriority item.
|
|
*/
|
|
private static void handleV2Struggle(Player player) {
|
|
java.util.Map<com.tiedup.remake.v2.BodyRegionV2, ItemStack> equipped =
|
|
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getAllEquipped(
|
|
player
|
|
);
|
|
|
|
if (equipped.isEmpty()) return;
|
|
|
|
// Auto-target: find highest posePriority item
|
|
com.tiedup.remake.v2.BodyRegionV2 bestRegion = null;
|
|
int bestPriority = Integer.MIN_VALUE;
|
|
|
|
for (java.util.Map.Entry<
|
|
com.tiedup.remake.v2.BodyRegionV2,
|
|
ItemStack
|
|
> entry : equipped.entrySet()) {
|
|
ItemStack stack = entry.getValue();
|
|
if (
|
|
stack.getItem() instanceof
|
|
com.tiedup.remake.v2.bondage.IV2BondageItem item
|
|
) {
|
|
if (item.getPosePriority(stack) > bestPriority) {
|
|
bestPriority = item.getPosePriority(stack);
|
|
bestRegion = entry.getKey();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestRegion != null) {
|
|
ModNetwork.sendToServer(
|
|
new com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart(
|
|
bestRegion
|
|
)
|
|
);
|
|
TiedUpMod.LOGGER.debug(
|
|
"[CLIENT] V2 Struggle key pressed - targeting region {}",
|
|
bestRegion.name()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check the crosshair entity: if it is a LivingEntity wearing a collar owned by the player,
|
|
* return it as the MASTER mode target. Returns null if no valid target.
|
|
*/
|
|
@Nullable
|
|
private static LivingEntity findOwnedCollarTarget(Player player) {
|
|
if (player == null) return null;
|
|
Minecraft mc = Minecraft.getInstance();
|
|
net.minecraft.world.entity.Entity crosshair = mc.crosshairPickEntity;
|
|
if (crosshair instanceof LivingEntity living) {
|
|
return checkCollarOwnership(living, player) ? living : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given entity has a collar in the NECK region that lists the player as an owner.
|
|
*/
|
|
private static boolean checkCollarOwnership(
|
|
LivingEntity target,
|
|
Player player
|
|
) {
|
|
ItemStack collarStack =
|
|
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getInRegion(
|
|
target,
|
|
BodyRegionV2.NECK
|
|
);
|
|
if (
|
|
!collarStack.isEmpty() &&
|
|
collarStack.getItem() instanceof ItemCollar collar
|
|
) {
|
|
return collar.isOwner(collarStack, player);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if player has any locked accessories.
|
|
*/
|
|
private static boolean hasAnyLockedAccessory(Player player) {
|
|
BodyRegionV2[] accessoryRegions = {
|
|
BodyRegionV2.MOUTH,
|
|
BodyRegionV2.EYES,
|
|
BodyRegionV2.EARS,
|
|
BodyRegionV2.NECK,
|
|
BodyRegionV2.TORSO,
|
|
BodyRegionV2.HANDS,
|
|
};
|
|
|
|
for (BodyRegionV2 region : accessoryRegions) {
|
|
ItemStack stack = V2EquipmentHelper.getInRegion(player, region);
|
|
if (
|
|
!stack.isEmpty() &&
|
|
stack.getItem() instanceof ILockable lockable
|
|
) {
|
|
if (lockable.isLocked(stack)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|