Files
Explosion-Overhaul/com/vinlanx/explosionoverhaul/BlockIndexManager.java
2026-05-04 10:03:58 +00:00

1806 lines
87 KiB
Java

/*
* Decompiled with CFR 0.152.
*/
package com.vinlanx.explosionoverhaul;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;
import com.vinlanx.explosionoverhaul.Config;
import com.vinlanx.explosionoverhaul.ExplosionOverhaul;
import com.vinlanx.explosionoverhaul.PacketHandler;
import com.vinlanx.explosionoverhaul.ScanLoadPromptPacket;
import com.vinlanx.explosionoverhaul.ScanProgressPacket;
import com.vinlanx.explosionoverhaul.ScanPromptPacket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.lang.invoke.LambdaMetafactory;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
import net.minecraft.core.BlockPos;
import net.minecraft.core.IdMap;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.event.level.ChunkEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.event.server.ServerStoppedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.server.ServerLifecycleHooks;
@Mod.EventBusSubscriber
public class BlockIndexManager {
private static volatile MinecraftServer currentServer;
private static final Map<ResourceKey<Level>, ConcurrentHashMap<Long, Set<Long>>> indexByDimension;
private static final ConcurrentLinkedQueue<ScanTask> chunkScanQueue;
private static final Map<ResourceKey<Level>, Set<Long>> pendingChunkKeys;
public static volatile boolean ENABLED;
private static final boolean SCAN_ONLY_ON_START = true;
private static volatile boolean INITIAL_SCAN_ENQUEUED;
private static volatile int totalChunksToScan;
private static volatile int chunksScanned;
private static volatile boolean scanningComplete;
private static volatile boolean abortCurrentScan;
private static volatile int lampsFound;
private static volatile int dripstonesFound;
private static volatile int glassBlocksFound;
private static volatile ResourceKey<Level> lastScannedDimension;
private static volatile boolean waitingForUserInput;
private static volatile ServerLevel pendingScanLevel;
private static volatile ResourceKey<Level> currentScannedWorld;
private static volatile boolean isSingleplayer;
private static volatile boolean serverScanPromptShown;
private static volatile boolean serverScanDecisionMade;
private static volatile boolean isRescanMode;
private static volatile ServerPlayer rescanRequestingPlayer;
private static volatile boolean userDeclinedScan;
private static volatile boolean waitingForLoadDecision;
private static volatile boolean hasSaveFile;
private static volatile String currentWorldId;
private static final List<String> DEFAULT_REINFORCED_GLASS_BLACKLIST;
private static final Path MOD_CONFIG_DIRECTORY;
private static final Path GLASS_BLACKLIST_FILE;
private static final CopyOnWriteArrayList<String> GLASS_BLACKLIST;
private static final AtomicReference<Set<String>> GLASS_BLACKLIST_LOOKUP;
private static ConcurrentHashMap<Long, Set<Long>> getOrCreateDimIndex(ServerLevel level) {
ResourceKey dim = level.m_46472_();
return indexByDimension.computeIfAbsent((ResourceKey<Level>)dim, k -> new ConcurrentHashMap());
}
private static Set<Long> getOrCreatePendingSet(ServerLevel level) {
ResourceKey dim = level.m_46472_();
return pendingChunkKeys.computeIfAbsent((ResourceKey<Level>)dim, k -> Collections.newSetFromMap(new ConcurrentHashMap()));
}
private static long chunkKey(ChunkPos cp) {
return (long)cp.f_45578_ & 0xFFFFFFFFL | ((long)cp.f_45579_ & 0xFFFFFFFFL) << 32;
}
private static long packPos(BlockPos pos) {
return pos.m_121878_();
}
private static BlockPos unpackPos(long packed) {
return BlockPos.m_122022_((long)packed);
}
public static int getTotalChunksToScan() {
return totalChunksToScan;
}
public static int getChunksScanned() {
return chunksScanned;
}
public static float getScanProgress() {
if (totalChunksToScan == 0) {
return 1.0f;
}
return Math.min(1.0f, (float)chunksScanned / (float)totalChunksToScan);
}
public static boolean isScanningComplete() {
return scanningComplete;
}
public static int getLampsFound() {
return lampsFound;
}
public static int getDripstonesFound() {
return dripstonesFound;
}
public static int getGlassBlocksFound() {
return glassBlocksFound;
}
private static void clearScanDataSilent() {
indexByDimension.clear();
chunkScanQueue.clear();
pendingChunkKeys.clear();
totalChunksToScan = 0;
chunksScanned = 0;
scanningComplete = false;
currentScannedWorld = null;
lastScannedDimension = null;
lampsFound = 0;
dripstonesFound = 0;
glassBlocksFound = 0;
}
private static void clearScanData() {
BlockIndexManager.clearScanDataSilent();
BlockIndexManager.sendProgressUpdate();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: cleared all scan data for world switch");
}
private static void clearAndSaveData() {
if (!indexByDimension.isEmpty() && scanningComplete) {
try {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
BlockIndexManager.saveIndexData(server);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: saved data before clearing");
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to save data before clearing: {}", (Object)e.getMessage());
}
}
BlockIndexManager.clearScanData();
}
private static void startSingleplayerScan(ServerLevel level) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: starting singleplayer scan for ALL dimensions");
BlockIndexManager.clearScanDataSilent();
MinecraftServer server = level.m_7654_();
BlockIndexManager.scanExistingChunksOnServer(server);
}
private static void startServerScan(MinecraftServer server) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: starting server-wide scan for all dimensions");
BlockIndexManager.clearScanDataSilent();
BlockIndexManager.scanExistingChunksOnServer(server);
}
private static void scanSingleWorld(ServerLevel level) {
try {
chunksScanned = 0;
scanningComplete = false;
lastScannedDimension = level.m_46472_();
lampsFound = 0;
dripstonesFound = 0;
glassBlocksFound = 0;
String dimensionName = level.m_46472_().m_135782_().toString();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: scanning existing chunks in world: {}", (Object)dimensionName);
Set<ChunkPos> existingChunks = BlockIndexManager.getAllExistingChunks(level);
totalChunksToScan = existingChunks.size();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: found {} chunks to scan", (Object)totalChunksToScan);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: using safe server-thread scanning for live chunks");
BlockIndexManager.scanWithSingleThread(level, existingChunks);
}
catch (Exception ex) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: failed to start scan: {}", (Object)ex.toString());
}
}
private static void scanWithSingleThread(ServerLevel level, Set<ChunkPos> existingChunks) {
for (ChunkPos cp : existingChunks) {
chunkScanQueue.add(new ScanTask((ResourceKey<Level>)level.m_46472_(), cp));
}
BlockIndexManager.sendProgressUpdate();
}
private static Set<ChunkPos> getAllExistingChunks(ServerLevel level) {
Set<ChunkPos> existingChunks;
block18: {
existingChunks = Collections.newSetFromMap(new ConcurrentHashMap());
try {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: scanning for all existing chunks...");
MinecraftServer server = level.m_7654_();
String dimPath = level.m_46472_().m_135782_().m_135815_();
Path worldPath = server.m_129843_(LevelResource.f_78182_);
Path regionPath = "overworld".equals(dimPath) ? worldPath.resolve("region") : ("the_nether".equals(dimPath) ? worldPath.resolve("DIM-1").resolve("region") : ("the_end".equals(dimPath) ? worldPath.resolve("DIM1").resolve("region") : worldPath.resolve("dimensions").resolve(level.m_46472_().m_135782_().m_135827_()).resolve(level.m_46472_().m_135782_().m_135815_()).resolve("region")));
ExplosionOverhaul.LOGGER.info("BlockIndexManager: scanning region files in: {}", (Object)regionPath);
if (Files.exists(regionPath, new LinkOption[0])) {
try (Stream<Path> regionFiles = Files.list(regionPath);){
List mcaFiles = regionFiles.filter(path -> path.toString().endsWith(".mca")).collect(Collectors.toList());
ExplosionOverhaul.LOGGER.info("BlockIndexManager: found {} .mca files in region directory", (Object)mcaFiles.size());
mcaFiles.parallelStream().forEach(regionFile -> {
block13: {
try {
int chunksInThisRegion;
int regionZ;
int regionX;
block14: {
String[] parts;
String fileName = regionFile.getFileName().toString();
if (!fileName.startsWith("r.") || !fileName.endsWith(".mca") || (parts = fileName.substring(2, fileName.length() - 4).split("\\.")).length != 2) break block13;
regionX = Integer.parseInt(parts[0]);
regionZ = Integer.parseInt(parts[1]);
chunksInThisRegion = 0;
try (InputStream is = Files.newInputStream(regionFile, new OpenOption[0]);){
byte[] header = new byte[4096];
int bytesRead = is.read(header);
if (bytesRead < 4096) break block14;
for (int i = 0; i < 1024; ++i) {
int offset = i * 4;
int chunkOffset = (header[offset] & 0xFF) << 16 | (header[offset + 1] & 0xFF) << 8 | header[offset + 2] & 0xFF;
if (chunkOffset <= 0) continue;
int cx = i % 32;
int cz = i / 32;
int chunkX = regionX * 32 + cx;
int chunkZ = regionZ * 32 + cz;
ChunkPos cp = new ChunkPos(chunkX, chunkZ);
existingChunks.add(cp);
++chunksInThisRegion;
}
}
catch (Exception readEx) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: failed to read region file {}: {}", (Object)fileName, (Object)readEx.getMessage());
for (int cx = 0; cx < 32; ++cx) {
for (int cz = 0; cz < 32; ++cz) {
int chunkX = regionX * 32 + cx;
int chunkZ = regionZ * 32 + cz;
ChunkPos cp = new ChunkPos(chunkX, chunkZ);
existingChunks.add(cp);
++chunksInThisRegion;
}
}
}
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: region {},{} - found {} existing chunks (read from .mca header)", new Object[]{regionX, regionZ, chunksInThisRegion});
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: failed to parse region file {}: {}", (Object)regionFile.getFileName(), (Object)e.getMessage());
}
}
});
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: scanned region files and found {} existing chunks", (Object)existingChunks.size());
break block18;
}
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: region directory not found: {}", (Object)regionPath);
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.error("BlockIndexManager: failed to scan world storage: {}", (Object)e.getMessage());
ExplosionOverhaul.LOGGER.info("BlockIndexManager: using fallback - estimate chunks from .mca files");
try {
String dimPath = level.m_46472_().m_135782_().m_135815_();
MinecraftServer server = level.m_7654_();
Path worldPath = server.m_129843_(LevelResource.f_78182_);
Path regionPath = "overworld".equals(dimPath) ? worldPath.resolve("region") : ("the_nether".equals(dimPath) ? worldPath.resolve("DIM-1").resolve("region") : ("the_end".equals(dimPath) ? worldPath.resolve("DIM1").resolve("region") : worldPath.resolve("dimensions").resolve(level.m_46472_().m_135782_().m_135827_()).resolve(level.m_46472_().m_135782_().m_135815_()).resolve("region")));
if (!Files.exists(regionPath, new LinkOption[0])) break block18;
try (Stream<Path> regionFiles = Files.list(regionPath);){
regionFiles.filter(path -> path.toString().endsWith(".mca")).forEach(regionFile -> {
String[] parts;
String fileName = regionFile.getFileName().toString();
if (fileName.startsWith("r.") && fileName.endsWith(".mca") && (parts = fileName.substring(2, fileName.length() - 4).split("\\.")).length == 2) {
int regionX = Integer.parseInt(parts[0]);
int regionZ = Integer.parseInt(parts[1]);
for (int cx = 0; cx < 32; ++cx) {
for (int cz = 0; cz < 32; ++cz) {
int chunkX = regionX * 32 + cx;
int chunkZ = regionZ * 32 + cz;
ChunkPos cp = new ChunkPos(chunkX, chunkZ);
existingChunks.add(cp);
}
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: fallback added ~1024 chunks from region {},{}", (Object)regionX, (Object)regionZ);
}
});
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: fallback found {} estimated chunks", (Object)existingChunks.size());
}
catch (Exception fallbackEx) {
ExplosionOverhaul.LOGGER.error("BlockIndexManager: fallback also failed: {}", (Object)fallbackEx.getMessage());
}
}
}
return existingChunks;
}
private static void saveIndexDataAfterScan(MinecraftServer server) {
try {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Starting auto-save after scan completion...");
BlockIndexManager.saveIndexData(server);
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to auto-save scan data: {}", (Object)e.getMessage());
e.printStackTrace();
}
}
private static void sendProgressUpdate() {
try {
ExplosionOverhaul.LOGGER.debug("sendProgressUpdate: {}/{} chunks, complete={}, L:{} D:{} G:{}", new Object[]{chunksScanned, totalChunksToScan, scanningComplete, lampsFound, dripstonesFound, glassBlocksFound});
if (scanningComplete) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: sending final progress update to OP players - {}/{} chunks, lamps={}, dripstones={}, glass={}", new Object[]{chunksScanned, totalChunksToScan, lampsFound, dripstonesFound, glassBlocksFound});
}
ScanProgressPacket packet = new ScanProgressPacket(totalChunksToScan, chunksScanned, scanningComplete, lampsFound, dripstonesFound, glassBlocksFound);
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
for (ServerPlayer player : server.m_6846_().m_11314_()) {
if (!BlockIndexManager.isPlayerAuthorized(player)) continue;
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
}
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send scan progress update: {}", (Object)e.getMessage());
}
}
private static void sendMultiThreadProgressUpdate(int currentProcessed, int currentLamps, int currentDripstones, int currentGlass) {
try {
ScanProgressPacket packet = new ScanProgressPacket(totalChunksToScan, currentProcessed, false, currentLamps, currentDripstones, currentGlass);
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
for (ServerPlayer player : server.m_6846_().m_11314_()) {
if (!BlockIndexManager.isPlayerAuthorized(player)) continue;
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
}
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send multi-thread progress update: {}", (Object)e.getMessage());
}
}
private static void sendScanPromptPacket(ServerPlayer player) {
try {
ScanPromptPacket packet = new ScanPromptPacket(true);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
ExplosionOverhaul.LOGGER.info("Sent scan prompt to authorized player: {}", (Object)player.m_7755_().getString());
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send scan prompt: {}", (Object)e.getMessage());
}
}
public static void startManualScan() {
if (BlockIndexManager.isChunkScanningDisabled()) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: manual scan cancelled - chunk scanning is disabled in config");
BlockIndexManager.cancelManualScan();
return;
}
if (waitingForUserInput && pendingScanLevel != null) {
waitingForUserInput = false;
serverScanDecisionMade = true;
userDeclinedScan = false;
if (isRescanMode) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: clearing data for rescan as user confirmed");
BlockIndexManager.clearScanDataSilent();
try {
MinecraftServer server = pendingScanLevel.m_7654_();
Path saveFile = BlockIndexManager.getSaveFilePath(server);
if (Files.exists(saveFile, new LinkOption[0])) {
Files.delete(saveFile);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: deleted old save file for rescan");
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to delete old save file: {}", (Object)e.getMessage());
}
}
if (isSingleplayer || isRescanMode) {
BlockIndexManager.startSingleplayerScan(pendingScanLevel);
} else {
BlockIndexManager.startServerScan(pendingScanLevel.m_7654_());
}
ServerPlayer requestingPlayer = rescanRequestingPlayer;
pendingScanLevel = null;
isRescanMode = false;
rescanRequestingPlayer = null;
try {
if (requestingPlayer != null && requestingPlayer.m_6084_()) {
ScanPromptPacket packet = new ScanPromptPacket(false);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> requestingPlayer), (Object)packet);
} else {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
for (ServerPlayer player : server.m_6846_().m_11314_()) {
if (!BlockIndexManager.isPlayerAuthorized(player)) continue;
ScanPromptPacket packet = new ScanPromptPacket(false);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
}
}
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to hide scan prompt: {}", (Object)e.getMessage());
}
}
}
/*
* Unable to fully structure code
*/
public static void loadExistingData() {
if (BlockIndexManager.waitingForLoadDecision && BlockIndexManager.pendingScanLevel != null) {
BlockIndexManager.waitingForLoadDecision = false;
BlockIndexManager.serverScanDecisionMade = true;
server = BlockIndexManager.pendingScanLevel.m_7654_();
loaded = BlockIndexManager.loadIndexData(server);
if (loaded) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Successfully loaded existing scan data");
BlockIndexManager.sendProgressUpdate();
try {
currentServer = ServerLifecycleHooks.getCurrentServer();
if (currentServer == null) ** GOTO lbl24
for (ServerPlayer serverPlayer : currentServer.m_6846_().m_11314_()) {
if (!BlockIndexManager.isPlayerAuthorized(serverPlayer)) continue;
packet = new ScanLoadPromptPacket(false);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with((Supplier<ServerPlayer>)LambdaMetafactory.metafactory(null, null, null, ()Ljava/lang/Object;, lambda$loadExistingData$11(net.minecraft.server.level.ServerPlayer ), ()Lnet/minecraft/server/level/ServerPlayer;)((ServerPlayer)serverPlayer)), (Object)packet);
ExplosionOverhaul.LOGGER.debug("Hidden load prompt for authorized player: {}", (Object)serverPlayer.m_7755_().getString());
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to hide load prompt: {}", (Object)e.getMessage());
}
} else {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: Failed to load existing data, falling back to new scan");
BlockIndexManager.startNewScan();
}
lbl24:
// 4 sources
BlockIndexManager.pendingScanLevel = null;
}
}
public static void startNewScan() {
if (waitingForLoadDecision && pendingScanLevel != null) {
waitingForLoadDecision = false;
serverScanDecisionMade = true;
userDeclinedScan = false;
try {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
for (ServerPlayer serverPlayer : server.m_6846_().m_11314_()) {
if (!BlockIndexManager.isPlayerAuthorized(serverPlayer)) continue;
ScanLoadPromptPacket hideLoadPacket = new ScanLoadPromptPacket(false);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> serverPlayer), (Object)hideLoadPacket);
ScanPromptPacket showScanPacket = new ScanPromptPacket(true);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> serverPlayer), (Object)showScanPacket);
ExplosionOverhaul.LOGGER.debug("Switched prompts for authorized player: {}", (Object)serverPlayer.m_7755_().getString());
}
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to switch prompts: {}", (Object)e.getMessage());
}
waitingForUserInput = true;
serverScanDecisionMade = false;
}
}
public static void cancelManualScan() {
if (waitingForUserInput) {
waitingForUserInput = false;
serverScanDecisionMade = true;
userDeclinedScan = true;
pendingScanLevel = null;
ServerPlayer requestingPlayer = rescanRequestingPlayer;
isRescanMode = false;
rescanRequestingPlayer = null;
chunkScanQueue.clear();
totalChunksToScan = 0;
chunksScanned = 0;
scanningComplete = false;
ExplosionOverhaul.LOGGER.info("BlockIndexManager: manual scan cancelled by user - cleared scan queue");
try {
if (requestingPlayer != null && requestingPlayer.m_6084_()) {
ScanPromptPacket packet = new ScanPromptPacket(false);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> requestingPlayer), (Object)packet);
} else {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
for (ServerPlayer player : server.m_6846_().m_11314_()) {
if (!BlockIndexManager.isPlayerAuthorized(player)) continue;
ScanPromptPacket packet = new ScanPromptPacket(false);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
}
}
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to hide scan prompt: {}", (Object)e.getMessage());
}
}
}
public static void resetScanState() {
waitingForUserInput = false;
waitingForLoadDecision = false;
pendingScanLevel = null;
serverScanPromptShown = false;
serverScanDecisionMade = false;
isRescanMode = false;
rescanRequestingPlayer = null;
userDeclinedScan = false;
hasSaveFile = false;
INITIAL_SCAN_ENQUEUED = false;
BlockIndexManager.clearAndSaveData();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: scan state reset by command");
}
public static void forceServerScan(MinecraftServer server) {
BlockIndexManager.resetScanState();
BlockIndexManager.startServerScan(server);
}
public static void forceSingleplayerScan(ServerLevel level) {
BlockIndexManager.resetScanState();
BlockIndexManager.startSingleplayerScan(level);
}
public static void showRescanPrompt(ServerPlayer player) {
isRescanMode = true;
waitingForUserInput = true;
pendingScanLevel = player.m_284548_();
rescanRequestingPlayer = player;
serverScanPromptShown = true;
serverScanDecisionMade = false;
try {
ScanPromptPacket packet = new ScanPromptPacket(true);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: showing rescan prompt to player: {}", (Object)player.m_7755_().getString());
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send rescan prompt: {}", (Object)e.getMessage());
}
}
private static void sendScanPromptPacket(boolean show) {
try {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
for (ServerPlayer player : server.m_6846_().m_11314_()) {
if (!BlockIndexManager.isPlayerAuthorized(player)) continue;
ScanPromptPacket packet = new ScanPromptPacket(show);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
if (!show) continue;
ExplosionOverhaul.LOGGER.debug("Sent scan prompt to authorized player: {}", (Object)player.m_7755_().getString());
}
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send scan prompt packet: {}", (Object)e.getMessage());
}
}
private static boolean isGlassBlock(BlockState state) {
try {
String blockName = ForgeRegistries.BLOCKS.getKey((Object)state.m_60734_()).toString().toLowerCase(Locale.ROOT);
if (BlockIndexManager.isGlassProtected(blockName)) {
return false;
}
return blockName.contains("glass");
}
catch (Exception e) {
return state.m_60713_(Blocks.f_50058_) || state.m_60713_(Blocks.f_152498_);
}
}
private static boolean isLampName(String name) {
return "minecraft:redstone_lamp".equals(name);
}
private static boolean isDripstoneName(String name) {
return "minecraft:pointed_dripstone".equals(name);
}
private static boolean isGlassName(String name) {
if (name == null) {
return false;
}
String normalized = name.toLowerCase(Locale.ROOT);
return normalized.contains("glass") && !BlockIndexManager.isGlassProtected(normalized);
}
public static boolean isReinforcedGlass(BlockState state) {
try {
String blockName = ForgeRegistries.BLOCKS.getKey((Object)state.m_60734_()).toString().toLowerCase(Locale.ROOT);
return BlockIndexManager.isGlassProtected(blockName);
}
catch (Exception e) {
return false;
}
}
public static List<String> getReinforcedGlassBlacklist() {
return new ArrayList<String>(GLASS_BLACKLIST);
}
public static List<String> getDefaultReinforcedGlassBlacklist() {
return new ArrayList<String>(DEFAULT_REINFORCED_GLASS_BLACKLIST);
}
public static void setReinforcedGlassBlacklistFromList(List<String> list) {
BlockIndexManager.applyGlassBlacklistList(list);
BlockIndexManager.saveGlassBlacklistToJson();
}
private static void ensureModConfigDirectory() {
try {
Files.createDirectories(MOD_CONFIG_DIRECTORY, new FileAttribute[0]);
}
catch (IOException e) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: failed to create config directory for glass blacklist", (Throwable)e);
}
}
private static void loadGlassBlacklistFromJson() {
BlockIndexManager.ensureModConfigDirectory();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
if (!Files.exists(GLASS_BLACKLIST_FILE, new LinkOption[0])) {
BlockIndexManager.applyGlassBlacklistList(DEFAULT_REINFORCED_GLASS_BLACKLIST);
BlockIndexManager.saveGlassBlacklistToJson();
return;
}
try (BufferedReader reader = Files.newBufferedReader(GLASS_BLACKLIST_FILE);){
List list = (List)gson.fromJson((Reader)reader, new TypeToken<List<String>>(){}.getType());
if (list == null) {
BlockIndexManager.applyGlassBlacklistList(DEFAULT_REINFORCED_GLASS_BLACKLIST);
return;
}
BlockIndexManager.applyGlassBlacklistList(list);
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: failed to load GlassBlacklist.json, falling back to defaults", (Throwable)e);
BlockIndexManager.applyGlassBlacklistList(DEFAULT_REINFORCED_GLASS_BLACKLIST);
}
}
private static void saveGlassBlacklistToJson() {
BlockIndexManager.ensureModConfigDirectory();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
try (BufferedWriter writer = Files.newBufferedWriter(GLASS_BLACKLIST_FILE, new OpenOption[0]);){
gson.toJson(new ArrayList<String>(GLASS_BLACKLIST), (Appendable)writer);
}
catch (IOException e) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: failed to save GlassBlacklist.json", (Throwable)e);
}
}
private static void applyGlassBlacklistList(List<String> list) {
List<String> normalized = BlockIndexManager.normalizeBlacklist(list);
GLASS_BLACKLIST.clear();
GLASS_BLACKLIST.addAll(normalized);
GLASS_BLACKLIST_LOOKUP.set(new HashSet<String>(normalized));
}
private static List<String> normalizeBlacklist(List<String> entries) {
if (entries == null) {
return new ArrayList<String>();
}
ArrayList<String> result = new ArrayList<String>();
HashSet<String> seen = new HashSet<String>();
for (String raw : entries) {
String candidate;
if (raw == null || (candidate = raw.trim().toLowerCase(Locale.ROOT)).isEmpty() || !seen.add(candidate)) continue;
result.add(candidate);
}
return result;
}
private static boolean isGlassProtected(String name) {
return GLASS_BLACKLIST_LOOKUP.get().contains(name);
}
private static String generateWorldId(MinecraftServer server) {
try {
Path worldPath = server.m_129843_(LevelResource.f_78182_);
String worldName = server.m_129910_().m_5462_();
String folderName = worldPath.getFileName().toString();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: World path: {}", (Object)worldPath);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: World name from level.dat: '{}'", (Object)worldName);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Folder name: '{}'", (Object)folderName);
if (folderName == null || folderName.trim().isEmpty()) {
folderName = "world";
}
if (worldName == null || worldName.trim().isEmpty()) {
worldName = "Unknown";
}
String combined = folderName + "_" + worldName;
String result = combined.replaceAll("[^a-zA-Z0-9_-]", "_").toLowerCase();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Generated world ID: '{}'", (Object)result);
return result;
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to generate world ID, using fallback: {}", (Object)e.getMessage());
String fallback = "unknown_world_" + System.currentTimeMillis();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Using fallback ID: '{}'", (Object)fallback);
return fallback;
}
}
private static Path getSaveFilePath(MinecraftServer server) {
String worldId = BlockIndexManager.generateWorldId(server);
Path configDir = server.m_129843_(LevelResource.f_78182_).getParent().resolve("config").resolve("explosionoverhaul");
try {
Files.createDirectories(configDir, new FileAttribute[0]);
ExplosionOverhaul.LOGGER.debug("BlockIndexManager: Using config directory: {}", (Object)configDir);
}
catch (IOException e) {
ExplosionOverhaul.LOGGER.error("Failed to create config directory: {}", (Object)e.getMessage());
}
Path saveFile = configDir.resolve("block_index_" + worldId + ".json");
ExplosionOverhaul.LOGGER.debug("BlockIndexManager: Save file path: {}", (Object)saveFile);
return saveFile;
}
public static void saveIndexData(MinecraftServer server) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Attempting to save data...");
ExplosionOverhaul.LOGGER.info("BlockIndexManager: IndexByDimension size: {}", (Object)indexByDimension.size());
try {
Path saveFile = BlockIndexManager.getSaveFilePath(server);
if (!scanningComplete) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: Scan incomplete - deleting save file instead of saving corrupted data");
if (Files.exists(saveFile, new LinkOption[0])) {
Files.delete(saveFile);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Deleted incomplete save file: {}", (Object)saveFile);
}
return;
}
if (indexByDimension.isEmpty()) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: No data to save - index is empty!");
return;
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Saving to file: {}", (Object)saveFile);
HashMap saveData = new HashMap();
int totalBlocks = 0;
for (Map.Entry<ResourceKey<Level>, ConcurrentHashMap<Long, Set<Long>>> dimEntry : indexByDimension.entrySet()) {
String dimKey = dimEntry.getKey().m_135782_().toString();
HashMap<String, Set<Long>> chunkMap = new HashMap<String, Set<Long>>();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Processing dimension: {}", (Object)dimKey);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Chunks in dimension: {}", (Object)dimEntry.getValue().size());
for (Map.Entry<Long, Set<Long>> chunkEntry : dimEntry.getValue().entrySet()) {
Set<Long> blocks = chunkEntry.getValue();
if (blocks.isEmpty()) continue;
chunkMap.put(chunkEntry.getKey().toString(), blocks);
totalBlocks += blocks.size();
}
if (chunkMap.isEmpty()) continue;
saveData.put(dimKey, chunkMap);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Added {} chunks with blocks to save data for dimension {}", (Object)chunkMap.size(), (Object)dimKey);
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Total blocks to save: {}", (Object)totalBlocks);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Dimensions to save: {}", (Object)saveData.size());
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(saveData);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: JSON length: {}", (Object)json.length());
if (json.length() < 20) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: JSON too small ({} bytes) - skipping save to prevent corruption", (Object)json.length());
return;
}
Files.write(saveFile, json.getBytes(), new OpenOption[0]);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Successfully saved index data to {}", (Object)saveFile);
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.error("BlockIndexManager: Failed to save index data: {}", (Object)e.getMessage());
e.printStackTrace();
}
}
public static boolean loadIndexData(MinecraftServer server) {
try {
Type saveDataType;
Path saveFile = BlockIndexManager.getSaveFilePath(server);
if (!Files.exists(saveFile, new LinkOption[0])) {
ExplosionOverhaul.LOGGER.debug("BlockIndexManager: No save file found at {}", (Object)saveFile);
return false;
}
Gson gson = new Gson();
String json = new String(Files.readAllBytes(saveFile));
Map saveData = (Map)gson.fromJson(json, saveDataType = new TypeToken<Map<String, Map<String, Set<Long>>>>(){}.getType());
if (saveData == null || saveData.isEmpty()) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: Save file is empty or corrupted");
return false;
}
indexByDimension.clear();
for (Map.Entry dimEntry : saveData.entrySet()) {
try {
ResourceKey dimKey = ResourceKey.m_135785_((ResourceKey)Registries.f_256858_, (ResourceLocation)ResourceLocation.parse((String)((String)dimEntry.getKey())));
ConcurrentHashMap chunkMap = new ConcurrentHashMap();
for (Map.Entry chunkEntry : ((Map)dimEntry.getValue()).entrySet()) {
Long chunkKey = Long.parseLong((String)chunkEntry.getKey());
Set blockSet = Collections.newSetFromMap(new ConcurrentHashMap());
blockSet.addAll((Collection)chunkEntry.getValue());
chunkMap.put(chunkKey, blockSet);
}
indexByDimension.put((ResourceKey<Level>)dimKey, chunkMap);
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: Failed to load dimension {}: {}", dimEntry.getKey(), (Object)e.getMessage());
}
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Successfully loaded index data from {}", (Object)saveFile);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Loaded {} dimensions with indexed blocks", (Object)indexByDimension.size());
scanningComplete = true;
totalChunksToScan = 0;
chunksScanned = 0;
lampsFound = 0;
dripstonesFound = 0;
glassBlocksFound = 0;
return true;
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.error("BlockIndexManager: Failed to load index data: {}", (Object)e.getMessage());
e.printStackTrace();
return false;
}
}
public static boolean hasSaveFile(MinecraftServer server) {
Path saveFile = BlockIndexManager.getSaveFilePath(server);
return Files.exists(saveFile, new LinkOption[0]) && Files.isRegularFile(saveFile, new LinkOption[0]);
}
private static int getChunksToScanPerTick() {
int cpuPercent = (Integer)Config.COMMON.scan.cpuUsagePercent.get();
int baseChunks = 500;
return Math.max(1, baseChunks * cpuPercent / 100);
}
private static boolean isChunkScanningDisabled() {
return (Boolean)Config.COMMON.scan.enableBlockIndexing.get() == false;
}
private static boolean isScanning() {
return !chunkScanQueue.isEmpty() && totalChunksToScan > 0 && !scanningComplete;
}
public static boolean isRescanMode() {
return isRescanMode;
}
public static void register(ServerLevel level, BlockPos pos, BlockType type) {
ConcurrentHashMap<Long, Set<Long>> dimIndex = BlockIndexManager.getOrCreateDimIndex(level);
ChunkPos cp = new ChunkPos(pos);
long ck = BlockIndexManager.chunkKey(cp);
dimIndex.computeIfAbsent(ck, k -> Collections.newSetFromMap(new ConcurrentHashMap())).add(BlockIndexManager.packPos(pos));
}
public static void unregister(ServerLevel level, BlockPos pos, BlockType type) {
ChunkPos cp;
long ck;
ConcurrentHashMap<Long, Set<Long>> dimIndex = BlockIndexManager.getOrCreateDimIndex(level);
Set<Long> set = dimIndex.get(ck = BlockIndexManager.chunkKey(cp = new ChunkPos(pos)));
if (set != null) {
set.remove(BlockIndexManager.packPos(pos));
if (set.isEmpty()) {
dimIndex.remove(ck);
}
}
}
public static void scanChunk(ServerLevel level, ChunkPos cp) {
long ck;
ConcurrentHashMap<Long, Set<Long>> dimIndex = BlockIndexManager.getOrCreateDimIndex(level);
if (dimIndex.containsKey(ck = BlockIndexManager.chunkKey(cp))) {
return;
}
int baseX = cp.m_45604_();
int baseZ = cp.m_45605_();
Set set = Collections.newSetFromMap(new ConcurrentHashMap());
int chunkLamps = 0;
int chunkDripstones = 0;
int chunkGlass = 0;
for (int lx = 0; lx < 16; ++lx) {
for (int lz = 0; lz < 16; ++lz) {
for (int y = level.m_141937_(); y < level.m_151558_(); ++y) {
BlockPos pos = new BlockPos(baseX + lx, y, baseZ + lz);
if (!level.m_46749_(pos)) continue;
BlockState state = level.m_8055_(pos);
if (state.m_60713_(Blocks.f_50261_)) {
set.add(BlockIndexManager.packPos(pos));
++chunkLamps;
continue;
}
if (state.m_60713_(Blocks.f_152588_)) {
set.add(BlockIndexManager.packPos(pos));
++chunkDripstones;
continue;
}
if (!BlockIndexManager.isGlassBlock(state)) continue;
set.add(BlockIndexManager.packPos(pos));
++chunkGlass;
}
}
}
lampsFound += chunkLamps;
dripstonesFound += chunkDripstones;
glassBlocksFound += chunkGlass;
if (!set.isEmpty()) {
dimIndex.put(ck, set);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Found {} blocks in chunk {} {}: {}", new Object[]{set.size(), cp.f_45578_, cp.f_45579_, set.size() > 10 ? set.size() + " blocks" : set});
}
if (++chunksScanned >= totalChunksToScan && totalChunksToScan > 0) {
MinecraftServer server;
scanningComplete = true;
int totalBlocksFound = 0;
for (Map.Entry<ResourceKey<Level>, ConcurrentHashMap<Long, Set<Long>>> dimEntry : indexByDimension.entrySet()) {
for (Map.Entry<Long, Set<Long>> chunkEntry : dimEntry.getValue().entrySet()) {
totalBlocksFound += chunkEntry.getValue().size();
}
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: chunk scanning completed! Scanned {}/{} chunks", (Object)chunksScanned, (Object)totalChunksToScan);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Found {} total blocks across {} dimensions", (Object)totalBlocksFound, (Object)indexByDimension.size());
try {
server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: Starting auto-save after scan completion...");
BlockIndexManager.saveIndexData(server);
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to auto-save scan data: {}", (Object)e.getMessage());
e.printStackTrace();
}
try {
server = ServerLifecycleHooks.getCurrentServer();
if (server != null) {
server.execute(() -> BlockIndexManager.sendProgressUpdate());
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to enqueue final progress update: {}", (Object)e.getMessage());
}
}
if (chunksScanned % 10 == 0 || scanningComplete) {
BlockIndexManager.sendProgressUpdate();
}
}
public static void removeChunk(ServerLevel level, ChunkPos cp) {
ConcurrentHashMap<Long, Set<Long>> dimIndex = BlockIndexManager.getOrCreateDimIndex(level);
long ck = BlockIndexManager.chunkKey(cp);
dimIndex.remove(ck);
Set<Long> pending = BlockIndexManager.getOrCreatePendingSet(level);
pending.remove(ck);
}
public static List<BlockPos> getNearby(ServerLevel level, BlockPos center, int radius, BlockType type) {
ArrayList<BlockPos> result = new ArrayList<BlockPos>();
int minX = center.m_123341_() - radius;
int maxX = center.m_123341_() + radius;
int minZ = center.m_123343_() - radius;
int maxZ = center.m_123343_() + radius;
int minChunkX = minX >> 4;
int maxChunkX = maxX >> 4;
int minChunkZ = minZ >> 4;
int maxChunkZ = maxZ >> 4;
ConcurrentHashMap<Long, Set<Long>> dimIndex = BlockIndexManager.getOrCreateDimIndex(level);
long r2 = (long)radius * (long)radius;
for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
long ck = BlockIndexManager.chunkKey(new ChunkPos(cx, cz));
Set<Long> set = dimIndex.get(ck);
if (set == null || set.isEmpty()) continue;
for (long packed : set) {
long dz;
long dy;
BlockPos pos = BlockIndexManager.unpackPos(packed);
long dx = pos.m_123341_() - center.m_123341_();
if (dx * dx + (dy = (long)(pos.m_123342_() - center.m_123342_())) * dy + (dz = (long)(pos.m_123343_() - center.m_123343_())) * dz > r2) continue;
BlockState state = level.m_8055_(pos);
if (type == BlockType.LAMP && state.m_60713_(Blocks.f_50261_)) {
result.add(pos);
}
if (type == BlockType.DRIPSTONE && state.m_60713_(Blocks.f_152588_)) {
result.add(pos);
}
if (type != BlockType.GLASS || !BlockIndexManager.isGlassBlock(state)) continue;
result.add(pos);
}
}
}
return result;
}
@SubscribeEvent
public static void onChunkLoad(ChunkEvent.Load event) {
if (!ENABLED) {
return;
}
if (BlockIndexManager.isChunkScanningDisabled()) {
return;
}
LevelAccessor levelAccessor = event.getLevel();
if (!(levelAccessor instanceof ServerLevel)) {
return;
}
ServerLevel level = (ServerLevel)levelAccessor;
if (userDeclinedScan) {
return;
}
if (!isSingleplayer && !serverScanDecisionMade) {
return;
}
if (INITIAL_SCAN_ENQUEUED) {
return;
}
ResourceKey chunkDimension = level.m_46472_();
if (lastScannedDimension != null && !lastScannedDimension.equals((Object)chunkDimension)) {
return;
}
if (scanningComplete) {
return;
}
long ck = BlockIndexManager.chunkKey(event.getChunk().m_7697_());
Set<Long> pending = BlockIndexManager.getOrCreatePendingSet(level);
if (pending.add(ck)) {
chunkScanQueue.add(new ScanTask((ResourceKey<Level>)level.m_46472_(), event.getChunk().m_7697_()));
}
}
@SubscribeEvent
public static void onChunkUnload(ChunkEvent.Unload event) {
if (!ENABLED) {
return;
}
LevelAccessor levelAccessor = event.getLevel();
if (!(levelAccessor instanceof ServerLevel)) {
return;
}
ServerLevel level = (ServerLevel)levelAccessor;
BlockIndexManager.removeChunk(level, event.getChunk().m_7697_());
chunkScanQueue.removeIf(t -> t.dim().equals((Object)level.m_46472_()) && t.chunkPos().equals((Object)event.getChunk().m_7697_()));
}
@SubscribeEvent
public static void onBlockPlaced(BlockEvent.EntityPlaceEvent event) {
if (!ENABLED) {
return;
}
LevelAccessor levelAccessor = event.getLevel();
if (!(levelAccessor instanceof ServerLevel)) {
return;
}
ServerLevel level = (ServerLevel)levelAccessor;
BlockPos pos = event.getPos();
BlockState state = level.m_8055_(pos);
if (state.m_60713_(Blocks.f_50261_)) {
BlockIndexManager.register(level, pos, BlockType.LAMP);
} else if (state.m_60713_(Blocks.f_152588_)) {
BlockIndexManager.register(level, pos, BlockType.DRIPSTONE);
} else if (BlockIndexManager.isGlassBlock(state)) {
BlockIndexManager.register(level, pos, BlockType.GLASS);
}
}
@SubscribeEvent
public static void onBlockBroken(BlockEvent.BreakEvent event) {
if (!ENABLED) {
return;
}
LevelAccessor levelAccessor = event.getLevel();
if (!(levelAccessor instanceof ServerLevel)) {
return;
}
ServerLevel level = (ServerLevel)levelAccessor;
BlockPos pos = event.getPos();
BlockIndexManager.unregister(level, pos, BlockType.LAMP);
BlockIndexManager.unregister(level, pos, BlockType.DRIPSTONE);
BlockIndexManager.unregister(level, pos, BlockType.GLASS);
}
public static boolean isPlayerAuthorized(ServerPlayer player) {
if (player == null) {
return false;
}
return player.m_20310_(2) || isSingleplayer;
}
@SubscribeEvent
public static void onPlayerJoinServer(PlayerEvent.PlayerLoggedInEvent event) {
if (!ENABLED) {
return;
}
if (BlockIndexManager.isChunkScanningDisabled()) {
return;
}
Player player = event.getEntity();
if (!(player instanceof ServerPlayer)) {
return;
}
ServerPlayer player2 = (ServerPlayer)player;
if (BlockIndexManager.isPlayerAuthorized(player2)) {
ServerLevel level = player2.m_284548_();
MinecraftServer server = level.m_7654_();
boolean saveFileExists = BlockIndexManager.hasSaveFile(server);
if (isSingleplayer) {
if (!(waitingForUserInput || waitingForLoadDecision || scanningComplete)) {
chunkScanQueue.clear();
totalChunksToScan = 0;
chunksScanned = 0;
}
if (!(waitingForUserInput || waitingForLoadDecision || BlockIndexManager.isScanning() || scanningComplete || userDeclinedScan)) {
pendingScanLevel = level;
if (saveFileExists) {
waitingForLoadDecision = true;
hasSaveFile = true;
try {
ScanLoadPromptPacket packet = new ScanLoadPromptPacket(true);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player2), (Object)packet);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: showing load prompt for existing save file in world: {}", (Object)level.m_46472_().m_135782_());
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send load prompt: {}", (Object)e.getMessage());
}
} else {
waitingForUserInput = true;
hasSaveFile = false;
BlockIndexManager.sendScanPromptPacket(true);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: waiting for user input to start scan in world: {}", (Object)level.m_46472_().m_135782_());
}
} else if (BlockIndexManager.isScanning() || !chunkScanQueue.isEmpty() && totalChunksToScan > 0) {
try {
ScanProgressPacket packet = new ScanProgressPacket(totalChunksToScan, chunksScanned, scanningComplete, lampsFound, dripstonesFound, glassBlocksFound);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player2), (Object)packet);
ExplosionOverhaul.LOGGER.debug("Sent scan progress to rejoining OP player: {}/{} chunks", (Object)chunksScanned, (Object)totalChunksToScan);
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send scan progress to rejoining OP player: {}", (Object)e.getMessage());
}
}
} else if (!serverScanDecisionMade && !userDeclinedScan) {
if (!serverScanPromptShown) {
serverScanPromptShown = true;
ExplosionOverhaul.LOGGER.info("BlockIndexManager: first OP joined server, waiting for scan decision from: {}", (Object)player2.m_7755_().getString());
} else {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: additional OP joined server, showing pending decision to: {}", (Object)player2.m_7755_().getString());
}
pendingScanLevel = level;
if (saveFileExists) {
waitingForLoadDecision = true;
hasSaveFile = true;
try {
ScanLoadPromptPacket packet = new ScanLoadPromptPacket(true);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player2), (Object)packet);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: showing load prompt for existing save file to OP: {}", (Object)player2.m_7755_().getString());
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send load prompt: {}", (Object)e.getMessage());
}
} else {
waitingForUserInput = true;
hasSaveFile = false;
try {
ScanPromptPacket packet = new ScanPromptPacket(true);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player2), (Object)packet);
ExplosionOverhaul.LOGGER.info("BlockIndexManager: showing scan prompt to OP: {}", (Object)player2.m_7755_().getString());
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send scan prompt: {}", (Object)e.getMessage());
}
}
} else if (totalChunksToScan > 0 && !scanningComplete) {
try {
ScanProgressPacket packet = new ScanProgressPacket(totalChunksToScan, chunksScanned, scanningComplete, lampsFound, dripstonesFound, glassBlocksFound);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player2), (Object)packet);
ExplosionOverhaul.LOGGER.debug("Sent scan progress to joining OP player: {}/{} chunks", (Object)chunksScanned, (Object)totalChunksToScan);
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to send scan progress to joining OP player: {}", (Object)e.getMessage());
}
}
}
}
@SubscribeEvent
public static void onServerTick(TickEvent.ServerTickEvent event) {
ScanTask task;
if (event.phase != TickEvent.Phase.END) {
return;
}
if (BlockIndexManager.isChunkScanningDisabled()) {
return;
}
if (userDeclinedScan) {
return;
}
if (!isSingleplayer && !serverScanDecisionMade) {
return;
}
int chunksToScan = BlockIndexManager.getChunksToScanPerTick();
for (int processed = 0; processed < chunksToScan && (task = chunkScanQueue.poll()) != null; ++processed) {
MinecraftServer server = event.getServer();
ServerLevel level = server.m_129880_(task.dim());
if (level == null) continue;
try {
BlockIndexManager.scanChunk(level, task.chunkPos());
long ck = BlockIndexManager.chunkKey(task.chunkPos());
Set<Long> pending = BlockIndexManager.getOrCreatePendingSet(level);
pending.remove(ck);
continue;
}
catch (Exception ex) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: error scanning chunk {} {}: {}", new Object[]{task.chunkPos().f_45578_, task.chunkPos().f_45579_, ex.toString()});
}
}
}
@SubscribeEvent
public static void onServerStarted(ServerStartedEvent event) {
currentServer = event.getServer();
abortCurrentScan = false;
if (!ENABLED) {
return;
}
if (BlockIndexManager.isChunkScanningDisabled()) {
return;
}
if (INITIAL_SCAN_ENQUEUED) {
return;
}
MinecraftServer server = event.getServer();
isSingleplayer = server.m_129792_();
if (!isSingleplayer) {
userDeclinedScan = false;
}
serverScanPromptShown = false;
serverScanDecisionMade = false;
if (isSingleplayer) {
ExplosionOverhaul.LOGGER.info("BlockIndexManager: singleplayer detected, will scan on world entry");
INITIAL_SCAN_ENQUEUED = true;
return;
}
ExplosionOverhaul.LOGGER.info("BlockIndexManager: multiplayer server detected, will wait for user decision before scanning");
INITIAL_SCAN_ENQUEUED = true;
}
private static void scanExistingChunksOnServer(MinecraftServer server) {
try {
int totalChunks;
chunksScanned = 0;
scanningComplete = false;
lampsFound = 0;
dripstonesFound = 0;
glassBlocksFound = 0;
chunkScanQueue.clear();
pendingChunkKeys.clear();
HyperScanner scanner = new HyperScanner(server);
totalChunksToScan = totalChunks = scanner.prepareTasks();
INITIAL_SCAN_ENQUEUED = true;
BlockIndexManager.sendProgressUpdate();
CompletableFuture.runAsync(() -> {
try {
scanner.run();
chunksScanned = scanner.getProcessedChunks();
lampsFound = scanner.getLampsFound();
dripstonesFound = scanner.getDripstonesFound();
glassBlocksFound = scanner.getGlassFound();
scanningComplete = true;
server.execute(() -> {
BlockIndexManager.sendProgressUpdate();
BlockIndexManager.saveIndexDataAfterScan(server);
ExplosionOverhaul.LOGGER.info("HyperScanner: background scan complete. Found {} lamps, {} dripstones, {} glass.", new Object[]{lampsFound, dripstonesFound, glassBlocksFound});
});
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.error("HyperScanner: background scan failed", (Throwable)e);
}
});
}
catch (Exception ex) {
ExplosionOverhaul.LOGGER.warn("BlockIndexManager: failed to start scan: {}", (Object)ex.toString());
}
}
@SubscribeEvent
public static void onServerStopped(ServerStoppedEvent event) {
if (!ENABLED) {
return;
}
abortCurrentScan = true;
if (!indexByDimension.isEmpty() && scanningComplete) {
try {
BlockIndexManager.saveIndexData(event.getServer());
ExplosionOverhaul.LOGGER.info("BlockIndexManager: saved data before server shutdown");
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("Failed to save data on server stop: {}", (Object)e.getMessage());
}
}
userDeclinedScan = false;
serverScanPromptShown = false;
serverScanDecisionMade = false;
waitingForUserInput = false;
waitingForLoadDecision = false;
pendingScanLevel = null;
isRescanMode = false;
rescanRequestingPlayer = null;
hasSaveFile = false;
currentWorldId = null;
INITIAL_SCAN_ENQUEUED = false;
try {
ScanProgressPacket packet = new ScanProgressPacket(0, 0, true, 0, 0, 0);
PacketHandler.INSTANCE.send(PacketDistributor.ALL.noArg(), (Object)packet);
}
catch (Exception exception) {
// empty catch block
}
currentServer = null;
scanningComplete = false;
totalChunksToScan = 0;
chunksScanned = 0;
ExplosionOverhaul.LOGGER.info("BlockIndexManager: reset scan state and aborted background tasks on server stop");
}
@SubscribeEvent
public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) {
Player player;
if (!ENABLED) {
return;
}
if (isSingleplayer) {
abortCurrentScan = true;
waitingForUserInput = false;
waitingForLoadDecision = false;
pendingScanLevel = null;
userDeclinedScan = false;
hasSaveFile = false;
scanningComplete = false;
totalChunksToScan = 0;
chunksScanned = 0;
BlockIndexManager.clearAndSaveData();
ExplosionOverhaul.LOGGER.info("BlockIndexManager: saved and cleared scan data on singleplayer logout");
}
if ((player = event.getEntity()) instanceof ServerPlayer) {
ServerPlayer player2 = (ServerPlayer)player;
try {
ScanProgressPacket packet = new ScanProgressPacket(0, 0, true, 0, 0, 0);
PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player2), (Object)packet);
}
catch (Exception exception) {
// empty catch block
}
}
}
private static Path resolveRegionPath(ServerLevel lvl) {
try {
Path worldPath = lvl.m_7654_().m_129843_(LevelResource.f_78182_);
String dimPath = lvl.m_46472_().m_135782_().m_135815_();
if ("overworld".equals(dimPath)) {
return worldPath.resolve("region");
}
if ("the_nether".equals(dimPath)) {
return worldPath.resolve("DIM-1").resolve("region");
}
if ("the_end".equals(dimPath)) {
return worldPath.resolve("DIM1").resolve("region");
}
return worldPath.resolve("dimensions").resolve(lvl.m_46472_().m_135782_().m_135827_()).resolve(lvl.m_46472_().m_135782_().m_135815_()).resolve("region");
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("HyperScanner: failed to resolve region path: {}", (Object)e.getMessage());
return null;
}
}
private static /* synthetic */ ServerPlayer lambda$loadExistingData$11(ServerPlayer serverPlayer) {
return serverPlayer;
}
static {
indexByDimension = new ConcurrentHashMap<ResourceKey<Level>, ConcurrentHashMap<Long, Set<Long>>>();
chunkScanQueue = new ConcurrentLinkedQueue();
pendingChunkKeys = new ConcurrentHashMap<ResourceKey<Level>, Set<Long>>();
ENABLED = true;
INITIAL_SCAN_ENQUEUED = false;
totalChunksToScan = 0;
chunksScanned = 0;
scanningComplete = false;
abortCurrentScan = false;
lampsFound = 0;
dripstonesFound = 0;
glassBlocksFound = 0;
lastScannedDimension = null;
waitingForUserInput = false;
pendingScanLevel = null;
currentScannedWorld = null;
isSingleplayer = false;
serverScanPromptShown = false;
serverScanDecisionMade = false;
isRescanMode = false;
rescanRequestingPlayer = null;
userDeclinedScan = false;
waitingForLoadDecision = false;
hasSaveFile = false;
currentWorldId = null;
DEFAULT_REINFORCED_GLASS_BLACKLIST = List.of("securitycraft:reinforced_white_stained_glass", "securitycraft:reinforced_orange_stained_glass", "securitycraft:reinforced_magenta_stained_glass", "securitycraft:reinforced_light_blue_stained_glass", "securitycraft:reinforced_yellow_stained_glass", "securitycraft:reinforced_lime_stained_glass", "securitycraft:reinforced_pink_stained_glass", "securitycraft:reinforced_gray_stained_glass", "securitycraft:reinforced_light_gray_stained_glass", "securitycraft:reinforced_cyan_stained_glass", "securitycraft:reinforced_purple_stained_glass", "securitycraft:reinforced_blue_stained_glass", "securitycraft:reinforced_brown_stained_glass", "securitycraft:reinforced_green_stained_glass", "securitycraft:reinforced_red_stained_glass", "securitycraft:reinforced_black_stained_glass", "securitycraft:reinforced_white_stained_glass_pane", "securitycraft:reinforced_orange_stained_glass_pane", "securitycraft:reinforced_magenta_stained_glass_pane", "securitycraft:reinforced_light_blue_stained_glass_pane", "securitycraft:reinforced_yellow_stained_glass_pane", "securitycraft:reinforced_lime_stained_glass_pane", "securitycraft:reinforced_pink_stained_glass_pane", "securitycraft:reinforced_gray_stained_glass_pane", "securitycraft:reinforced_light_gray_stained_glass_pane", "securitycraft:reinforced_cyan_stained_glass_pane", "securitycraft:reinforced_purple_stained_glass_pane", "securitycraft:reinforced_blue_stained_glass_pane", "securitycraft:reinforced_brown_stained_glass_pane", "securitycraft:reinforced_green_stained_glass_pane", "securitycraft:reinforced_red_stained_glass_pane", "securitycraft:reinforced_black_stained_glass_pane", "securitycraft:reinforced_glass", "securitycraft:reinforced_glass_pane", "create_tank_defenses:titanium_morning_reinforced_blast_glass", "mofus_better_end_:reinforced_glass", "crusty_chunks:reinforced_glass", "createnuclear:reinforced_glass", "mekanismgenerators:reactor_glass", "mekanism:structural_glass");
MOD_CONFIG_DIRECTORY = Paths.get("config", "explosionoverhaul");
GLASS_BLACKLIST_FILE = MOD_CONFIG_DIRECTORY.resolve("GlassBlacklist.json");
GLASS_BLACKLIST = new CopyOnWriteArrayList<String>(DEFAULT_REINFORCED_GLASS_BLACKLIST);
GLASS_BLACKLIST_LOOKUP = new AtomicReference<HashSet<String>>(new HashSet<String>(DEFAULT_REINFORCED_GLASS_BLACKLIST));
BlockIndexManager.loadGlassBlacklistFromJson();
}
private record ScanTask(ResourceKey<Level> dim, ChunkPos chunkPos) {
}
public static enum BlockType {
LAMP,
DRIPSTONE,
GLASS;
}
private static class HyperScanner {
private static final int SECTOR_BYTES = 4096;
private final MinecraftServer server;
private final ConcurrentLinkedQueue<RegionTask> tasks = new ConcurrentLinkedQueue();
private final AtomicInteger processedChunks = new AtomicInteger();
private final LongAdder lamps = new LongAdder();
private final LongAdder drips = new LongAdder();
private final LongAdder glass = new LongAdder();
private final int workerCount;
HyperScanner(MinecraftServer server) {
this.server = server;
this.workerCount = this.resolveWorkerCount();
}
int prepareTasks() {
int total = 0;
for (ServerLevel level : this.server.m_129785_()) {
Path regionPath = BlockIndexManager.resolveRegionPath(level);
if (regionPath == null || !Files.exists(regionPath, new LinkOption[0])) {
ExplosionOverhaul.LOGGER.info("HyperScanner: region path not found for {}", (Object)level.m_46472_().m_135782_());
continue;
}
try {
Stream<Path> regionFiles = Files.list(regionPath);
try {
for (Path regionFile : regionFiles.filter(p -> p.toString().endsWith(".mca")).toList()) {
RegionInfo info = this.parseRegion(regionFile);
if (info == null || info.chunkCount <= 0) continue;
this.tasks.add(new RegionTask(level, regionFile, info.regionX, info.regionZ));
total += info.chunkCount;
}
}
finally {
if (regionFiles == null) continue;
regionFiles.close();
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("HyperScanner: failed to enumerate region files in {}: {}", (Object)regionPath, (Object)e.getMessage());
}
}
ExplosionOverhaul.LOGGER.info("HyperScanner: prepared {} region tasks, total chunks {}", (Object)this.tasks.size(), (Object)total);
return total;
}
void run() {
if (this.tasks.isEmpty()) {
ExplosionOverhaul.LOGGER.info("HyperScanner: no tasks to run");
return;
}
ExecutorService pool = Executors.newFixedThreadPool(this.workerCount, r -> {
Thread t = new Thread(r, "EO-HyperScanner");
t.setDaemon(true);
return t;
});
for (int i = 0; i < this.workerCount; ++i) {
pool.execute(this::workerLoop);
}
pool.shutdown();
try {
while (!pool.isTerminated()) {
pool.awaitTermination(1L, TimeUnit.SECONDS);
}
}
catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
int getProcessedChunks() {
return this.processedChunks.get();
}
int getLampsFound() {
return this.lamps.intValue();
}
int getDripstonesFound() {
return this.drips.intValue();
}
int getGlassFound() {
return this.glass.intValue();
}
private void workerLoop() {
RegionTask task;
while (!abortCurrentScan && (task = this.tasks.poll()) != null) {
this.scanRegion(task);
}
}
private void scanRegion(RegionTask task) {
try (RandomAccessFile raf = new RandomAccessFile(task.file().toFile(), "r");){
byte[] header = new byte[4096];
int read = raf.read(header);
if (read < 4096) {
return;
}
for (int i = 0; i < 1024; ++i) {
if (abortCurrentScan) {
return;
}
int offsetIndex = i * 4;
int chunkOffset = (header[offsetIndex] & 0xFF) << 16 | (header[offsetIndex + 1] & 0xFF) << 8 | header[offsetIndex + 2] & 0xFF;
int sectorCount = header[offsetIndex + 3] & 0xFF;
if (chunkOffset == 0 || sectorCount == 0) continue;
int localX = i % 32;
int localZ = i / 32;
int chunkX = task.regionX() * 32 + localX;
int chunkZ = task.regionZ() * 32 + localZ;
try {
this.processChunk(raf, chunkOffset, sectorCount, new ChunkPos(chunkX, chunkZ), task.level());
continue;
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("HyperScanner: failed to process chunk {} {} in {}: {}", new Object[]{chunkX, chunkZ, task.file().getFileName(), e.getMessage()});
}
}
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("HyperScanner: failed to scan region {}: {}", (Object)task.file().getFileName(), (Object)e.getMessage());
}
}
private void processChunk(RandomAccessFile raf, int chunkOffsetSectors, int sectorCount, ChunkPos chunkPos, ServerLevel level) throws IOException {
InflaterInputStream in;
long filePos = (long)chunkOffsetSectors * 4096L;
raf.seek(filePos);
int length = raf.readInt();
if (length <= 0 || length > sectorCount * 4096) {
return;
}
int compressionType = raf.readUnsignedByte();
byte[] data = new byte[length - 1];
raf.readFully(data);
ByteArrayInputStream base = new ByteArrayInputStream(data);
if (compressionType == 2) {
in = new InflaterInputStream(base);
} else if (compressionType == 1) {
in = new GZIPInputStream(base);
} else {
return;
}
CompoundTag root = NbtIo.m_128928_((DataInput)new DataInputStream(in));
if (root == null) {
return;
}
CompoundTag levelTag = root.m_128425_("Level", 10) ? root.m_128469_("Level") : (root.m_128425_("level", 10) ? root.m_128469_("level") : root);
ListTag sections = levelTag.m_128437_("sections", 10);
HashSet<Long> found = new HashSet<Long>();
for (int i = 0; i < sections.size(); ++i) {
CompoundTag section = sections.m_128728_(i);
this.processSection(section, chunkPos, level, found);
}
if (!found.isEmpty()) {
ConcurrentHashMap<Long, Set<Long>> dimIndex = BlockIndexManager.getOrCreateDimIndex(level);
dimIndex.put(BlockIndexManager.chunkKey(chunkPos), found);
}
if (abortCurrentScan) {
return;
}
int processed = this.processedChunks.incrementAndGet();
this.maybeSendProgress(processed);
}
private void processSection(CompoundTag section, ChunkPos chunkPos, ServerLevel level, Set<Long> out) {
if (!section.m_128425_("block_states", 10)) {
return;
}
CompoundTag blockStatesTag = section.m_128469_("block_states");
if (blockStatesTag.m_128425_("palette", 9)) {
ListTag palette = blockStatesTag.m_128437_("palette", 10);
boolean hasInterestingBlock = false;
for (int i = 0; i < palette.size(); ++i) {
String name = palette.m_128728_(i).m_128461_("Name");
if (!name.equals("minecraft:redstone_lamp") && !name.equals("minecraft:pointed_dripstone") && (!name.contains("glass") || BlockIndexManager.isGlassProtected(name))) continue;
hasInterestingBlock = true;
break;
}
if (!hasInterestingBlock) {
return;
}
}
try {
DataResult result = PalettedContainer.m_238371_((IdMap)Block.f_49791_, (Codec)BlockState.f_61039_, (PalettedContainer.Strategy)PalettedContainer.Strategy.f_188137_, (Object)Blocks.f_50016_.m_49966_()).parse((DynamicOps)NbtOps.f_128958_, (Object)blockStatesTag);
result.resultOrPartial(err -> {}).ifPresent(container -> {
byte sectionY = section.m_128445_("Y");
int baseY = sectionY * 16;
if (baseY < level.m_141937_() || baseY >= level.m_151558_()) {
return;
}
IdentityHashMap<BlockState, Byte> stateTypeCache = new IdentityHashMap<BlockState, Byte>();
for (int y = 0; y < 16; ++y) {
int worldY = baseY + y;
if (worldY < level.m_141937_() || worldY >= level.m_151558_()) continue;
for (int z = 0; z < 16; ++z) {
for (int x = 0; x < 16; ++x) {
BlockState state = (BlockState)container.m_63087_(x, y, z);
Byte type = (Byte)stateTypeCache.get(state);
if (type == null) {
type = state.m_60713_(Blocks.f_50261_) ? Byte.valueOf((byte)1) : (state.m_60713_(Blocks.f_152588_) ? Byte.valueOf((byte)2) : (BlockIndexManager.isGlassBlock(state) ? Byte.valueOf((byte)3) : Byte.valueOf((byte)0)));
stateTypeCache.put(state, type);
}
if (type == 0) continue;
int worldX = chunkPos.m_45604_() + x;
int worldZ = chunkPos.m_45605_() + z;
out.add(BlockPos.m_121882_((int)worldX, (int)worldY, (int)worldZ));
if (type == 1) {
this.lamps.increment();
continue;
}
if (type == 2) {
this.drips.increment();
continue;
}
if (type != 3) continue;
this.glass.increment();
}
}
}
});
}
catch (Exception exception) {
// empty catch block
}
}
private void maybeSendProgress(int processed) {
if (processed % 50 == 0 || processed == totalChunksToScan) {
int l = this.lamps.intValue();
int d = this.drips.intValue();
int g = this.glass.intValue();
this.server.execute(() -> BlockIndexManager.sendMultiThreadProgressUpdate(processed, l, d, g));
}
}
private RegionInfo parseRegion(Path regionFile) {
int regionZ;
int regionX;
String fileName = regionFile.getFileName().toString();
if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
return null;
}
String[] parts = fileName.substring(2, fileName.length() - 4).split("\\.");
if (parts.length != 2) {
return null;
}
try {
regionX = Integer.parseInt(parts[0]);
regionZ = Integer.parseInt(parts[1]);
}
catch (NumberFormatException nfe) {
return null;
}
int chunkCount = this.countChunksInRegion(regionFile);
return new RegionInfo(regionX, regionZ, chunkCount);
}
/*
* Enabled aggressive block sorting
* Enabled unnecessary exception pruning
* Enabled aggressive exception aggregation
*/
private int countChunksInRegion(Path regionFile) {
int count = 0;
try (RandomAccessFile raf = new RandomAccessFile(regionFile.toFile(), "r");){
byte[] header = new byte[4096];
int read = raf.read(header);
if (read < 4096) {
int n = 0;
return n;
}
int i = 0;
while (i < 1024) {
int offset = i * 4;
int chunkOffset = (header[offset] & 0xFF) << 16 | (header[offset + 1] & 0xFF) << 8 | header[offset + 2] & 0xFF;
int sectorCount = header[offset + 3] & 0xFF;
if (chunkOffset > 0 && sectorCount > 0) {
++count;
}
++i;
}
return count;
}
catch (Exception e) {
ExplosionOverhaul.LOGGER.warn("HyperScanner: failed to read region header {}: {}", (Object)regionFile.getFileName(), (Object)e.getMessage());
}
return count;
}
private int resolveWorkerCount() {
int configured = (Integer)Config.COMMON.scan.maxScanThreads.get();
int cores = Runtime.getRuntime().availableProcessors();
if (configured <= 0) {
return Math.max(1, cores);
}
return Math.max(1, Math.min(configured, cores));
}
}
private record RegionInfo(int regionX, int regionZ, int chunkCount) {
}
private record RegionTask(ServerLevel level, Path file, int regionX, int regionZ) {
}
}