From 68e3ecb4b5f748169a25d1cf774bcbbbf568914d Mon Sep 17 00:00:00 2001 From: rfresh2 <89827146+rfresh2@users.noreply.github.com> Date: Mon, 24 Jul 2023 20:48:57 -0700 Subject: [PATCH] Portals Highlighting feature --- README.md | 2 +- build.gradle | 2 +- .../java/xaeroplus/event/ChunkDataEvent.java | 22 ++ .../xaeroplus/mixin/client/MixinGuiMap.java | 14 ++ .../client/MixinSupportXaeroWorldmap.java | 16 ++ .../client/mc/MixinNetHandlerPlayClient.java | 23 ++ .../java/xaeroplus/module/impl/NewChunks.java | 5 +- .../module/impl/PortalSkipDetection.java | 124 ++++------ .../java/xaeroplus/module/impl/Portals.java | 167 +++++++++++++ .../settings/XaeroPlusSettingRegistry.java | 28 ++- src/main/java/xaeroplus/util/ChunkUtils.java | 4 + src/main/java/xaeroplus/util/WDLHelper.java | 2 + .../ChunkHighlightBaseCacheHandler.java} | 85 ++++--- .../ChunkHighlightCacheDimensionHandler.java} | 47 ++-- .../ChunkHighlightData.java} | 10 +- .../ChunkHighlightDatabase.java} | 60 +++-- .../highlights/ChunkHighlightLocalCache.java | 63 +++++ .../highlights/ChunkHighlightSavingCache.java | 219 ++++++++++++++++++ .../{ => highlights}/HighlightAtChunkPos.java | 2 +- .../{ => highlights}/RegionRenderPos.java | 2 +- .../util/newchunks/NewChunksCache.java | 3 +- .../util/newchunks/NewChunksLocalCache.java | 61 ++--- .../util/newchunks/NewChunksSavingCache.java | 191 ++------------- .../assets/xaeroplus/lang/en_us.lang | 6 + src/main/resources/mixins.xaeroplus.json | 1 + 25 files changed, 778 insertions(+), 381 deletions(-) create mode 100644 src/main/java/xaeroplus/event/ChunkDataEvent.java create mode 100644 src/main/java/xaeroplus/mixin/client/mc/MixinNetHandlerPlayClient.java create mode 100644 src/main/java/xaeroplus/module/impl/Portals.java rename src/main/java/xaeroplus/util/{newchunks/NewChunksBaseCacheHandler.java => highlights/ChunkHighlightBaseCacheHandler.java} (54%) rename src/main/java/xaeroplus/util/{newchunks/NewChunksSavingCacheDimensionHandler.java => highlights/ChunkHighlightCacheDimensionHandler.java} (67%) rename src/main/java/xaeroplus/util/{newchunks/NewChunkData.java => highlights/ChunkHighlightData.java} (61%) rename src/main/java/xaeroplus/util/{newchunks/NewChunksDatabase.java => highlights/ChunkHighlightDatabase.java} (56%) create mode 100644 src/main/java/xaeroplus/util/highlights/ChunkHighlightLocalCache.java create mode 100644 src/main/java/xaeroplus/util/highlights/ChunkHighlightSavingCache.java rename src/main/java/xaeroplus/util/{ => highlights}/HighlightAtChunkPos.java (94%) rename src/main/java/xaeroplus/util/{ => highlights}/RegionRenderPos.java (95%) diff --git a/README.md b/README.md index d5034ec7..83454fe5 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,13 @@ Or a modpack zip with these jars is included in the Github releases. * [Adjustable minimap scaling that increases how many chunks are visible](https://cdn.discordapp.com/attachments/971140948593635335/1109734742842146937/Temurin-1.8.0_352_2023.03.29_-_16.16.08.32.DVR.mp4) * [NewChunks Highlighting in MiniMap and WorldMap.](https://cdn.discordapp.com/attachments/971140948593635335/1109735633045434408/Base_Profile_2023.01.02_-_11.26.22.02.DVR.mp4) * [WorldDownloader 4.1.1.0](https://github.com/Pokechu22/WorldDownloader/) integration (does not work with Future's Forge WDL jar) - * [Highlights chunks as they are downloaded in the Minimap and WorldMap.](https://cdn.discordapp.com/attachments/971140948593635335/1109735287006961705/Temurin-1.8.0_352_2023.01.02_-_18.54.28.04.DVR.mp4) * [Baritone](https://github.com/cabaletta/baritone) integration * Baritone Goals synced as temporary waypoints * [Point and Click Travel](https://cdn.discordapp.com/attachments/1005598555186139156/1125306712300204082/Base_Profile_2023.07.02_-_23.04.34.09.DVR.mp4) * [Waystones](https://legacy.curseforge.com/minecraft/mc-mods/waystones) integration * Syncs Waystones as temporary waypoints * [Portal Skip Highlighting in Minimap and WorldMap](https://cdn.discordapp.com/attachments/1029572347818151947/1109656254265163816/Base_Profile_2023.05.20_-_18.34.34.34.DVR.mp4). Detects chunks where a portal could have been loaded. +* [Portal Highlighting in Minimap and WorldMap.](https://cdn.discordapp.com/attachments/1127463054804779028/1133251771217752175/Base_Profile_2023.07.24_-_21.02.36.03.mp4) * [Transparent minimap background instead of wasted black screen space.](https://cdn.discordapp.com/attachments/963821382569979904/1088651890335686716/2023-03-23_19.26.36.png) * [Fast map region writes](https://cdn.discordapp.com/attachments/963821382569979904/1049947847467995196/Temurin-1.8.0_345_2022.12.06_-_22.44.28.05.DVR.mp4). Prevent missed chunks in map while traveling at very high speeds. * Allow multiple MC instances to read/write to the same map concurrently diff --git a/build.gradle b/build.gradle index 31cf2e6b..cefbee69 100644 --- a/build.gradle +++ b/build.gradle @@ -118,7 +118,7 @@ dependencies { implementation(fg.deobf("maven.modrinth:xaeros-world-map:1.31.0_Forge_1.12")) implementation(fg.deobf("maven.modrinth:xaeros-minimap:23.6.0_Forge_1.12")) implementation(fg.deobf('cabaletta:baritone-deobf-unoptimized-mcp-dev:1.2')).setChanging(true) - implementation(fg.deobf("curse.maven:waystones-245755:2859589")) + compileOnly(fg.deobf("curse.maven:waystones-245755:2859589")) annotationProcessor('org.spongepowered:mixin:0.8.5:processor') { exclude module: 'gson' diff --git a/src/main/java/xaeroplus/event/ChunkDataEvent.java b/src/main/java/xaeroplus/event/ChunkDataEvent.java new file mode 100644 index 00000000..b3663bfd --- /dev/null +++ b/src/main/java/xaeroplus/event/ChunkDataEvent.java @@ -0,0 +1,22 @@ +package xaeroplus.event; + +import net.minecraft.world.chunk.Chunk; +import net.minecraftforge.fml.common.eventhandler.Event; + +public class ChunkDataEvent extends Event { + private final boolean isFullChunk; + private final Chunk chunk; + + public ChunkDataEvent(final boolean isFullChunk, final Chunk chunk) { + this.isFullChunk = isFullChunk; + this.chunk = chunk; + } + + public boolean isFullChunk() { + return this.isFullChunk; + } + + public Chunk getChunk() { + return this.chunk; + } +} diff --git a/src/main/java/xaeroplus/mixin/client/MixinGuiMap.java b/src/main/java/xaeroplus/mixin/client/MixinGuiMap.java index 8daf35b3..93c7a7ae 100644 --- a/src/main/java/xaeroplus/mixin/client/MixinGuiMap.java +++ b/src/main/java/xaeroplus/mixin/client/MixinGuiMap.java @@ -53,8 +53,10 @@ import xaeroplus.module.ModuleManager; import xaeroplus.module.impl.NewChunks; import xaeroplus.module.impl.PortalSkipDetection; +import xaeroplus.module.impl.Portals; import xaeroplus.settings.XaeroPlusSettingRegistry; import xaeroplus.util.*; +import xaeroplus.util.highlights.HighlightAtChunkPos; import java.io.IOException; import java.util.ArrayList; @@ -1101,6 +1103,18 @@ public void customDrawScreen(int scaledMouseX, int scaledMouseY, float partialTi } GuiHelper.drawRectList(rects, portalSkipDetection.getPortalSkipChunksColor()); } + if (XaeroPlusSettingRegistry.portalsEnabledSetting.getValue() && !mc.gameSettings.hideGUI) { + final List rects = new ArrayList<>(32); + final Portals portals = ModuleManager.getModule(Portals.class); + for (final HighlightAtChunkPos c : portals.getPortalsInRegion(leafRegionMinX, leafRegionMinZ, leveledSideInRegions, Shared.customDimensionId)) { + final float left = (float) ((c.x << 4) - flooredCameraX); + final float top = (float) ((c.z << 4) - flooredCameraZ); + final float right = left + 16; + final float bottom = top + 16; + rects.add(new GuiHelper.Rect(left, top, right, bottom)); + } + GuiHelper.drawRectList(rects, portals.getPortalsColor()); + } if (XaeroPlusSettingRegistry.wdlEnabledSetting.getValue() && !mc.gameSettings.hideGUI && WDLHelper.isWdlPresent() diff --git a/src/main/java/xaeroplus/mixin/client/MixinSupportXaeroWorldmap.java b/src/main/java/xaeroplus/mixin/client/MixinSupportXaeroWorldmap.java index 8b9e6c3b..fd02eb2c 100644 --- a/src/main/java/xaeroplus/mixin/client/MixinSupportXaeroWorldmap.java +++ b/src/main/java/xaeroplus/mixin/client/MixinSupportXaeroWorldmap.java @@ -31,6 +31,7 @@ import xaeroplus.module.ModuleManager; import xaeroplus.module.impl.NewChunks; import xaeroplus.module.impl.PortalSkipDetection; +import xaeroplus.module.impl.Portals; import xaeroplus.settings.XaeroPlusSettingRegistry; import xaeroplus.util.*; @@ -343,6 +344,21 @@ public void drawMinimap(XaeroMinimapSession minimapSession, MinimapRendererHelpe } GuiHelper.drawRectList(rects, ModuleManager.getModule(PortalSkipDetection.class).getPortalSkipChunksColor()); } + if (XaeroPlusSettingRegistry.portalsEnabledSetting.getValue()) { + final List rects = new ArrayList<>(32); + for (int t = 0; t < 16; ++t) { + final int chunkPosX = chunk.getX() * 4 + t % 4; + final int chunkPosZ = chunk.getZ() * 4 + t / 4; + if (ModuleManager.getModule(Portals.class).isPortalChunk(chunkPosX, chunkPosZ, Shared.customDimensionId)) { + final float left = drawX + 16 * (t % 4); + final float top = drawZ + 16 * (t / 4); + final float right = left + 16; + final float bottom = top + 16; + rects.add(new GuiHelper.Rect(left, top, right, bottom)); + } + } + GuiHelper.drawRectList(rects, ModuleManager.getModule(Portals.class).getPortalsColor()); + } if (XaeroPlusSettingRegistry.wdlEnabledSetting.getValue() && WDLHelper.isWdlPresent() && WDLHelper.isDownloading() diff --git a/src/main/java/xaeroplus/mixin/client/mc/MixinNetHandlerPlayClient.java b/src/main/java/xaeroplus/mixin/client/mc/MixinNetHandlerPlayClient.java new file mode 100644 index 00000000..8e123dfe --- /dev/null +++ b/src/main/java/xaeroplus/mixin/client/mc/MixinNetHandlerPlayClient.java @@ -0,0 +1,23 @@ +package xaeroplus.mixin.client.mc; + +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.network.play.server.SPacketChunkData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import xaeroplus.XaeroPlus; +import xaeroplus.event.ChunkDataEvent; + +@Mixin(NetHandlerPlayClient.class) +public class MixinNetHandlerPlayClient { + @Shadow + private WorldClient world; + + @Inject(method = "handleChunkData", at = @At("RETURN")) + public void handleChunkData(final SPacketChunkData packetIn, final CallbackInfo ci) { + XaeroPlus.EVENT_BUS.post(new ChunkDataEvent(packetIn.isFullChunk(), this.world.getChunk(packetIn.getChunkX(), packetIn.getChunkZ()))); + } +} diff --git a/src/main/java/xaeroplus/module/impl/NewChunks.java b/src/main/java/xaeroplus/module/impl/NewChunks.java index 93c581c2..d3ff2881 100644 --- a/src/main/java/xaeroplus/module/impl/NewChunks.java +++ b/src/main/java/xaeroplus/module/impl/NewChunks.java @@ -10,7 +10,7 @@ import xaeroplus.module.Module; import xaeroplus.settings.XaeroPlusSettingRegistry; import xaeroplus.util.ColorHelper; -import xaeroplus.util.HighlightAtChunkPos; +import xaeroplus.util.highlights.HighlightAtChunkPos; import xaeroplus.util.newchunks.NewChunksCache; import xaeroplus.util.newchunks.NewChunksLocalCache; import xaeroplus.util.newchunks.NewChunksSavingCache; @@ -23,13 +23,14 @@ public class NewChunks extends Module { private NewChunksCache newChunksCache = new NewChunksLocalCache(); private int newChunksColor = getColor(255, 0, 0, 100); + private static final String DATABASE_NAME = "XaeroPlusNewChunks"; public void setNewChunksCache(boolean disk) { try { Long2LongOpenHashMap map = newChunksCache.getNewChunksState(); newChunksCache.onDisable(); if (disk) { - newChunksCache = new NewChunksSavingCache(); + newChunksCache = new NewChunksSavingCache(DATABASE_NAME); } else { newChunksCache = new NewChunksLocalCache(); } diff --git a/src/main/java/xaeroplus/module/impl/PortalSkipDetection.java b/src/main/java/xaeroplus/module/impl/PortalSkipDetection.java index 72ed4a97..bd3a7898 100644 --- a/src/main/java/xaeroplus/module/impl/PortalSkipDetection.java +++ b/src/main/java/xaeroplus/module/impl/PortalSkipDetection.java @@ -1,9 +1,7 @@ package xaeroplus.module.impl; -import com.github.benmanes.caffeine.cache.AsyncLoadingCache; -import com.github.benmanes.caffeine.cache.Caffeine; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import net.minecraft.util.math.ChunkPos; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import xaero.map.WorldMapSession; @@ -16,16 +14,18 @@ import xaeroplus.module.Module; import xaeroplus.module.ModuleManager; import xaeroplus.settings.XaeroPlusSettingRegistry; -import xaeroplus.util.*; +import xaeroplus.util.ChunkUtils; +import xaeroplus.util.ColorHelper; +import xaeroplus.util.CustomDimensionMapProcessor; +import xaeroplus.util.SeenChunksTrackingMapTileChunk; +import xaeroplus.util.highlights.ChunkHighlightLocalCache; +import xaeroplus.util.highlights.HighlightAtChunkPos; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.concurrent.*; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import static xaeroplus.util.ChunkUtils.*; import static xaeroplus.util.GuiMapHelper.*; @@ -35,13 +35,7 @@ public class PortalSkipDetection extends Module { private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private Future portalSkipDetectionSearchFuture = null; private int portalSkipChunksColor = ColorHelper.getColor(255, 255, 255, 100); - private final LongOpenHashSet portalSkipChunks = new LongOpenHashSet(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final AsyncLoadingCache> regionRenderCache = Caffeine.newBuilder() - .expireAfterWrite(3000L, TimeUnit.MILLISECONDS) - .refreshAfterWrite(500L, TimeUnit.MILLISECONDS) - .executor(Shared.cacheRefreshExecutorService) - .buildAsync(key -> loadHighlightChunksAtRegion(key.leafRegionX, key.leafRegionZ, key.level, this::isPortalSkipChunk).call()); + private final ChunkHighlightLocalCache cache = new ChunkHighlightLocalCache(); private int windowRegionX = 0; private int windowRegionZ = 0; private int windowRegionSize = 0; @@ -52,23 +46,22 @@ public class PortalSkipDetection extends Module { @SubscribeEvent public void onClientTickEvent(final TickEvent.ClientTickEvent event) { - if (!worldCacheInitialized) return; - if (event.phase == TickEvent.Phase.END) { - if (portalSkipDetectionSearchFuture == null || portalSkipDetectionSearchFuture.isDone()) { - tickCounter++; - if (tickCounter >= searchDelayTicks) { - tickCounter = 0; - Optional guiMapOptional = getGuiMap(); - if (guiMapOptional.isPresent()) { - final GuiMap guiMap = guiMapOptional.get(); - final int mapCenterX = getGuiMapCenterRegionX(guiMap); - final int mapCenterZ = getGuiMapCenterRegionZ(guiMap); - final int mapSize = getGuiMapRegionSize(guiMap); - setWindow(mapCenterX, mapCenterZ, mapSize); - } else { - setWindow(ChunkUtils.getPlayerRegionX(), ChunkUtils.getPlayerRegionZ(), defaultRegionWindowSize); - } - } + if (!worldCacheInitialized + || event.phase != TickEvent.Phase.END + || portalSkipDetectionSearchFuture != null + && !portalSkipDetectionSearchFuture.isDone()) return; + tickCounter++; + if (tickCounter >= searchDelayTicks) { + tickCounter = 0; + Optional guiMapOptional = getGuiMap(); + if (guiMapOptional.isPresent()) { + final GuiMap guiMap = guiMapOptional.get(); + final int mapCenterX = getGuiMapCenterRegionX(guiMap); + final int mapCenterZ = getGuiMapCenterRegionZ(guiMap); + final int mapSize = getGuiMapRegionSize(guiMap); + setWindow(mapCenterX, mapCenterZ, mapSize); + } else { + setWindow(ChunkUtils.getPlayerRegionX(), ChunkUtils.getPlayerRegionZ(), defaultRegionWindowSize); } } } @@ -93,8 +86,7 @@ public void onDisable() { private void initializeWorld() { try { final String worldId = XaeroWorldMapCore.currentSession.getMapProcessor().getCurrentWorldId(); - final String mwId = XaeroWorldMapCore.currentSession.getMapProcessor().getCurrentMWId(); - if (worldId == null || mwId == null) return; + if (worldId == null) return; worldCacheInitialized = true; } catch (final Exception e) { // expected on game launch @@ -102,12 +94,7 @@ private void initializeWorld() { } private void reset() { - try { - lock.writeLock().lock(); - portalSkipChunks.clear(); - } finally { - lock.writeLock().unlock(); - } + cache.reset(); final Future future = portalSkipDetectionSearchFuture; if (future != null && !future.isDone()) { future.cancel(true); @@ -127,7 +114,7 @@ private void searchForPortalSkipChunks() { final int windowRegionZ = this.windowRegionZ; final int windowRegionSize = this.windowRegionSize; final int currentlyViewedDimension = getCurrentlyViewedDimension(); - final HashSet portalDetectionSearchChunks = new HashSet<>(); + final LongOpenHashSet portalDetectionSearchChunks = new LongOpenHashSet(); for (int regionX = windowRegionX - windowRegionSize; regionX <= windowRegionX + windowRegionSize; regionX++) { final int baseChunkCoordX = ChunkUtils.regionCoordToChunkCoord(regionX); for (int regionZ = windowRegionZ - windowRegionSize; regionZ <= windowRegionZ + windowRegionSize; regionZ++) { @@ -136,22 +123,20 @@ private void searchForPortalSkipChunks() { for (int chunkZ = 0; chunkZ < 32; chunkZ++) { final int chunkPosX = baseChunkCoordX + chunkX; final int chunkPosZ = baseChunkCoordZ + chunkZ; - if (isChunkSeen(chunkPosX, chunkPosZ, currentlyViewedDimension)) { - if (!isNewChunk(chunkPosX, chunkPosZ, currentlyViewedDimension)) { - portalDetectionSearchChunks.add(new ChunkPos(chunkPosX, chunkPosZ)); - } + if (isChunkSeen(chunkPosX, chunkPosZ, currentlyViewedDimension) && !isNewChunk(chunkPosX, chunkPosZ, currentlyViewedDimension)) { + portalDetectionSearchChunks.add(ChunkUtils.chunkPosToLong(chunkPosX, chunkPosZ)); } } } } } - final HashSet portalAreaChunks = new HashSet<>(); - for (final ChunkPos chunkPos : portalDetectionSearchChunks) { + final Long2LongOpenHashMap portalAreaChunks = new Long2LongOpenHashMap(); + for (final long chunkPos : portalDetectionSearchChunks) { boolean allSeen = true; - final HashSet portalChunkTempSet = new HashSet<>(); + final LongOpenHashSet portalChunkTempSet = new LongOpenHashSet(); for (int xOffset = 0; xOffset < 15; xOffset++) { for (int zOffset = 0; zOffset < 15; zOffset++) { - final ChunkPos currentChunkPos = new ChunkPos(chunkPos.x + xOffset, chunkPos.z + zOffset); + final long currentChunkPos = ChunkUtils.chunkPosToLong(ChunkUtils.longToChunkX(chunkPos) + xOffset, ChunkUtils.longToChunkZ(chunkPos) + zOffset); portalChunkTempSet.add(currentChunkPos); if (!portalDetectionSearchChunks.contains(currentChunkPos)) { allSeen = false; @@ -160,20 +145,12 @@ private void searchForPortalSkipChunks() { } } if (!allSeen) { - portalChunkTempSet.clear(); break; } } - if (allSeen) portalAreaChunks.addAll(portalChunkTempSet); - } - final List chunks = portalAreaChunks.stream().map(chunkPos -> chunkPosToLong(chunkPos.x, chunkPos.z)).collect(Collectors.toList()); - try { - lock.writeLock().lock(); - this.portalSkipChunks.clear(); - this.portalSkipChunks.addAll(chunks); - } finally { - lock.writeLock().unlock(); + if (allSeen) portalChunkTempSet.forEach(c -> portalAreaChunks.put(c, 0)); } + cache.replaceState(portalAreaChunks); } catch (final Exception e) { XaeroPlus.LOGGER.error("Error searching for portal skip chunks", e); } @@ -184,8 +161,6 @@ private boolean isNewChunk(final int chunkPosX, final int chunkPosZ, final int c } private boolean isChunkSeen(final int chunkPosX, final int chunkPosZ, final int currentlyViewedDimension) { - //5100,7578 - //318,473 final WorldMapSession currentSession = XaeroWorldMapCore.currentSession; if (currentSession == null) return false; final CustomDimensionMapProcessor mapProcessor = (CustomDimensionMapProcessor) currentSession.getMapProcessor(); @@ -219,17 +194,7 @@ public void setAlpha(final float a) { public List getPortalSkipChunksInRegion( final int leafRegionX, final int leafRegionZ, final int level) { - try { - CompletableFuture> future = regionRenderCache.get(new RegionRenderPos(leafRegionX, leafRegionZ, level)); - if (future.isDone()) { - return future.get(); - } else { - return Collections.emptyList(); - } - } catch (Exception e) { - XaeroPlus.LOGGER.error("Error loading portal skip chunks", e); - } - return Collections.emptyList(); + return cache.getHighlightsInRegion(leafRegionX, leafRegionZ, level); } public boolean isPortalSkipChunk(final int chunkPosX, final int chunkPosZ) { @@ -237,16 +202,7 @@ public boolean isPortalSkipChunk(final int chunkPosX, final int chunkPosZ) { } public boolean isPortalSkipChunk(final long chunkPos) { - try { - if (lock.readLock().tryLock(1, TimeUnit.SECONDS)) { - boolean containsKey = portalSkipChunks.contains(chunkPos); - lock.readLock().unlock(); - return containsKey; - } - } catch (final Exception e) { - XaeroPlus.LOGGER.error("Error checking if chunk is portal skip chunk", e); - } - return false; + return cache.isHighlighted(chunkPos); } public void setSearchDelayTicks(final float delay) { diff --git a/src/main/java/xaeroplus/module/impl/Portals.java b/src/main/java/xaeroplus/module/impl/Portals.java new file mode 100644 index 00000000..488fac30 --- /dev/null +++ b/src/main/java/xaeroplus/module/impl/Portals.java @@ -0,0 +1,167 @@ +package xaeroplus.module.impl; + +import net.minecraft.block.BlockPortal; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.network.play.server.SPacketBlockChange; +import net.minecraft.network.play.server.SPacketMultiBlockChange; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.chunk.Chunk; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import xaeroplus.XaeroPlus; +import xaeroplus.event.ChunkDataEvent; +import xaeroplus.event.PacketReceivedEvent; +import xaeroplus.event.XaeroWorldChangeEvent; +import xaeroplus.module.Module; +import xaeroplus.settings.XaeroPlusSettingRegistry; +import xaeroplus.util.ChunkUtils; +import xaeroplus.util.ColorHelper; +import xaeroplus.util.highlights.ChunkHighlightSavingCache; +import xaeroplus.util.highlights.HighlightAtChunkPos; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static xaeroplus.util.ColorHelper.getColor; + +@Module.ModuleInfo() +public class Portals extends Module { + + private ChunkHighlightSavingCache portalsCache; + private final ExecutorService searchExecutor = Executors.newSingleThreadExecutor(); + private final Minecraft mc = Minecraft.getMinecraft(); + private int portalsColor = getColor(0, 255, 0, 100); + private static final String DATABASE_NAME = "XaeroPlusPortals"; + + @Override + public void onEnable() { + if (portalsCache == null) { + portalsCache = new ChunkHighlightSavingCache(DATABASE_NAME); + portalsCache.onEnable(); + searchAllLoadedChunks(); + } + } + + @Override + public void onDisable() { + if (portalsCache != null) { + portalsCache.onDisable(); + portalsCache = null; + } + } + + @SubscribeEvent + public void onChunkData(final ChunkDataEvent event) { + if (event.isFullChunk()) { + findPortalInChunkAsync(event.getChunk()); + } + } + // 161, 142 + @SubscribeEvent + public void onPacketReceived(final PacketReceivedEvent event) { + if (event.packet instanceof SPacketBlockChange) { + final SPacketBlockChange packet = (SPacketBlockChange) event.packet; + handleBlockChange(packet.getBlockPosition(), packet.getBlockState()); + } else if (event.packet instanceof SPacketMultiBlockChange) { + final SPacketMultiBlockChange packet = (SPacketMultiBlockChange) event.packet; + for (final SPacketMultiBlockChange.BlockUpdateData update : packet.getChangedBlocks()) { + handleBlockChange(update.getPos(), update.getBlockState()); + } + } + } + + @SubscribeEvent + public void onXaeroWorldChangeEvent(final XaeroWorldChangeEvent event) { + portalsCache.handleWorldChange(); + } + + @SubscribeEvent + public void onClientTickEvent(final TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.END) { + portalsCache.handleTick(); + } + } + private void findPortalInChunkAsync(final Chunk chunk) { + searchExecutor.submit(() -> { + try { + int iterations = 0; + while (iterations++ < 3) { + if (findPortalInChunk(chunk)) break; + // mitigate race condition during world changes hackily + Thread.sleep(500); + } + } catch (final Throwable e) { + XaeroPlus.LOGGER.error("Error searching for portal in chunk: {}, {}", chunk.x, chunk.z, e); + } + }); + } + + private boolean findPortalInChunk(final Chunk chunk) { + final boolean chunkHadPortal = portalsCache.isHighlighted(chunk.x, chunk.z); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 256; y++) { + IBlockState blockState = chunk.getBlockState(x, y, z); + if (blockState.getBlock() instanceof BlockPortal) { + return portalsCache.addHighlight(chunk.x, chunk.z); + } + } + } + } + if (chunkHadPortal) { + portalsCache.removeHighlight(chunk.x, chunk.z); + } + return true; + } + + private void searchAllLoadedChunks() { + if (mc.world == null) return; + final int renderDist = mc.gameSettings.renderDistanceChunks; + final int xMin = ChunkUtils.getPlayerChunkX() - renderDist; + final int xMax = ChunkUtils.getPlayerChunkX() + renderDist; + final int zMin = ChunkUtils.getPlayerChunkZ() - renderDist; + final int zMax = ChunkUtils.getPlayerChunkZ() + renderDist; + for (int x = xMin; x <= xMax; x++) { + for (int z = zMin; z <= zMax; z++) { + Chunk chunk = mc.world.getChunk(x, z); + if (!chunk.isLoaded()) continue; + findPortalInChunkAsync(chunk); + } + } + } + + private void handleBlockChange(final BlockPos pos, final IBlockState state) { + if (portalsCache.isHighlighted(ChunkUtils.posToChunkPos(pos.getX()), ChunkUtils.posToChunkPos(pos.getZ()))) { + if (!(state.getBlock() instanceof BlockPortal)) { + portalsCache.removeHighlight(ChunkUtils.posToChunkPos(pos.getX()), ChunkUtils.posToChunkPos(pos.getZ())); + } + } else if (state.getBlock() instanceof BlockPortal) { + portalsCache.addHighlight(ChunkUtils.posToChunkPos(pos.getX()), ChunkUtils.posToChunkPos(pos.getZ())); + } + } + + public int getPortalsColor() { + return portalsColor; + } + + public void setRgbColor(final int color) { + portalsColor = ColorHelper.getColorWithAlpha(color, (int) XaeroPlusSettingRegistry.portalsAlphaSetting.getValue()); + } + + public void setAlpha(final float a) { + portalsColor = ColorHelper.getColorWithAlpha(portalsColor, (int) (a)); + } + + public List getPortalsInRegion( + final int leafRegionX, final int leafRegionZ, + final int level, + final int dimension) { + return portalsCache.getHighlightsInRegion(leafRegionX, leafRegionZ, level, dimension); + } + + public boolean isPortalChunk(final int chunkPosX, final int chunkPosZ, final int dimensionId) { + return portalsCache.isHighlighted(chunkPosX, chunkPosZ, dimensionId); + } +} diff --git a/src/main/java/xaeroplus/settings/XaeroPlusSettingRegistry.java b/src/main/java/xaeroplus/settings/XaeroPlusSettingRegistry.java index 60154c9d..02f47a50 100644 --- a/src/main/java/xaeroplus/settings/XaeroPlusSettingRegistry.java +++ b/src/main/java/xaeroplus/settings/XaeroPlusSettingRegistry.java @@ -1,10 +1,7 @@ package xaeroplus.settings; import xaeroplus.module.ModuleManager; -import xaeroplus.module.impl.BaritoneGoalSync; -import xaeroplus.module.impl.NewChunks; -import xaeroplus.module.impl.PortalSkipDetection; -import xaeroplus.module.impl.WaystoneSync; +import xaeroplus.module.impl.*; import xaeroplus.util.*; import static xaeroplus.settings.XaeroPlusSettingsReflectionHax.SettingLocation; @@ -130,6 +127,29 @@ public final class XaeroPlusSettingRegistry { ColorHelper.HighlightColor.values(), ColorHelper.HighlightColor.RED, SettingLocation.WORLD_MAP_MAIN); + public static final XaeroPlusBooleanSetting portalsEnabledSetting = XaeroPlusBooleanSetting.create( + "Portal Highlights", + "setting.world_map.portals", + "setting.world_map.portals.tooltip", + (b) -> ModuleManager.getModule(Portals.class).setEnabled(b), + true, + SettingLocation.WORLD_MAP_MAIN); + public static final XaeroPlusFloatSetting portalsAlphaSetting = XaeroPlusFloatSetting.create( + "Portal Highlights Opacity", + "setting.world_map.portals_opacity", + 10f, 255f, 10f, + "setting.world_map.portals_opacity.tooltip", + (b) -> ModuleManager.getModule(Portals.class).setAlpha(b), + 100, + SettingLocation.WORLD_MAP_MAIN); + public static final XaeroPlusEnumSetting portalsColorSetting = XaeroPlusEnumSetting.create( + "Portal Highlights Color", + "setting.world_map.portals_color", + "setting.world_map.portals_color.tooltip", + (b) -> ModuleManager.getModule(Portals.class).setRgbColor(b.getColor()), + ColorHelper.HighlightColor.values(), + ColorHelper.HighlightColor.MAGENTA, + SettingLocation.WORLD_MAP_MAIN); public static final XaeroPlusBooleanSetting portalSkipDetectionEnabledSetting = XaeroPlusBooleanSetting.create( "PortalSkip Detection", "setting.world_map.portal_skip_detection", diff --git a/src/main/java/xaeroplus/util/ChunkUtils.java b/src/main/java/xaeroplus/util/ChunkUtils.java index 2f8c95a7..0f975069 100644 --- a/src/main/java/xaeroplus/util/ChunkUtils.java +++ b/src/main/java/xaeroplus/util/ChunkUtils.java @@ -2,6 +2,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.util.math.ChunkPos; +import xaeroplus.util.highlights.HighlightAtChunkPos; import java.util.ArrayList; import java.util.List; @@ -30,6 +31,9 @@ public static int longToChunkX(final long l) { public static int longToChunkZ(final long l) { return (int)(l >> 32 & 4294967295L); } + public static int posToChunkPos(final int i) { + return i >> 4; + } public static Callable> loadHighlightChunksAtRegion( final int leafRegionX, final int leafRegionZ, final int level, diff --git a/src/main/java/xaeroplus/util/WDLHelper.java b/src/main/java/xaeroplus/util/WDLHelper.java index 67413c4e..d79eb2ab 100644 --- a/src/main/java/xaeroplus/util/WDLHelper.java +++ b/src/main/java/xaeroplus/util/WDLHelper.java @@ -8,6 +8,8 @@ import wdl.WDL; import xaeroplus.XaeroPlus; import xaeroplus.settings.XaeroPlusSettingRegistry; +import xaeroplus.util.highlights.HighlightAtChunkPos; +import xaeroplus.util.highlights.RegionRenderPos; import java.util.Collections; import java.util.HashSet; diff --git a/src/main/java/xaeroplus/util/newchunks/NewChunksBaseCacheHandler.java b/src/main/java/xaeroplus/util/highlights/ChunkHighlightBaseCacheHandler.java similarity index 54% rename from src/main/java/xaeroplus/util/newchunks/NewChunksBaseCacheHandler.java rename to src/main/java/xaeroplus/util/highlights/ChunkHighlightBaseCacheHandler.java index 84cba196..8c44e624 100644 --- a/src/main/java/xaeroplus/util/newchunks/NewChunksBaseCacheHandler.java +++ b/src/main/java/xaeroplus/util/highlights/ChunkHighlightBaseCacheHandler.java @@ -1,11 +1,9 @@ -package xaeroplus.util.newchunks; +package xaeroplus.util.highlights; import com.github.benmanes.caffeine.cache.AsyncLoadingCache; import com.github.benmanes.caffeine.cache.Caffeine; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import xaeroplus.XaeroPlus; -import xaeroplus.util.HighlightAtChunkPos; -import xaeroplus.util.RegionRenderPos; import xaeroplus.util.Shared; import java.util.Collections; @@ -18,16 +16,20 @@ import static xaeroplus.util.ChunkUtils.chunkPosToLong; import static xaeroplus.util.ChunkUtils.loadHighlightChunksAtRegion; -public abstract class NewChunksBaseCacheHandler { - final Long2LongOpenHashMap chunks = new Long2LongOpenHashMap(); - final ReadWriteLock lock = new ReentrantReadWriteLock(); +public abstract class ChunkHighlightBaseCacheHandler { + public final ReadWriteLock lock = new ReentrantReadWriteLock(); + public final Long2LongOpenHashMap chunks = new Long2LongOpenHashMap(); private final AsyncLoadingCache> regionRenderCache = Caffeine.newBuilder() .expireAfterWrite(3000, TimeUnit.MILLISECONDS) .refreshAfterWrite(500, TimeUnit.MILLISECONDS) .executor(Shared.cacheRefreshExecutorService) - .buildAsync(key -> loadHighlightChunksAtRegion(key.leafRegionX, key.leafRegionZ, key.level, this::isNewChunk).call()); + .buildAsync(key -> loadHighlightChunksAtRegion(key.leafRegionX, key.leafRegionZ, key.level, this::isHighlighted).call()); - public void addNewChunk(final int x, final int z, final long foundTime) { + public void addHighlight(final int x, final int z) { + addHighlight(x, z, System.currentTimeMillis()); + } + + public void addHighlight(final int x, final int z, final long foundTime) { final long chunkPos = chunkPosToLong(x, z); try { if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) { @@ -39,16 +41,11 @@ public void addNewChunk(final int x, final int z, final long foundTime) { } } - public void addNewChunk(final int x, final int z) { - addNewChunk(x, z, System.currentTimeMillis()); - } - - // in chunkpos coordinates - public boolean isNewChunk(final int x, final int z) { - return isNewChunk(chunkPosToLong(x, z)); + public boolean isHighlighted(final int x, final int z) { + return isHighlighted(chunkPosToLong(x, z)); } - public boolean isNewChunk(final long chunkPos) { + public boolean isHighlighted(final long chunkPos) { try { if (lock.readLock().tryLock(1, TimeUnit.SECONDS)) { boolean containsKey = chunks.containsKey(chunkPos); @@ -56,12 +53,26 @@ public boolean isNewChunk(final long chunkPos) { return containsKey; } } catch (final Exception e) { - XaeroPlus.LOGGER.error("Error checking if chunk is new", e); + XaeroPlus.LOGGER.error("Error checking if chunk contains portal", e); } return false; } - public Long2LongOpenHashMap getNewChunksState() { + public List getHighlightsInRegion(final int leafRegionX, final int leafRegionZ, final int level) { + try { + final CompletableFuture> future = regionRenderCache.get(new RegionRenderPos(leafRegionX, leafRegionZ, level)); + if (future.isDone()) { + return future.get(); + } else { + return Collections.emptyList(); + } + } catch (Exception e) { + XaeroPlus.LOGGER.error("Error handling Portals region lookup", e); + } + return Collections.emptyList(); + } + + public Long2LongOpenHashMap getHighlightsState() { return chunks; } @@ -76,17 +87,37 @@ public void loadPreviousState(final Long2LongOpenHashMap state) { } } - public List getNewChunksInRegion(final int leafRegionX, final int leafRegionZ, final int level) { + public void removeHighlight(final int x, final int z) { try { - final CompletableFuture> future = regionRenderCache.get(new RegionRenderPos(leafRegionX, leafRegionZ, level)); - if (future.isDone()) { - return future.get(); - } else { - return Collections.emptyList(); + if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) { + chunks.remove(chunkPosToLong(x, z)); + lock.writeLock().unlock(); } - } catch (Exception e) { - XaeroPlus.LOGGER.error("Error handling NewChunks region lookup", e); + } catch (final Exception e) { + XaeroPlus.LOGGER.error("Error removing highlight", e); + } + } + + public void replaceState(final Long2LongOpenHashMap state) { + try { + if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) { + this.chunks.clear(); + this.chunks.putAll(state); + lock.writeLock().unlock(); + } + } catch (final Exception e) { + XaeroPlus.LOGGER.error("Failed replacing cache state", e); + } + } + + public void reset() { + try { + if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) { + chunks.clear(); + lock.writeLock().unlock(); + } + } catch (final Exception e) { + XaeroPlus.LOGGER.error("Failed resetting cache", e); } - return Collections.emptyList(); } } diff --git a/src/main/java/xaeroplus/util/newchunks/NewChunksSavingCacheDimensionHandler.java b/src/main/java/xaeroplus/util/highlights/ChunkHighlightCacheDimensionHandler.java similarity index 67% rename from src/main/java/xaeroplus/util/newchunks/NewChunksSavingCacheDimensionHandler.java rename to src/main/java/xaeroplus/util/highlights/ChunkHighlightCacheDimensionHandler.java index 91860a51..f4f70885 100644 --- a/src/main/java/xaeroplus/util/newchunks/NewChunksSavingCacheDimensionHandler.java +++ b/src/main/java/xaeroplus/util/highlights/ChunkHighlightCacheDimensionHandler.java @@ -1,4 +1,4 @@ -package xaeroplus.util.newchunks; +package xaeroplus.util.highlights; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; @@ -14,16 +14,16 @@ import static xaeroplus.util.ChunkUtils.chunkPosToLong; import static xaeroplus.util.ChunkUtils.regionCoordToChunkCoord; -public class NewChunksSavingCacheDimensionHandler extends NewChunksBaseCacheHandler { +public class ChunkHighlightCacheDimensionHandler extends ChunkHighlightBaseCacheHandler { private final int dimension; private int windowRegionX = 0; private int windowRegionZ = 0; // square centered at windowX, windowZ with size windowSize private int windowRegionSize = 0; - private final NewChunksDatabase database; + private final ChunkHighlightDatabase database; private final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); - public NewChunksSavingCacheDimensionHandler(final int dimension, final NewChunksDatabase database) { + public ChunkHighlightCacheDimensionHandler(final int dimension, final ChunkHighlightDatabase database) { this.dimension = dimension; this.database = database; } @@ -32,32 +32,33 @@ public void setWindow(int regionX, int regionZ, int regionSize) { this.windowRegionX = regionX; this.windowRegionZ = regionZ; this.windowRegionSize = regionSize; - writeNewChunksOutsideWindowToDatabase(); - loadNewChunksInWindow(); + writeHighlightsOutsideWindowToDatabase(); + loadHighlightsInWindow(); } - private void loadNewChunksInWindow() { + private void loadHighlightsInWindow() { executorService.submit(() -> { - final List newChunks = database.getNewChunksInWindow(dimension, + final List chunks = database.getHighlightsInWindow( + dimension, windowRegionX - windowRegionSize, windowRegionX + windowRegionSize, windowRegionZ - windowRegionSize, windowRegionZ + windowRegionSize ); try { if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) { - for (final NewChunkData chunk : newChunks) { - chunks.put(chunkPosToLong(chunk.x, chunk.z), chunk.foundTime); + for (final ChunkHighlightData chunk : chunks) { + this.chunks.put(chunkPosToLong(chunk.x, chunk.z), chunk.foundTime); } lock.writeLock().unlock(); } } catch (final Exception e) { - XaeroPlus.LOGGER.error("Failed to load new chunks in window", e); + XaeroPlus.LOGGER.error("Failed to load portals in window", e); } }); } - private void writeNewChunksOutsideWindowToDatabase() { + private void writeHighlightsOutsideWindowToDatabase() { executorService.execute(() -> { - final List chunksToWrite = new ArrayList<>(); + final List chunksToWrite = new ArrayList<>(); try { if (lock.writeLock().tryLock(1L, TimeUnit.SECONDS)) { chunks.long2LongEntrySet().removeIf(entry -> { @@ -68,7 +69,7 @@ private void writeNewChunksOutsideWindowToDatabase() { || chunkX > regionCoordToChunkCoord(windowRegionX + windowRegionSize) || chunkZ < regionCoordToChunkCoord(windowRegionZ - windowRegionSize) || chunkZ > regionCoordToChunkCoord(windowRegionZ + windowRegionSize)) { - chunksToWrite.add(new NewChunkData(chunkX, chunkZ, entry.getLongValue())); + chunksToWrite.add(new ChunkHighlightData(chunkX, chunkZ, entry.getLongValue())); return true; } return false; @@ -76,32 +77,38 @@ private void writeNewChunksOutsideWindowToDatabase() { lock.writeLock().unlock(); } } catch (final Exception e) { - XaeroPlus.LOGGER.error("Error while writing new chunks outside window to database", e); + XaeroPlus.LOGGER.error("Error while writing portals outside window to database", e); } - database.insertNewChunkList(chunksToWrite, dimension); + database.insertHighlightList(chunksToWrite, dimension); }); } - public ListenableFuture writeAllChunksToDatabase() { + public ListenableFuture writeAllHighlightsToDatabase() { return executorService.submit(() -> { - final List chunksToWrite = new ArrayList<>(); + final List chunksToWrite = new ArrayList<>(); try { if (lock.readLock().tryLock(1, TimeUnit.SECONDS)) { chunks.long2LongEntrySet().forEach(entry -> { final long chunkPos = entry.getLongKey(); final int chunkX = ChunkUtils.longToChunkX(chunkPos); final int chunkZ = ChunkUtils.longToChunkZ(chunkPos); - chunksToWrite.add(new NewChunkData(chunkX, chunkZ, entry.getLongValue())); + chunksToWrite.add(new ChunkHighlightData(chunkX, chunkZ, entry.getLongValue())); }); lock.readLock().unlock(); } } catch (final Exception e) { XaeroPlus.LOGGER.error("Error while writing all chunks to database", e); } - database.insertNewChunkList(chunksToWrite, dimension); + database.insertHighlightList(chunksToWrite, dimension); }); } + @Override + public void removeHighlight(final int x, final int z) { + super.removeHighlight(x, z); + database.removeHighlight(x, z, dimension); + } + public void close() { executorService.shutdown(); } diff --git a/src/main/java/xaeroplus/util/newchunks/NewChunkData.java b/src/main/java/xaeroplus/util/highlights/ChunkHighlightData.java similarity index 61% rename from src/main/java/xaeroplus/util/newchunks/NewChunkData.java rename to src/main/java/xaeroplus/util/highlights/ChunkHighlightData.java index b1629940..0b0e1eb9 100644 --- a/src/main/java/xaeroplus/util/newchunks/NewChunkData.java +++ b/src/main/java/xaeroplus/util/highlights/ChunkHighlightData.java @@ -1,26 +1,26 @@ -package xaeroplus.util.newchunks; +package xaeroplus.util.highlights; import static xaeroplus.util.ChunkUtils.longToChunkX; import static xaeroplus.util.ChunkUtils.longToChunkZ; -public class NewChunkData { +public class ChunkHighlightData { public final int x; public final int z; public final long foundTime; - public NewChunkData(final long chunkPos, final long foundTime) { + public ChunkHighlightData(final long chunkPos, final long foundTime) { this.x = longToChunkX(chunkPos); this.z = longToChunkZ(chunkPos); this.foundTime = foundTime; } - public NewChunkData(final int x, final int z, final int foundTime) { + public ChunkHighlightData(final int x, final int z, final int foundTime) { this.x = x; this.z = z; this.foundTime = (long) foundTime; } - public NewChunkData(final int x, final int z, final long foundTime) { + public ChunkHighlightData(final int x, final int z, final long foundTime) { this.x = x; this.z = z; this.foundTime = foundTime; diff --git a/src/main/java/xaeroplus/util/newchunks/NewChunksDatabase.java b/src/main/java/xaeroplus/util/highlights/ChunkHighlightDatabase.java similarity index 56% rename from src/main/java/xaeroplus/util/newchunks/NewChunksDatabase.java rename to src/main/java/xaeroplus/util/highlights/ChunkHighlightDatabase.java index a7c0a15a..fd0d5098 100644 --- a/src/main/java/xaeroplus/util/newchunks/NewChunksDatabase.java +++ b/src/main/java/xaeroplus/util/highlights/ChunkHighlightDatabase.java @@ -1,4 +1,4 @@ -package xaeroplus.util.newchunks; +package xaeroplus.util.highlights; import com.google.common.collect.Lists; import xaero.map.WorldMap; @@ -16,21 +16,22 @@ import static xaeroplus.util.ChunkUtils.regionCoordToChunkCoord; -public class NewChunksDatabase implements Closeable { - public static final int MAX_NEWCHUNKS_LIST_SIZE = 25000; +public class ChunkHighlightDatabase implements Closeable { + public static int MAX_HIGHLIGHTS_LIST = 25000; private final Connection connection; - public NewChunksDatabase(String worldId) { + + public ChunkHighlightDatabase(String worldId, String databaseName) { try { - final Path dbPath = WorldMap.saveFolder.toPath().resolve(worldId).resolve("XaeroPlusNewChunks.db"); + final Path dbPath = WorldMap.saveFolder.toPath().resolve(worldId).resolve(databaseName + ".db"); connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); - createNewChunksTable(); + createHighlightsTables(); } catch (Exception e) { - XaeroPlus.LOGGER.error("Error while creating new chunks database", e); + XaeroPlus.LOGGER.error("Error while creating chunk highlight database", e); throw new RuntimeException(e); } } - public void createNewChunksTable() { + private void createHighlightsTables() { try { connection.createStatement().execute("CREATE TABLE IF NOT EXISTS \"0\" (x INTEGER, z INTEGER, foundTime INTEGER)"); connection.createStatement().execute("CREATE TABLE IF NOT EXISTS \"-1\" (x INTEGER, z INTEGER, foundTime INTEGER)"); @@ -43,58 +44,67 @@ public void createNewChunksTable() { } } - public void insertNewChunkList(final List newChunks, final int dimension) { - if (newChunks.isEmpty()) { + public void insertHighlightList(final List chunks, final int dimension) { + if (chunks.isEmpty()) { return; } - if (newChunks.size() > MAX_NEWCHUNKS_LIST_SIZE) { - Lists.partition(newChunks, MAX_NEWCHUNKS_LIST_SIZE).forEach(l -> insertNewChunksListInternal(l, dimension)); + if (chunks.size() > MAX_HIGHLIGHTS_LIST) { + Lists.partition(chunks, MAX_HIGHLIGHTS_LIST).forEach(l -> insertHighlightsListInternal(l, dimension)); } else { - insertNewChunksListInternal(newChunks, dimension); + insertHighlightsListInternal(chunks, dimension); } } - private void insertNewChunksListInternal(final List newChunks, final int dimension) { + private void insertHighlightsListInternal(final List chunks, final int dimension) { try { String statement = "INSERT OR IGNORE INTO \"" + dimension + "\" VALUES "; - statement += newChunks.stream() + statement += chunks.stream() .map(chunk -> "(" + chunk.x + ", " + chunk.z + ", " + chunk.foundTime + ")") .collect(Collectors.joining(", ")); connection.createStatement().execute(statement); } catch (Exception e) { - XaeroPlus.LOGGER.error("Error inserting new chunks into database", e); + XaeroPlus.LOGGER.error("Error inserting chunks into database", e); } } - public List getNewChunksInWindow( + public List getHighlightsInWindow( final int dimension, final int regionXMin, final int regionXMax, final int regionZMin, final int regionZMax) { try { - ResultSet resultSet = connection.createStatement().executeQuery("SELECT * FROM \"" + dimension + "\" " - + "WHERE x >= " + regionCoordToChunkCoord(regionXMin) + " AND x <= " + regionCoordToChunkCoord(regionXMax) - + " AND z >= " + regionCoordToChunkCoord(regionZMin) + " AND z <= " + regionCoordToChunkCoord(regionZMax)); - List newChunks = new ArrayList<>(); + ResultSet resultSet = connection.createStatement().executeQuery( + "SELECT * FROM \"" + dimension + "\" " + + "WHERE x >= " + regionCoordToChunkCoord(regionXMin) + " AND x <= " + regionCoordToChunkCoord(regionXMax) + + " AND z >= " + regionCoordToChunkCoord(regionZMin) + " AND z <= " + regionCoordToChunkCoord(regionZMax)); + List chunks = new ArrayList<>(); while (resultSet.next()) { - newChunks.add(new NewChunkData( + chunks.add(new ChunkHighlightData( resultSet.getInt("x"), resultSet.getInt("z"), resultSet.getInt("foundTime"))); } - return newChunks; + return chunks; } catch (final Exception e) { - XaeroPlus.LOGGER.error("Error while getting new chunks from database", e); + XaeroPlus.LOGGER.error("Error getting chunks from database", e); // fall through } return Collections.emptyList(); } + public void removeHighlight(final int x, final int z, final int dimension) { + try { + connection.createStatement().execute("DELETE FROM \"" + dimension + "\" WHERE x = " + x + " AND z = " + z); + } catch (Exception e) { + XaeroPlus.LOGGER.error("Error while removing highlight from database", e); + } + } + @Override public void close() { try { connection.close(); } catch (Exception e) { - XaeroPlus.LOGGER.warn("Failed closing NewChunks database connection", e); + XaeroPlus.LOGGER.warn("Failed closing database connection", e); } } } diff --git a/src/main/java/xaeroplus/util/highlights/ChunkHighlightLocalCache.java b/src/main/java/xaeroplus/util/highlights/ChunkHighlightLocalCache.java new file mode 100644 index 00000000..38c57ae9 --- /dev/null +++ b/src/main/java/xaeroplus/util/highlights/ChunkHighlightLocalCache.java @@ -0,0 +1,63 @@ +package xaeroplus.util.highlights; + +import xaeroplus.XaeroPlus; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class ChunkHighlightLocalCache extends ChunkHighlightBaseCacheHandler { + private static final int maxNumber = 5000; + + @Override + public void addHighlight(final int x, final int z) { + limitChunksSize(); + super.addHighlight(x, z); + } + + @Override + public void addHighlight(final int x, final int z, final long foundTime) { + limitChunksSize(); + super.addHighlight(x, z, foundTime); + } + + private void limitChunksSize() { + try { + if (chunks.size() > maxNumber) { + if (lock.readLock().tryLock(1, TimeUnit.SECONDS)) { + // remove oldest 500 chunks + final List toRemove = chunks.long2LongEntrySet().stream() + .sorted(Map.Entry.comparingByValue()) + .limit(500) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + lock.readLock().unlock(); + if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) { + toRemove.forEach(l -> chunks.remove((long) l)); + lock.writeLock().unlock(); + } + } + } + } catch (final Exception e) { + XaeroPlus.LOGGER.error("Error limiting local cache size", e); + } + } + + public void handleWorldChange() { + + } + + public void handleTick() { + + } + + public void onEnable() { + + } + + public void onDisable() { + + } + +} diff --git a/src/main/java/xaeroplus/util/highlights/ChunkHighlightSavingCache.java b/src/main/java/xaeroplus/util/highlights/ChunkHighlightSavingCache.java new file mode 100644 index 00000000..59e0ad7c --- /dev/null +++ b/src/main/java/xaeroplus/util/highlights/ChunkHighlightSavingCache.java @@ -0,0 +1,219 @@ +package xaeroplus.util.highlights; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import xaero.map.core.XaeroWorldMapCore; +import xaero.map.gui.GuiMap; +import xaeroplus.XaeroPlus; +import xaeroplus.util.ChunkUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static xaeroplus.util.ChunkUtils.getActualDimension; +import static xaeroplus.util.GuiMapHelper.*; + +public class ChunkHighlightSavingCache { + // these are initialized async + private Optional netherCache = Optional.empty(); + private Optional overworldCache = Optional.empty(); + private Optional endCache = Optional.empty(); + private Optional database = Optional.empty(); + private static final int defaultRegionWindowSize = 2; // when we are only viewing the minimap + private String currentWorldId; + private boolean worldCacheInitialized = false; + private final String databaseName; + + public ChunkHighlightSavingCache(final String databaseName) { + this.databaseName = databaseName; + } + + public boolean addHighlight(final int x, final int z) { + try { + getCacheForCurrentDimension() + .orElseThrow(() -> new RuntimeException("Didn't find cache for current dimension")) + .addHighlight(x, z); + return true; + } catch (final Exception e) { + XaeroPlus.LOGGER.error("Error adding highlight to saving cache: {}", databaseName, e); + return false; + } + } + + public void addHighlight(final int x, final int z, final long foundTime, final int dimension) { + getCacheForDimension(dimension) + .orElseThrow(() -> new RuntimeException("Dimension not found: " + dimension)) + .addHighlight(x, z, foundTime); + } + + public boolean isHighlighted(final int chunkPosX, final int chunkPosZ, final int dimensionId) { + return getCacheForDimension(dimensionId) + .map(c -> c.isHighlighted(chunkPosX, chunkPosZ)) + .orElse(false); + } + + public boolean isHighlighted(final int chunkPosX, final int chunkPosZ) { + return getCacheForDimension(ChunkUtils.getActualDimension()) + .map(c -> c.isHighlighted(chunkPosX, chunkPosZ)) + .orElse(false); + } + + public List getHighlightsInRegion(final int leafRegionX, final int leafRegionZ, final int level, int dimension) { + return getCacheForDimension(dimension) + .map(c -> c.getHighlightsInRegion(leafRegionX, leafRegionZ, level)) + .orElse(Collections.emptyList()); + } + + public void handleWorldChange() { + Futures.whenAllComplete(saveAllChunks()) + .call(() -> { + reset(); + initializeWorld(); + loadChunksInActualDimension(); + return null; + }); + } + + public void reset() { + this.worldCacheInitialized = false; + this.currentWorldId = null; + this.netherCache.ifPresent(ChunkHighlightCacheDimensionHandler::close); + this.netherCache = Optional.empty(); + this.overworldCache.ifPresent(ChunkHighlightCacheDimensionHandler::close); + this.overworldCache = Optional.empty(); + this.endCache.ifPresent(ChunkHighlightCacheDimensionHandler::close); + this.endCache = Optional.empty(); + this.database.ifPresent(ChunkHighlightDatabase::close); + this.database = Optional.empty(); + } + + private List> saveAllChunks() { + return getAllCaches().stream() + .map(ChunkHighlightCacheDimensionHandler::writeAllHighlightsToDatabase) + .collect(Collectors.toList()); + } + + public Optional getCacheForCurrentDimension() { + switch (getActualDimension()) { + case -1: + return netherCache; + case 0: + return overworldCache; + case 1: + return endCache; + default: + throw new RuntimeException("Unknown dimension: " + getActualDimension()); + } + } + + public Optional getCacheForDimension(final int dimension) { + switch (dimension) { + case -1: + return netherCache; + case 0: + return overworldCache; + case 1: + return endCache; + default: + throw new RuntimeException("Unknown dimension: " + dimension); + } + } + + private List getAllCaches() { + return Stream.of(netherCache, overworldCache, endCache).filter(Optional::isPresent).map(Optional::get).collect( + Collectors.toList()); + } + + public List getCachesExceptDimension(final int dimension) { + switch (dimension) { + case -1: + return Stream.of(overworldCache, endCache).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); + case 0: + return Stream.of(netherCache, endCache).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); + case 1: + return Stream.of(netherCache, overworldCache).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); + default: + throw new RuntimeException("Unknown dimension: " + dimension); + } + } + + private void initializeWorld() { + try { + final String worldId = XaeroWorldMapCore.currentSession.getMapProcessor().getCurrentWorldId(); + if (worldId == null) return; + final int dimension = getActualDimension(); + if (dimension != 0 && dimension != -1 && dimension != 1) { + XaeroPlus.LOGGER.error("Unexpected dimension ID: " + dimension + ". Not initializing saving cache: {}.", databaseName); + return; + } + this.currentWorldId = worldId; + final ChunkHighlightDatabase db = new ChunkHighlightDatabase(worldId, databaseName); + this.database = Optional.of(db); + this.netherCache = Optional.of(new ChunkHighlightCacheDimensionHandler(-1, db)); + this.overworldCache = Optional.of(new ChunkHighlightCacheDimensionHandler(0, db)); + this.endCache = Optional.of(new ChunkHighlightCacheDimensionHandler(1, db)); + this.worldCacheInitialized = true; + loadChunksInActualDimension(); + } catch (final Exception e) { + // expected on game launch + } + } + + private void loadChunksInActualDimension() { + getCacheForCurrentDimension().ifPresent( + c -> c.setWindow(ChunkUtils.actualPlayerRegionX(), ChunkUtils.actualPlayerRegionZ(), defaultRegionWindowSize)); + } + + public void onEnable() { + if (!worldCacheInitialized) { + initializeWorld(); + } + } + + public void onDisable() { + Futures.whenAllComplete(saveAllChunks()).call(() -> { + reset(); + return null; + }); + } + + int tickCounter = 0; + public void handleTick() { + if (!worldCacheInitialized) return; + // limit so we don't overflow + if (tickCounter > 2400) tickCounter = 0; + if (tickCounter++ % 30 != 0) { // run once every 1.5 seconds + return; + } + // autosave current window every 60 seconds + if (tickCounter % 1200 == 0) { + getAllCaches().forEach(ChunkHighlightCacheDimensionHandler::writeAllHighlightsToDatabase); + return; + } + + Optional guiMapOptional = getGuiMap(); + if (guiMapOptional.isPresent()) { + final GuiMap guiMap = guiMapOptional.get(); + final int mapDimension = getCurrentlyViewedDimension(); + final int mapCenterX = getGuiMapCenterRegionX(guiMap); + final int mapCenterZ = getGuiMapCenterRegionZ(guiMap); + final int mapSize = getGuiMapRegionSize(guiMap); + getCacheForDimension(mapDimension).ifPresent(c -> c.setWindow(mapCenterX, mapCenterZ, mapSize)); + getCachesExceptDimension(mapDimension).forEach(cache -> cache.setWindow(0, 0, 0)); + } else { + getCacheForDimension(getCurrentlyViewedDimension()).ifPresent(c -> c.setWindow(ChunkUtils.getPlayerRegionX(), ChunkUtils.getPlayerRegionZ(), defaultRegionWindowSize)); + getCachesExceptDimension(getCurrentlyViewedDimension()).forEach(cache -> cache.setWindow(0, 0, 0)); + } + } + + public void removeHighlight(final int x, final int z) { + try { + getCacheForCurrentDimension().ifPresent(c -> c.removeHighlight(x, z)); + } catch (final Exception e) { + XaeroPlus.LOGGER.error("Error removing portal marker from saving cache", e); + } + } +} diff --git a/src/main/java/xaeroplus/util/HighlightAtChunkPos.java b/src/main/java/xaeroplus/util/highlights/HighlightAtChunkPos.java similarity index 94% rename from src/main/java/xaeroplus/util/HighlightAtChunkPos.java rename to src/main/java/xaeroplus/util/highlights/HighlightAtChunkPos.java index 4959551e..326ff281 100644 --- a/src/main/java/xaeroplus/util/HighlightAtChunkPos.java +++ b/src/main/java/xaeroplus/util/highlights/HighlightAtChunkPos.java @@ -1,4 +1,4 @@ -package xaeroplus.util; +package xaeroplus.util.highlights; import java.util.Objects; diff --git a/src/main/java/xaeroplus/util/RegionRenderPos.java b/src/main/java/xaeroplus/util/highlights/RegionRenderPos.java similarity index 95% rename from src/main/java/xaeroplus/util/RegionRenderPos.java rename to src/main/java/xaeroplus/util/highlights/RegionRenderPos.java index 2f2b9514..ed86ec74 100644 --- a/src/main/java/xaeroplus/util/RegionRenderPos.java +++ b/src/main/java/xaeroplus/util/highlights/RegionRenderPos.java @@ -1,4 +1,4 @@ -package xaeroplus.util; +package xaeroplus.util.highlights; import java.util.Objects; diff --git a/src/main/java/xaeroplus/util/newchunks/NewChunksCache.java b/src/main/java/xaeroplus/util/newchunks/NewChunksCache.java index 7db6d642..689c7d34 100644 --- a/src/main/java/xaeroplus/util/newchunks/NewChunksCache.java +++ b/src/main/java/xaeroplus/util/newchunks/NewChunksCache.java @@ -1,12 +1,13 @@ package xaeroplus.util.newchunks; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; -import xaeroplus.util.HighlightAtChunkPos; +import xaeroplus.util.highlights.HighlightAtChunkPos; import java.util.List; public interface NewChunksCache { void addNewChunk(final int x, final int z); + void addNewChunk(int x, int z, long foundTime, int dimensionId); boolean isNewChunk(final int chunkPosX, final int chunkPosZ, final int dimensionId); List getNewChunksInRegion(final int leafRegionX, final int leafRegionZ, final int level, int dimension); void handleWorldChange(); diff --git a/src/main/java/xaeroplus/util/newchunks/NewChunksLocalCache.java b/src/main/java/xaeroplus/util/newchunks/NewChunksLocalCache.java index f8e455c2..005012ae 100644 --- a/src/main/java/xaeroplus/util/newchunks/NewChunksLocalCache.java +++ b/src/main/java/xaeroplus/util/newchunks/NewChunksLocalCache.java @@ -1,82 +1,67 @@ package xaeroplus.util.newchunks; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; -import xaeroplus.XaeroPlus; -import xaeroplus.util.HighlightAtChunkPos; +import xaeroplus.util.highlights.ChunkHighlightLocalCache; +import xaeroplus.util.highlights.HighlightAtChunkPos; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; import static xaeroplus.util.ChunkUtils.getActualDimension; -public class NewChunksLocalCache extends NewChunksBaseCacheHandler implements NewChunksCache { - private final Long2LongOpenHashMap chunks = new Long2LongOpenHashMap(); - private static final int maxNumber = 5000; - private final ReadWriteLock lock = new ReentrantReadWriteLock(); +public class NewChunksLocalCache implements NewChunksCache { + private final ChunkHighlightLocalCache delegate = new ChunkHighlightLocalCache(); @Override public void addNewChunk(final int x, final int z) { - limitChunksSize(); - super.addNewChunk(x, z); + delegate.addHighlight(x, z); } - private void limitChunksSize() { - try { - if (chunks.size() > maxNumber) { - if (lock.readLock().tryLock(1, TimeUnit.SECONDS)) { - // remove oldest 500 chunks - final List toRemove = chunks.long2LongEntrySet().stream() - .sorted(Map.Entry.comparingByValue()) - .limit(500) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - lock.readLock().unlock(); - if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) { - toRemove.forEach(l -> chunks.remove((long) l)); - lock.writeLock().unlock(); - } - } - } - } catch (final Exception e) { - XaeroPlus.LOGGER.error("Error limiting local cache size", e); - } + @Override + public void addNewChunk(final int x, final int z, final long foundTime, final int dimensionId) { + delegate.addHighlight(x, z, foundTime); } @Override public boolean isNewChunk(final int chunkPosX, final int chunkPosZ, final int dimensionId) { // local cache doesn't support cross-dimensional lookups if (dimensionId != getActualDimension()) return false; - return super.isNewChunk(chunkPosX, chunkPosZ); + return delegate.isHighlighted(chunkPosX, chunkPosZ); } @Override public List getNewChunksInRegion(final int leafRegionX, final int leafRegionZ, final int level, final int dimension) { if (dimension != getActualDimension()) return Collections.emptyList(); - return super.getNewChunksInRegion(leafRegionX, leafRegionZ, level); + return delegate.getHighlightsInRegion(leafRegionX, leafRegionZ, level); } @Override public void handleWorldChange() { - + delegate.handleWorldChange(); } @Override public void handleTick() { - + delegate.handleTick(); } @Override public void onEnable() { - + delegate.onEnable(); } @Override public void onDisable() { + delegate.onDisable(); + } + @Override + public Long2LongOpenHashMap getNewChunksState() { + return delegate.getHighlightsState(); + } + + @Override + public void loadPreviousState(final Long2LongOpenHashMap state) { + delegate.loadPreviousState(state); } } diff --git a/src/main/java/xaeroplus/util/newchunks/NewChunksSavingCache.java b/src/main/java/xaeroplus/util/newchunks/NewChunksSavingCache.java index 1a9613be..29f146ab 100644 --- a/src/main/java/xaeroplus/util/newchunks/NewChunksSavingCache.java +++ b/src/main/java/xaeroplus/util/newchunks/NewChunksSavingCache.java @@ -1,217 +1,66 @@ package xaeroplus.util.newchunks; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; -import xaero.map.core.XaeroWorldMapCore; -import xaero.map.gui.GuiMap; -import xaeroplus.XaeroPlus; -import xaeroplus.util.ChunkUtils; -import xaeroplus.util.HighlightAtChunkPos; +import xaeroplus.util.highlights.ChunkHighlightCacheDimensionHandler; +import xaeroplus.util.highlights.ChunkHighlightSavingCache; +import xaeroplus.util.highlights.HighlightAtChunkPos; -import java.util.Collections; import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static xaeroplus.util.ChunkUtils.getActualDimension; -import static xaeroplus.util.GuiMapHelper.*; public class NewChunksSavingCache implements NewChunksCache { + private final ChunkHighlightSavingCache delegate; - // these are initialized async - private Optional netherCache = Optional.empty(); - private Optional overworldCache = Optional.empty(); - private Optional endCache = Optional.empty(); - private Optional newChunksDatabase = Optional.empty(); - private static final int defaultRegionWindowSize = 2; // when we are only viewing the minimap - private String currentWorldId; - private boolean worldCacheInitialized = false; + public NewChunksSavingCache(final String databaseName) { + this.delegate = new ChunkHighlightSavingCache(databaseName); + } @Override public void addNewChunk(final int x, final int z) { - try { - getCacheForCurrentDimension().ifPresent(c -> c.addNewChunk(x, z)); - } catch (final Exception e) { - XaeroPlus.LOGGER.error("Error adding new chunk to saving cache", e); - } + delegate.addHighlight(x, z); } - public void addNewChunk(final int x, final int z, final long foundTime, final int dimension) { - getCacheForDimension(dimension) - .orElseThrow(() -> new RuntimeException("Dimension not found: " + dimension)) - .addNewChunk(x, z, foundTime); + @Override + public void addNewChunk(final int x, final int z, final long foundTime, final int dimensionId) { + delegate.addHighlight(x, z, foundTime, dimensionId); } @Override public boolean isNewChunk(final int chunkPosX, final int chunkPosZ, final int dimensionId) { - return getCacheForDimension(dimensionId) - .map(c -> c.isNewChunk(chunkPosX, chunkPosZ)) - .orElse(false); + return delegate.isHighlighted(chunkPosX, chunkPosZ, dimensionId); } @Override - public List getNewChunksInRegion(final int leafRegionX, final int leafRegionZ, final int level, int dimension) { - return getCacheForDimension(dimension) - .map(c -> c.getNewChunksInRegion(leafRegionX, leafRegionZ, level)) - .orElse(Collections.emptyList()); + public List getNewChunksInRegion(final int leafRegionX, final int leafRegionZ, final int level, final int dimension) { + return delegate.getHighlightsInRegion(leafRegionX, leafRegionZ, level, dimension); } @Override public void handleWorldChange() { - Futures.whenAllComplete(saveAllChunks()) - .call(() -> { - reset(); - initializeWorld(); - loadChunksInActualDimension(); - return null; - }); + delegate.handleWorldChange(); } - int tickCounter = 0; @Override public void handleTick() { - if (!worldCacheInitialized) return; - // limit so we don't overflow - if (tickCounter > 2400) tickCounter = 0; - if (tickCounter++ % 30 != 0) { // run once every 1.5 seconds - return; - } - // autosave current window every 60 seconds - if (tickCounter % 1200 == 0) { - getAllCaches().forEach(NewChunksSavingCacheDimensionHandler::writeAllChunksToDatabase); - return; - } - - Optional guiMapOptional = getGuiMap(); - if (guiMapOptional.isPresent()) { - final GuiMap guiMap = guiMapOptional.get(); - final int mapDimension = getCurrentlyViewedDimension(); - final int mapCenterX = getGuiMapCenterRegionX(guiMap); - final int mapCenterZ = getGuiMapCenterRegionZ(guiMap); - final int mapSize = getGuiMapRegionSize(guiMap); - getCacheForDimension(mapDimension).ifPresent(c -> c.setWindow(mapCenterX, mapCenterZ, mapSize)); - getCachesExceptDimension(mapDimension).forEach(cache -> cache.setWindow(0, 0, 0)); - } else { - getCacheForDimension(getCurrentlyViewedDimension()).ifPresent(c -> c.setWindow(ChunkUtils.getPlayerRegionX(), ChunkUtils.getPlayerRegionZ(), defaultRegionWindowSize)); - getCachesExceptDimension(getCurrentlyViewedDimension()).forEach(cache -> cache.setWindow(0, 0, 0)); - } + delegate.handleTick(); } @Override public void onEnable() { - initializeWorld(); + delegate.onEnable(); } @Override public void onDisable() { - Futures.whenAllComplete(saveAllChunks()).call(() -> { - reset(); - return null; - }); + delegate.onDisable(); } @Override public Long2LongOpenHashMap getNewChunksState() { - return getCacheForCurrentDimension().map(NewChunksSavingCacheDimensionHandler::getNewChunksState).orElse(new Long2LongOpenHashMap()); + return delegate.getCacheForCurrentDimension().map(ChunkHighlightCacheDimensionHandler::getHighlightsState).orElse(new Long2LongOpenHashMap()); } @Override public void loadPreviousState(final Long2LongOpenHashMap state) { - getCacheForCurrentDimension().ifPresent(c -> c.loadPreviousState(state)); - } - - public void reset() { - this.worldCacheInitialized = false; - this.currentWorldId = null; - this.netherCache.ifPresent(NewChunksSavingCacheDimensionHandler::close); - this.netherCache = Optional.empty(); - this.overworldCache.ifPresent(NewChunksSavingCacheDimensionHandler::close); - this.overworldCache = Optional.empty(); - this.endCache.ifPresent(NewChunksSavingCacheDimensionHandler::close); - this.endCache = Optional.empty(); - this.newChunksDatabase.ifPresent(NewChunksDatabase::close); - this.newChunksDatabase = Optional.empty(); - } - - private List> saveAllChunks() { - return getAllCaches().stream() - .map(NewChunksSavingCacheDimensionHandler::writeAllChunksToDatabase) - .collect(Collectors.toList()); - } - - public Optional getCacheForCurrentDimension() { - switch (getActualDimension()) { - case -1: - return netherCache; - case 0: - return overworldCache; - case 1: - return endCache; - default: - throw new RuntimeException("Unknown dimension: " + getActualDimension()); - } - } - - public Optional getCacheForDimension(final int dimension) { - switch (dimension) { - case -1: - return netherCache; - case 0: - return overworldCache; - case 1: - return endCache; - default: - throw new RuntimeException("Unknown dimension: " + dimension); - } - } - - private List getAllCaches() { - return Stream.of(netherCache, overworldCache, endCache).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); - } - - public List getCachesExceptDimension(final int dimension) { - switch (dimension) { - case -1: - return Stream.of(overworldCache, endCache).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); - case 0: - return Stream.of(netherCache, endCache).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); - case 1: - return Stream.of(netherCache, overworldCache).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); - default: - throw new RuntimeException("Unknown dimension: " + dimension); - } - } - - private void initializeWorld() { - try { - final String worldId = XaeroWorldMapCore.currentSession.getMapProcessor().getCurrentWorldId(); - final String mwId = XaeroWorldMapCore.currentSession.getMapProcessor().getCurrentMWId(); - if (worldId == null || mwId == null) return; - final int dimension = getActualDimension(); - if (dimension != 0 && dimension != -1 && dimension != 1) { - XaeroPlus.LOGGER.error("Unexpected dimension ID: " + dimension + ". Disable Save/Load NewChunks to Disk to restore functionality."); - return; - } - ; - this.currentWorldId = worldId; - final NewChunksDatabase db = new NewChunksDatabase(worldId); - this.newChunksDatabase = Optional.of(db); - this.netherCache = Optional.of(new NewChunksSavingCacheDimensionHandler(-1, db)); - this.overworldCache = Optional.of(new NewChunksSavingCacheDimensionHandler(0, db)); - this.endCache = Optional.of(new NewChunksSavingCacheDimensionHandler(1, db)); - this.worldCacheInitialized = true; - NewChunksV1Converter.convert(this, worldId, mwId); - loadChunksInActualDimension(); - } catch (final Exception e) { - // expected on game launch - } - } - - private void loadChunksInActualDimension() { - getCacheForCurrentDimension() - .ifPresent(c -> - c.setWindow(ChunkUtils.actualPlayerRegionX(), ChunkUtils.actualPlayerRegionZ(), defaultRegionWindowSize)); + delegate.getCacheForCurrentDimension().ifPresent(c -> c.loadPreviousState(state)); } } diff --git a/src/main/resources/assets/xaeroplus/lang/en_us.lang b/src/main/resources/assets/xaeroplus/lang/en_us.lang index f6f600c4..496ddd7f 100644 --- a/src/main/resources/assets/xaeroplus/lang/en_us.lang +++ b/src/main/resources/assets/xaeroplus/lang/en_us.lang @@ -26,6 +26,12 @@ setting.world_map.new_chunks_opacity=New Chunks Opacity setting.world_map.new_chunks_opacity.tooltip=Changes the color opacity of NewChunks. setting.world_map.new_chunks_color=New Chunks Color setting.world_map.new_chunks_color.tooltip=Changes the color of NewChunks. +setting.world_map.portals=Portal Highlights +setting.world_map.portals.tooltip=Highlights chunks where portals were found on the Minimap and WorldMap. +setting.world_map.portals_opacity=Portals Opacity +setting.world_map.portals_opacity.tooltip=Changes the color opacity of Portal Highlights. +setting.world_map.portals_color=Portals Color +setting.world_map.portals_color.tooltip=Changes the color of Portal highlights. setting.world_map.portal_skip_detection=PortalSkip Detection setting.world_map.portal_skip_detection.tooltip=Highlights chunks where portals could have been loaded into. \n This is useful for basehunting to detect where players could switch dimensions along a trail to avoid hunters. \n One thing to note: 2b2t's view distance is not large enough to detect portal skip areas. You need to load surrounding chunks - specifically a 15x15 chunk area setting.world_map.portal_skip_opacity=PortalSkip Opacity diff --git a/src/main/resources/mixins.xaeroplus.json b/src/main/resources/mixins.xaeroplus.json index eeb5b31f..af400267 100644 --- a/src/main/resources/mixins.xaeroplus.json +++ b/src/main/resources/mixins.xaeroplus.json @@ -46,6 +46,7 @@ "MixinWorldDataReader", "MixinWorldMapGuiSettings", "MixinWorldMapModSettings", + "mc.MixinNetHandlerPlayClient", "mc.MixinNetworkManager" ] }