Files
TiedUp-/src/main/java/com/tiedup/remake/client/events/CellHighlightHandler.java
NotEvil f6466360b6 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.
2026-04-12 00:51:22 +02:00

324 lines
12 KiB
Java

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;
}
}