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:
@@ -0,0 +1,234 @@
|
||||
package com.tiedup.remake.client.events;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.tiedup.remake.core.ModConfig;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.HumanoidArm;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.RenderGuiOverlayEvent;
|
||||
import net.minecraftforge.client.gui.overlay.VanillaGuiOverlay;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Phase 5: Blindfold Rendering
|
||||
*
|
||||
* Based on the original TiedUp! mod (1.12.2) by Yuti & Marl Velius.
|
||||
*
|
||||
* The original approach:
|
||||
* 1. Render blindfold texture over the entire screen (covers everything)
|
||||
* 2. Manually redraw the hotbar on top of the blindfold
|
||||
*
|
||||
* This ensures the hotbar remains visible while the game world is obscured.
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE,
|
||||
value = Dist.CLIENT
|
||||
)
|
||||
public class BlindfoldRenderEventHandler {
|
||||
|
||||
private static final ResourceLocation BLINDFOLD_TEXTURE =
|
||||
ResourceLocation.fromNamespaceAndPath(
|
||||
TiedUpMod.MOD_ID,
|
||||
"textures/misc/blindfolded.png"
|
||||
);
|
||||
|
||||
// Vanilla widgets texture (contains hotbar graphics)
|
||||
private static final ResourceLocation WIDGETS_TEXTURE =
|
||||
ResourceLocation.fromNamespaceAndPath(
|
||||
"minecraft",
|
||||
"textures/gui/widgets.png"
|
||||
);
|
||||
|
||||
private static boolean wasBlindfolded = false;
|
||||
|
||||
/**
|
||||
* Render the blindfold overlay AFTER the hotbar is rendered.
|
||||
* Then redraw the hotbar on top of the blindfold (original mod approach).
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onRenderGuiPost(RenderGuiOverlayEvent.Post event) {
|
||||
// Render after HOTBAR (same as original mod)
|
||||
if (event.getOverlay() != VanillaGuiOverlay.HOTBAR.type()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
LocalPlayer player = mc.player;
|
||||
|
||||
// Safety checks
|
||||
if (player == null || mc.options.hideGui) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-hardcore mode: hide blindfold when a GUI screen is open
|
||||
boolean hardcore = ModConfig.CLIENT.hardcoreBlindfold.get();
|
||||
if (!hardcore && mc.screen != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get player state
|
||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isBlindfolded = state.isBlindfolded();
|
||||
|
||||
// Log state changes only
|
||||
if (isBlindfolded != wasBlindfolded) {
|
||||
if (isBlindfolded) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BLINDFOLD] Player is now blindfolded - rendering overlay"
|
||||
);
|
||||
} else {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BLINDFOLD] Player is no longer blindfolded - stopping overlay"
|
||||
);
|
||||
}
|
||||
wasBlindfolded = isBlindfolded;
|
||||
}
|
||||
|
||||
// Only render if blindfolded
|
||||
if (!isBlindfolded) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int screenWidth = mc.getWindow().getGuiScaledWidth();
|
||||
int screenHeight = mc.getWindow().getGuiScaledHeight();
|
||||
|
||||
// Set opacity: hardcore forces full opacity, otherwise use config
|
||||
float opacity = hardcore
|
||||
? 1.0F
|
||||
: ModConfig.CLIENT.blindfoldOverlayOpacity
|
||||
.get()
|
||||
.floatValue();
|
||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, opacity);
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
|
||||
// Step 1: Render the blindfold texture over the entire screen
|
||||
event
|
||||
.getGuiGraphics()
|
||||
.blit(
|
||||
BLINDFOLD_TEXTURE,
|
||||
0,
|
||||
0,
|
||||
0.0F,
|
||||
0.0F,
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
screenWidth,
|
||||
screenHeight
|
||||
);
|
||||
|
||||
// Reset shader color for hotbar
|
||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
|
||||
// Step 2: Redraw the hotbar on top of the blindfold (original mod approach)
|
||||
redrawHotbar(
|
||||
mc,
|
||||
event.getGuiGraphics(),
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
player
|
||||
);
|
||||
} catch (RuntimeException e) {
|
||||
TiedUpMod.LOGGER.error("[BLINDFOLD] Error rendering overlay", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually redraw the hotbar on top of the blindfold texture.
|
||||
* Based on the original mod's redrawHotBar() function.
|
||||
*
|
||||
* This draws:
|
||||
* - Hotbar background (182x22 pixels)
|
||||
* - Selected slot highlight
|
||||
* - Offhand slot (if item present)
|
||||
*/
|
||||
private static void redrawHotbar(
|
||||
Minecraft mc,
|
||||
GuiGraphics guiGraphics,
|
||||
int screenWidth,
|
||||
int screenHeight,
|
||||
LocalPlayer player
|
||||
) {
|
||||
// Reset render state
|
||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
|
||||
// Center of screen (hotbar is centered)
|
||||
int centerX = screenWidth / 2;
|
||||
int hotbarY = screenHeight - 22; // Hotbar is 22 pixels from bottom
|
||||
|
||||
// Draw hotbar background (182 pixels wide, 22 pixels tall)
|
||||
// Original: this.drawTexturedModalRect(i - 91, sr.getScaledHeight() - 22, 0, 0, 182, 22);
|
||||
guiGraphics.blit(
|
||||
WIDGETS_TEXTURE,
|
||||
centerX - 91,
|
||||
hotbarY, // Position
|
||||
0,
|
||||
0, // Texture UV start
|
||||
182,
|
||||
22 // Size
|
||||
);
|
||||
|
||||
// Draw selected slot highlight (24x22 pixels)
|
||||
// Original: this.drawTexturedModalRect(i - 91 - 1 + entityplayer.inventory.currentItem * 20, ...);
|
||||
int selectedSlot = player.getInventory().selected;
|
||||
guiGraphics.blit(
|
||||
WIDGETS_TEXTURE,
|
||||
centerX - 91 - 1 + selectedSlot * 20,
|
||||
hotbarY - 1, // Position (offset by selected slot)
|
||||
0,
|
||||
22, // Texture UV (highlight texture)
|
||||
24,
|
||||
22 // Size
|
||||
);
|
||||
|
||||
// Draw offhand slot if player has an item in offhand
|
||||
ItemStack offhandItem = player.getItemInHand(InteractionHand.OFF_HAND);
|
||||
if (!offhandItem.isEmpty()) {
|
||||
HumanoidArm offhandSide = player.getMainArm().getOpposite();
|
||||
|
||||
if (offhandSide == HumanoidArm.LEFT) {
|
||||
// Offhand on left side
|
||||
// Original: this.drawTexturedModalRect(i - 91 - 29, sr.getScaledHeight() - 23, 24, 22, 29, 24);
|
||||
guiGraphics.blit(
|
||||
WIDGETS_TEXTURE,
|
||||
centerX - 91 - 29,
|
||||
hotbarY - 1, // Position
|
||||
24,
|
||||
22, // Texture UV
|
||||
29,
|
||||
24 // Size
|
||||
);
|
||||
} else {
|
||||
// Offhand on right side
|
||||
// Original: this.drawTexturedModalRect(i + 91, sr.getScaledHeight() - 23, 53, 22, 29, 24);
|
||||
guiGraphics.blit(
|
||||
WIDGETS_TEXTURE,
|
||||
centerX + 91,
|
||||
hotbarY - 1, // Position
|
||||
53,
|
||||
22, // Texture UV
|
||||
29,
|
||||
24 // Size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RenderSystem.disableBlend();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package com.tiedup.remake.client.events;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.tiedup.remake.blocks.BlockMarker;
|
||||
import com.tiedup.remake.blocks.entity.MarkerBlockEntity;
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.cells.CellRegistryV2;
|
||||
import com.tiedup.remake.cells.MarkerType;
|
||||
import com.tiedup.remake.client.renderer.CellOutlineRenderer;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.ItemAdminWand;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.RenderLevelStageEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Event handler for rendering cell outlines when holding an admin wand.
|
||||
*
|
||||
* Phase: Kidnapper Revamp - Cell System
|
||||
*
|
||||
* Renders colored outlines around cell positions when:
|
||||
* - Player is holding an Admin Wand
|
||||
* - A cell is currently selected in the wand
|
||||
*
|
||||
* The outlines help builders visualize which blocks are part of the cell.
|
||||
*
|
||||
* Network sync: On dedicated servers, cell data is synced via PacketSyncCellData.
|
||||
* On integrated servers, direct access to server data is used as a fallback.
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE,
|
||||
value = Dist.CLIENT
|
||||
)
|
||||
public class CellHighlightHandler {
|
||||
|
||||
// Client-side cache of cell data (synced from server via PacketSyncCellData)
|
||||
private static final java.util.Map<UUID, CellDataV2> syncedCells =
|
||||
new java.util.concurrent.ConcurrentHashMap<>();
|
||||
|
||||
// Legacy single-cell cache for backward compatibility
|
||||
private static CellDataV2 cachedCellData = null;
|
||||
private static UUID cachedCellId = null;
|
||||
|
||||
/**
|
||||
* Render cell outlines after translucent blocks.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onRenderLevelStage(RenderLevelStageEvent event) {
|
||||
// Only render after translucent stage (so outlines appear on top)
|
||||
if (
|
||||
event.getStage() !=
|
||||
RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
LocalPlayer player = mc.player;
|
||||
|
||||
if (player == null) return;
|
||||
|
||||
// Check if player is holding an Admin Wand
|
||||
ItemStack mainHand = player.getMainHandItem();
|
||||
ItemStack offHand = player.getOffhandItem();
|
||||
|
||||
boolean holdingAdminWand =
|
||||
mainHand.getItem() instanceof ItemAdminWand ||
|
||||
offHand.getItem() instanceof ItemAdminWand;
|
||||
|
||||
if (!holdingAdminWand) {
|
||||
cachedCellData = null;
|
||||
cachedCellId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
PoseStack poseStack = event.getPoseStack();
|
||||
Camera camera = event.getCamera();
|
||||
|
||||
// If holding Admin Wand, render nearby structure markers and preview
|
||||
renderNearbyStructureMarkers(poseStack, camera, player);
|
||||
renderAdminWandPreview(poseStack, camera, player, mainHand, offHand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a preview outline showing where the Admin Wand will place a marker.
|
||||
*/
|
||||
private static void renderAdminWandPreview(
|
||||
PoseStack poseStack,
|
||||
Camera camera,
|
||||
LocalPlayer player,
|
||||
ItemStack mainHand,
|
||||
ItemStack offHand
|
||||
) {
|
||||
// Get the block the player is looking at
|
||||
net.minecraft.world.phys.HitResult hitResult =
|
||||
Minecraft.getInstance().hitResult;
|
||||
if (
|
||||
hitResult == null ||
|
||||
hitResult.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
net.minecraft.world.phys.BlockHitResult blockHit =
|
||||
(net.minecraft.world.phys.BlockHitResult) hitResult;
|
||||
BlockPos targetPos = blockHit.getBlockPos().above(); // Marker goes above the clicked block
|
||||
|
||||
// Get the current marker type from the wand
|
||||
MarkerType type;
|
||||
if (mainHand.getItem() instanceof ItemAdminWand) {
|
||||
type = ItemAdminWand.getCurrentType(mainHand);
|
||||
} else {
|
||||
type = ItemAdminWand.getCurrentType(offHand);
|
||||
}
|
||||
|
||||
Vec3 cameraPos = camera.getPosition();
|
||||
float[] color = CellOutlineRenderer.getColorForType(type);
|
||||
|
||||
// Make preview semi-transparent and pulsing
|
||||
float alpha =
|
||||
0.5f + 0.3f * (float) Math.sin(System.currentTimeMillis() / 200.0);
|
||||
float[] previewColor = { color[0], color[1], color[2], alpha };
|
||||
|
||||
// Setup rendering (depth test off so preview shows through blocks)
|
||||
com.mojang.blaze3d.systems.RenderSystem.enableBlend();
|
||||
com.mojang.blaze3d.systems.RenderSystem.defaultBlendFunc();
|
||||
com.mojang.blaze3d.systems.RenderSystem.disableDepthTest();
|
||||
com.mojang.blaze3d.systems.RenderSystem.depthMask(false);
|
||||
com.mojang.blaze3d.systems.RenderSystem.setShader(
|
||||
net.minecraft.client.renderer.GameRenderer::getPositionColorShader
|
||||
);
|
||||
|
||||
CellOutlineRenderer.renderFilledBlock(
|
||||
poseStack,
|
||||
targetPos,
|
||||
cameraPos,
|
||||
previewColor
|
||||
);
|
||||
|
||||
com.mojang.blaze3d.systems.RenderSystem.depthMask(true);
|
||||
com.mojang.blaze3d.systems.RenderSystem.enableDepthTest();
|
||||
com.mojang.blaze3d.systems.RenderSystem.disableBlend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render outlines for nearby structure markers (markers without cell IDs).
|
||||
*/
|
||||
private static void renderNearbyStructureMarkers(
|
||||
PoseStack poseStack,
|
||||
Camera camera,
|
||||
LocalPlayer player
|
||||
) {
|
||||
Level level = player.level();
|
||||
BlockPos playerPos = player.blockPosition();
|
||||
Vec3 cameraPos = camera.getPosition();
|
||||
|
||||
// Collect markers first to check if we need to render anything
|
||||
java.util.List<
|
||||
java.util.Map.Entry<BlockPos, MarkerType>
|
||||
> markersToRender = new java.util.ArrayList<>();
|
||||
|
||||
// Scan in a 32-block radius for structure markers
|
||||
int radius = 32;
|
||||
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
for (int y = -radius / 2; y <= radius / 2; y++) {
|
||||
for (int z = -radius; z <= radius; z++) {
|
||||
BlockPos pos = playerPos.offset(x, y, z);
|
||||
|
||||
if (
|
||||
level.getBlockState(pos).getBlock() instanceof
|
||||
BlockMarker
|
||||
) {
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (be instanceof MarkerBlockEntity marker) {
|
||||
// Only render structure markers (no cell ID)
|
||||
if (marker.getCellId() == null) {
|
||||
markersToRender.add(
|
||||
java.util.Map.entry(
|
||||
pos,
|
||||
marker.getMarkerType()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only setup rendering if we have markers to render
|
||||
if (!markersToRender.isEmpty()) {
|
||||
// Setup rendering state (depth test off so markers show through blocks)
|
||||
com.mojang.blaze3d.systems.RenderSystem.enableBlend();
|
||||
com.mojang.blaze3d.systems.RenderSystem.defaultBlendFunc();
|
||||
com.mojang.blaze3d.systems.RenderSystem.disableDepthTest();
|
||||
com.mojang.blaze3d.systems.RenderSystem.depthMask(false);
|
||||
com.mojang.blaze3d.systems.RenderSystem.setShader(
|
||||
net.minecraft.client.renderer
|
||||
.GameRenderer::getPositionColorShader
|
||||
);
|
||||
|
||||
for (var entry : markersToRender) {
|
||||
BlockPos pos = entry.getKey();
|
||||
MarkerType type = entry.getValue();
|
||||
float[] baseColor = CellOutlineRenderer.getColorForType(type);
|
||||
// Semi-transparent fill
|
||||
float[] fillColor = {
|
||||
baseColor[0],
|
||||
baseColor[1],
|
||||
baseColor[2],
|
||||
0.4f,
|
||||
};
|
||||
CellOutlineRenderer.renderFilledBlock(
|
||||
poseStack,
|
||||
pos,
|
||||
cameraPos,
|
||||
fillColor
|
||||
);
|
||||
}
|
||||
|
||||
// Restore rendering state
|
||||
com.mojang.blaze3d.systems.RenderSystem.depthMask(true);
|
||||
com.mojang.blaze3d.systems.RenderSystem.enableDepthTest();
|
||||
com.mojang.blaze3d.systems.RenderSystem.disableBlend();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cell data on client side.
|
||||
* First checks the network-synced cache, then falls back to integrated server access.
|
||||
*
|
||||
* @param cellId The cell UUID to look up
|
||||
* @return CellDataV2 if found, null otherwise
|
||||
*/
|
||||
private static CellDataV2 getCellDataClient(UUID cellId) {
|
||||
if (cellId == null) return null;
|
||||
|
||||
// Priority 1: Check network-synced cache (works on dedicated servers)
|
||||
CellDataV2 synced = syncedCells.get(cellId);
|
||||
if (synced != null) {
|
||||
return synced;
|
||||
}
|
||||
|
||||
// Priority 2: Check legacy single-cell cache
|
||||
if (cellId.equals(cachedCellId) && cachedCellData != null) {
|
||||
return cachedCellData;
|
||||
}
|
||||
|
||||
// Priority 3: On integrated server, access server level directly (fallback)
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.getSingleplayerServer() != null) {
|
||||
ServerLevel serverLevel = mc.getSingleplayerServer().overworld();
|
||||
if (serverLevel != null) {
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
CellDataV2 cell = registry.getCell(cellId);
|
||||
if (cell != null) {
|
||||
// Cache for future use
|
||||
cachedCellId = cellId;
|
||||
cachedCellData = cell;
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not found - on dedicated server, packet hasn't arrived yet
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cached cell data (called from network sync - PacketSyncCellData).
|
||||
* Stores in both the synced map and legacy cache for compatibility.
|
||||
*
|
||||
* @param cell The cell data received from server
|
||||
*/
|
||||
public static void updateCachedCell(CellDataV2 cell) {
|
||||
if (cell != null) {
|
||||
// Store in synced map
|
||||
syncedCells.put(cell.getId(), cell);
|
||||
|
||||
// Also update legacy cache
|
||||
cachedCellId = cell.getId();
|
||||
cachedCellData = cell;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cell from the cache (e.g., when cell is deleted).
|
||||
*
|
||||
* @param cellId The cell UUID to remove
|
||||
*/
|
||||
public static void removeCachedCell(UUID cellId) {
|
||||
if (cellId != null) {
|
||||
syncedCells.remove(cellId);
|
||||
if (cellId.equals(cachedCellId)) {
|
||||
cachedCellId = null;
|
||||
cachedCellData = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached cell data.
|
||||
* Called when disconnecting from server or on dimension change.
|
||||
*/
|
||||
public static void clearCache() {
|
||||
syncedCells.clear();
|
||||
cachedCellId = null;
|
||||
cachedCellData = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.tiedup.remake.client.events;
|
||||
|
||||
import com.tiedup.remake.client.MuffledSoundInstance;
|
||||
import com.tiedup.remake.core.ModConfig;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.client.event.sound.PlaySoundEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Client-side sound handler for earplugs effect.
|
||||
*
|
||||
* When the player has earplugs equipped, all sounds are muffled
|
||||
* (volume reduced to simulate hearing impairment).
|
||||
*
|
||||
* Based on Forge's PlaySoundEvent to intercept and modify sounds.
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE,
|
||||
value = Dist.CLIENT
|
||||
)
|
||||
public class EarplugSoundHandler {
|
||||
|
||||
/** Pitch modifier to make sounds more muffled (much lower for "underwater" effect) */
|
||||
private static final float MUFFLED_PITCH_MODIFIER = 0.6f;
|
||||
|
||||
/** Categories to always let through at normal volume (important UI feedback) */
|
||||
private static final SoundSource[] UNAFFECTED_CATEGORIES = {
|
||||
SoundSource.MASTER, // Master volume controls
|
||||
SoundSource.MUSIC, // Music is internal, not muffled by earplugs
|
||||
};
|
||||
|
||||
/**
|
||||
* Intercept sound events and muffle them if player has earplugs.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onPlaySound(PlaySoundEvent event) {
|
||||
// Get the sound being played
|
||||
SoundInstance sound = event.getSound();
|
||||
if (sound == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if player has earplugs
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) {
|
||||
return;
|
||||
}
|
||||
LocalPlayer player = mc.player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
||||
if (state == null || !state.hasEarplugs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this sound category should be affected
|
||||
SoundSource source = sound.getSource();
|
||||
for (SoundSource unaffected : UNAFFECTED_CATEGORIES) {
|
||||
if (source == unaffected) {
|
||||
return; // Don't muffle this category
|
||||
}
|
||||
}
|
||||
|
||||
// Don't wrap already-wrapped sounds (prevent infinite recursion)
|
||||
if (sound instanceof MuffledSoundInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap the sound with our muffling wrapper
|
||||
// The wrapper delegates to the original but modifies getVolume()/getPitch()
|
||||
SoundInstance muffledSound = new MuffledSoundInstance(
|
||||
sound,
|
||||
ModConfig.CLIENT.earplugVolumeMultiplier.get().floatValue(),
|
||||
MUFFLED_PITCH_MODIFIER
|
||||
);
|
||||
|
||||
event.setSound(muffledSound);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.tiedup.remake.client.events;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.tiedup.remake.client.animation.BondageAnimationManager;
|
||||
import com.tiedup.remake.client.animation.PendingAnimationManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.event.entity.EntityLeaveLevelEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Automatic cleanup handler for entity-related resources.
|
||||
*
|
||||
* <p>This handler automatically cleans up animation layers and pending animations
|
||||
* when entities leave the world, preventing memory leaks from stale cache entries.
|
||||
*
|
||||
* <p>Phase: Performance & Memory Management
|
||||
*
|
||||
* <p>Previously, cleanup had to be called manually via {@link BondageAnimationManager#cleanup(java.util.UUID)},
|
||||
* which was error-prone and could lead to memory leaks if forgotten.
|
||||
* This handler ensures cleanup happens automatically on entity removal.
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE,
|
||||
value = Dist.CLIENT
|
||||
)
|
||||
public class EntityCleanupHandler {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
/**
|
||||
* Automatically clean up animation resources when an entity leaves the world.
|
||||
*
|
||||
* <p>This event fires when:
|
||||
* <ul>
|
||||
* <li>An entity is removed from the world (killed, despawned, unloaded)</li>
|
||||
* <li>A player logs out</li>
|
||||
* <li>A chunk is unloaded and its entities are removed</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Cleanup includes:
|
||||
* <ul>
|
||||
* <li>Removing animation layers from {@link BondageAnimationManager}</li>
|
||||
* <li>Removing pending animations from {@link PendingAnimationManager}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param event The entity leave level event
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onEntityLeaveLevel(EntityLeaveLevelEvent event) {
|
||||
// Only process on client side
|
||||
if (!event.getLevel().isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up animation layers
|
||||
BondageAnimationManager.cleanup(event.getEntity().getUUID());
|
||||
|
||||
// Clean up pending animation queue
|
||||
PendingAnimationManager.remove(event.getEntity().getUUID());
|
||||
|
||||
LOGGER.debug(
|
||||
"Auto-cleaned animation resources for entity: {} (type: {})",
|
||||
event.getEntity().getUUID(),
|
||||
event.getEntity().getClass().getSimpleName()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.tiedup.remake.client.events;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.BindVariant;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Client-side handler for smooth leash proxy positioning.
|
||||
*
|
||||
* FIX: Changed from RenderLevelStageEvent.AFTER_ENTITIES to ClientTickEvent.
|
||||
* AFTER_ENTITIES positioned the proxy AFTER rendering, causing 1-frame lag.
|
||||
* ClientTickEvent positions BEFORE rendering for smooth leash display.
|
||||
*
|
||||
* Instead of waiting for server position updates (which causes lag),
|
||||
* this handler repositions the proxy entity locally each tick based
|
||||
* on the player's current position.
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
value = Dist.CLIENT,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE
|
||||
)
|
||||
public class LeashProxyClientHandler {
|
||||
|
||||
/**
|
||||
* Map of player UUID -> proxy entity ID.
|
||||
* Uses UUID for player (persistent) and entity ID for proxy (runtime).
|
||||
*/
|
||||
private static final Map<UUID, Integer> playerToProxy =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/** Default Y offset for normal standing pose (neck height) */
|
||||
private static final double DEFAULT_Y_OFFSET = 1.3;
|
||||
|
||||
/** Y offset for dogwalk pose (back/hip level) */
|
||||
private static final double DOGWALK_Y_OFFSET = 0.35;
|
||||
|
||||
/**
|
||||
* Handle sync packet from server.
|
||||
* Called when a player gets leashed or unleashed.
|
||||
*/
|
||||
public static void handleSyncPacket(
|
||||
UUID targetPlayerUUID,
|
||||
int proxyId,
|
||||
boolean attach
|
||||
) {
|
||||
if (attach) {
|
||||
playerToProxy.put(targetPlayerUUID, proxyId);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[LeashProxyClient] Registered proxy {} for player {}",
|
||||
proxyId,
|
||||
targetPlayerUUID
|
||||
);
|
||||
} else {
|
||||
playerToProxy.remove(targetPlayerUUID);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[LeashProxyClient] Removed proxy for player {}",
|
||||
targetPlayerUUID
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FIX: Use ClientTickEvent instead of RenderLevelStageEvent.AFTER_ENTITIES.
|
||||
* This positions proxies BEFORE rendering, eliminating the 1-frame lag
|
||||
* that caused jittery leash rendering.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
// Only run at end of tick (after player position is updated)
|
||||
if (event.phase != TickEvent.Phase.END) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerToProxy.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.level == null || mc.isPaused()) {
|
||||
return;
|
||||
}
|
||||
Level level = mc.level;
|
||||
|
||||
// Reposition each tracked proxy
|
||||
for (Map.Entry<UUID, Integer> entry : playerToProxy.entrySet()) {
|
||||
UUID playerUUID = entry.getKey();
|
||||
int proxyId = entry.getValue();
|
||||
|
||||
Player playerEntity = level.getPlayerByUUID(playerUUID);
|
||||
Entity proxyEntity = level.getEntity(proxyId);
|
||||
|
||||
if (playerEntity != null && proxyEntity != null) {
|
||||
// FIX: Calculate Y offset based on bind type (dogwalk vs normal)
|
||||
double yOffset = calculateYOffset(playerEntity);
|
||||
|
||||
// Use current position (interpolation will be handled by renderer)
|
||||
double x = playerEntity.getX();
|
||||
double y = playerEntity.getY() + yOffset;
|
||||
double z = playerEntity.getZ() - 0.15;
|
||||
|
||||
// Set proxy position
|
||||
proxyEntity.setPos(x, y, z);
|
||||
|
||||
// Update old positions for smooth interpolation
|
||||
proxyEntity.xOld = proxyEntity.xo = x;
|
||||
proxyEntity.yOld = proxyEntity.yo = y;
|
||||
proxyEntity.zOld = proxyEntity.zo = z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Y offset based on player's bind type.
|
||||
* Dogwalk (DOGBINDER) uses lower offset for 4-legged pose.
|
||||
*/
|
||||
private static double calculateYOffset(Player player) {
|
||||
IBondageState state = KidnappedHelper.getKidnappedState(player);
|
||||
if (state != null && state.isTiedUp()) {
|
||||
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
|
||||
if (
|
||||
!bind.isEmpty() &&
|
||||
bind.getItem() == ModItems.getBind(BindVariant.DOGBINDER)
|
||||
) {
|
||||
return DOGWALK_Y_OFFSET;
|
||||
}
|
||||
}
|
||||
return DEFAULT_Y_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all tracked proxies.
|
||||
* Called when disconnecting from server.
|
||||
*/
|
||||
public static void clearAll() {
|
||||
playerToProxy.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.tiedup.remake.client.events;
|
||||
|
||||
import com.tiedup.remake.items.base.*;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageItem;
|
||||
import com.tiedup.remake.network.selfbondage.PacketSelfBondage;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Client-side event handler for self-bondage input.
|
||||
*
|
||||
* Intercepts left-click when holding bondage items and
|
||||
* sends packets continuously to the server to perform self-bondage.
|
||||
*
|
||||
* Self-bondage items:
|
||||
* - Binds (rope, chain, etc.) - Self-tie (requires holding left-click)
|
||||
* - Gags - Self-gag (if already tied, instant)
|
||||
* - Blindfolds - Self-blindfold (if already tied, instant)
|
||||
* - Mittens - Self-mitten (if already tied, instant)
|
||||
* - Earplugs - Self-earplug (if already tied, instant)
|
||||
* - Collar - NOT ALLOWED (cannot self-collar)
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = "tiedup",
|
||||
value = Dist.CLIENT,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE
|
||||
)
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class SelfBondageInputHandler {
|
||||
|
||||
/** Track if we're currently in self-bondage mode */
|
||||
private static boolean isSelfBondageActive = false;
|
||||
|
||||
/** The hand we're using for self-bondage */
|
||||
private static InteractionHand activeHand = null;
|
||||
|
||||
/** Tick counter for packet sending interval */
|
||||
private static int tickCounter = 0;
|
||||
|
||||
/** Send packet every 4 ticks (5 times per second) for smooth progress */
|
||||
private static final int PACKET_INTERVAL = 4;
|
||||
|
||||
/**
|
||||
* Handle left-click in empty air - START self-bondage.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onLeftClickEmpty(
|
||||
PlayerInteractEvent.LeftClickEmpty event
|
||||
) {
|
||||
startSelfBondage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle left-click on block - START self-bondage (cancel block breaking).
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onLeftClickBlock(
|
||||
PlayerInteractEvent.LeftClickBlock event
|
||||
) {
|
||||
if (!event.getLevel().isClientSide()) return;
|
||||
|
||||
ItemStack stack = event.getItemStack();
|
||||
if (isSelfBondageItem(stack.getItem())) {
|
||||
event.setCanceled(true);
|
||||
startSelfBondage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start self-bondage mode if holding a bondage item.
|
||||
*/
|
||||
private static void startSelfBondage() {
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) return;
|
||||
|
||||
// Check main hand first, then off hand
|
||||
InteractionHand hand = InteractionHand.MAIN_HAND;
|
||||
ItemStack stack = player.getMainHandItem();
|
||||
|
||||
if (!isSelfBondageItem(stack.getItem())) {
|
||||
stack = player.getOffhandItem();
|
||||
hand = InteractionHand.OFF_HAND;
|
||||
|
||||
if (!isSelfBondageItem(stack.getItem())) {
|
||||
return; // No bondage item in either hand
|
||||
}
|
||||
}
|
||||
|
||||
// Start self-bondage mode
|
||||
isSelfBondageActive = true;
|
||||
activeHand = hand;
|
||||
tickCounter = 0;
|
||||
|
||||
// Send initial packet immediately
|
||||
ModNetwork.sendToServer(new PacketSelfBondage(hand));
|
||||
}
|
||||
|
||||
/**
|
||||
* Client tick - continuously send packets while attack button is held.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
if (!isSelfBondageActive) return;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
LocalPlayer player = mc.player;
|
||||
|
||||
// Stop if conditions are no longer valid
|
||||
if (player == null || mc.screen != null) {
|
||||
stopSelfBondage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if attack button is still held
|
||||
if (!mc.options.keyAttack.isDown()) {
|
||||
stopSelfBondage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if still holding bondage item in the active hand
|
||||
ItemStack stack = player.getItemInHand(activeHand);
|
||||
if (!isSelfBondageItem(stack.getItem())) {
|
||||
stopSelfBondage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send packet at interval for continuous progress
|
||||
tickCounter++;
|
||||
if (tickCounter >= PACKET_INTERVAL) {
|
||||
tickCounter = 0;
|
||||
ModNetwork.sendToServer(new PacketSelfBondage(activeHand));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop self-bondage mode.
|
||||
*/
|
||||
private static void stopSelfBondage() {
|
||||
isSelfBondageActive = false;
|
||||
activeHand = null;
|
||||
tickCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an item supports self-bondage.
|
||||
* Collar is explicitly excluded.
|
||||
*/
|
||||
private static boolean isSelfBondageItem(Item item) {
|
||||
// Collar cannot be self-equipped (V1 collar guard)
|
||||
if (item instanceof ItemCollar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// V2 bondage items support self-bondage (left-click hold with tying duration)
|
||||
if (item instanceof IV2BondageItem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// V1 bondage items (legacy)
|
||||
return (
|
||||
item instanceof ItemBind ||
|
||||
item instanceof ItemGag ||
|
||||
item instanceof ItemBlindfold ||
|
||||
item instanceof ItemMittens ||
|
||||
item instanceof ItemEarplugs
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user