diff options
Diffstat (limited to 'src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/Navigation.java')
-rw-r--r-- | src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/Navigation.java | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/Navigation.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/Navigation.java new file mode 100644 index 00000000..6244c32c --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/Navigation.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2022 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscfeatures; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.NotEnoughUpdates; +import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils; +import io.github.moulberry.notenoughupdates.events.RepositoryReloadEvent; +import io.github.moulberry.notenoughupdates.listener.RenderListener; +import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.LocationChangeEvent; +import io.github.moulberry.notenoughupdates.miscgui.GuiNavigation; +import io.github.moulberry.notenoughupdates.util.ItemUtils; +import io.github.moulberry.notenoughupdates.util.JsonUtils; +import io.github.moulberry.notenoughupdates.util.NotificationHandler; +import io.github.moulberry.notenoughupdates.util.SBInfo; +import io.github.moulberry.notenoughupdates.util.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.inventory.GuiChest; +import net.minecraft.inventory.ContainerChest; +import net.minecraft.item.ItemStack; +import net.minecraft.util.BlockPos; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.Vec3i; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.InputEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import org.lwjgl.input.Keyboard; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class Navigation { + + private List<Teleporter> teleporters = new ArrayList<>(); + private Map<String, String> areaNames = new HashMap<>(); + private Map<String, WarpPoint> warps = new HashMap<>(); + private Map<String, JsonObject> waypoints = new HashMap<>(); + + public Map<String, JsonObject> getWaypoints() { + return waypoints; + } + + public static class WarpPoint { + public final BlockPos blockPos; + public final String warpName, modeName; + + public WarpPoint(double x, double y, double z, String warpName, String modeName) { + this.blockPos = new BlockPos(x, y, z); + this.warpName = warpName; + this.modeName = modeName; + } + } + + public static class Teleporter { + public final double x, y, z; + public final String from, to; + + public Teleporter(double x, double y, double z, String from, String to) { + this.x = x; + this.y = y; + this.z = z; + this.from = from; + this.to = to; + } + } + + private NotEnoughUpdates neu; + + public Navigation(NotEnoughUpdates notEnoughUpdates) { + neu = notEnoughUpdates; + } + + /* JsonObject (x,y,z,island,displayname) */ + private JsonObject currentlyTrackedWaypoint = null; + private BlockPos position = null; + private String island = null; + private String displayName = null; + private String internalname = null; + private String warpAgainTo = null; + private int lastInvHashcode = 0; + private Instant warpAgainTiming = null; + + private Teleporter nextTeleporter = null; + + public boolean isValidWaypoint(JsonObject object) { + return object.has("x") + && object.has("y") + && object.has("z") + && object.has("island") + && object.has("displayname") + && object.has("internalname"); + } + + public void trackWaypoint(String trackNow) { + if (trackNow == null) { + trackWaypoint((JsonObject) null); + } else { + JsonObject jsonObject = waypoints.get(trackNow); + if (jsonObject == null) { + showError( + "Could not track waypoint " + trackNow + ". This is likely due to an outdated or broken repository.", + true + ); + return; + } + trackWaypoint(jsonObject); + } + } + + public void trackWaypoint(JsonObject trackNow) { + if (trackNow != null && !isValidWaypoint(trackNow)) { + showError("Could not track waypoint. This is likely due to an outdated or broken repository.", true); + return; + } + if (!neu.config.hidden.hasOpenedWaypointMenu) + NotificationHandler.displayNotification(Arrays.asList( + "You just tracked a waypoint.", + "Press [N] to open the waypoint menu to untrack it", + "or to find other waypoints to track.", + "Press [X] to close this message." + ), true, false); + currentlyTrackedWaypoint = trackNow; + updateData(); + } + + @SubscribeEvent + public void onRepositoryReload(RepositoryReloadEvent event) { + JsonObject obj = Utils.getConstant("islands", neu.manager.gson); + List<Teleporter> teleporters = JsonUtils.getJsonArrayOrEmpty(obj, "teleporters", jsonElement -> { + JsonObject teleporterObj = jsonElement.getAsJsonObject(); + return new Teleporter( + teleporterObj.get("x").getAsDouble(), + teleporterObj.get("y").getAsDouble(), + teleporterObj.get("z").getAsDouble(), + teleporterObj.get("from").getAsString(), + teleporterObj.get("to").getAsString() + ); + }); + for (Teleporter teleporter : teleporters) { + if (teleporter.from.equals(teleporter.to)) { + showError("Found self referencing teleporter: " + teleporter.from, true); + } + } + this.teleporters = teleporters; + this.waypoints = NotEnoughUpdates.INSTANCE.manager + .getItemInformation().values().stream() + .filter(this::isValidWaypoint) + .collect(Collectors.toMap(it -> it.get("internalname").getAsString(), it -> it)); + this.areaNames = JsonUtils.transformJsonObjectToMap(obj.getAsJsonObject("area_names"), JsonElement::getAsString); + this.warps = JsonUtils.getJsonArrayOrEmpty(obj, "island_warps", jsonElement -> { + JsonObject warpObject = jsonElement.getAsJsonObject(); + return new WarpPoint( + warpObject.get("x").getAsDouble(), + warpObject.get("y").getAsDouble(), + warpObject.get("z").getAsDouble(), + warpObject.get("warp").getAsString(), + warpObject.get("mode").getAsString() + ); + }).stream().collect(Collectors.toMap(it -> it.warpName, it -> it)); + } + + @SubscribeEvent + public void onKeybindPressed(InputEvent.KeyInputEvent event) { + if (!Keyboard.getEventKeyState()) return; + int key = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); + if (neu.config.misc.keybindWaypoint == key) { + if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) { + if (currentlyTrackedWaypoint != null) { + useWarpCommand(); + } + } else { + Minecraft.getMinecraft().displayGuiScreen(new GuiNavigation()); + } + } + } + + @SubscribeEvent + public void onGuiTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.START) return; + if (Minecraft.getMinecraft().theWorld == null) return; + if (Minecraft.getMinecraft().thePlayer == null) return; + if (neu.config.getProfileSpecific() == null) return; + + if (Minecraft.getMinecraft().currentScreen instanceof GuiChest && RenderListener.inventoryLoaded) { + GuiChest currentScreen = (GuiChest) Minecraft.getMinecraft().currentScreen; + ContainerChest container = (ContainerChest) currentScreen.inventorySlots; + if (container.getLowerChestInventory().getDisplayName().getUnformattedText().equals("Fast Travel")) { + int hashCode = container.getInventory().hashCode(); + if (hashCode == lastInvHashcode) return; + lastInvHashcode = hashCode; + for (ItemStack stackInSlot : container.getInventory()) { + if (stackInSlot == null) continue; + List<String> lore = ItemUtils.getLore(stackInSlot); + if (lore.isEmpty()) + continue; + String warpLine = Utils.cleanColour(lore.get(0)); + if (!warpLine.startsWith("/warp ")) continue; + String warpName = warpLine.substring(6); + boolean isUnlocked = !lore.contains("§cWarp not unlocked!"); + neu.config.getProfileSpecific().unlockedWarpScrolls.put(warpName, isUnlocked); + } + } + } + } + + public Map<String, WarpPoint> getWarps() { + return warps; + } + + public WarpPoint getClosestWarp(String mode, Vec3i position, boolean checkAvailable) { + double minDistance = -1; + HashMap<String, Boolean> unlockedWarpScrolls = neu.config.getProfileSpecific().unlockedWarpScrolls; + WarpPoint minWarp = null; + for (WarpPoint value : warps.values()) { + if (value.modeName.equals(mode)) { + if (checkAvailable && !unlockedWarpScrolls.getOrDefault(value.warpName, false)) + continue; + double distance = value.blockPos.distanceSq(position); + if (distance < minDistance || minWarp == null) { + minDistance = distance; + minWarp = value; + } + } + } + return minWarp; + } + + public void useWarpCommand() { + EntityPlayerSP thePlayer = Minecraft.getMinecraft().thePlayer; + if (currentlyTrackedWaypoint == null || thePlayer == null) return; + WarpPoint closestWarp = getClosestWarp(island, position, true); + if (closestWarp == null) { + showError("Could not find an unlocked warp that could be used.", false); + return; + } + + if (!island.equals(SBInfo.getInstance().mode)) { + warpAgainTiming = Instant.now(); + warpAgainTo = closestWarp.warpName; + } else if (thePlayer.getDistanceSq(position) < closestWarp.blockPos.distanceSq(position)) { + showError("You are already on the same island and nearer than the closest unlocked warp scroll.", false); + return; + } + thePlayer.sendChatMessage("/warp " + closestWarp.warpName); + } + + @SubscribeEvent + public void onTeleportDone(EntityJoinWorldEvent event) { + if (neu.config.misc.warpTwice + && event.entity == Minecraft.getMinecraft().thePlayer + && warpAgainTo != null + && warpAgainTiming != null + && warpAgainTiming.plusSeconds(1).isAfter(Instant.now())) { + warpAgainTiming = null; + String savedWarpAgain = warpAgainTo; + warpAgainTo = null; + Minecraft.getMinecraft().thePlayer.sendChatMessage("/warp " + savedWarpAgain); + } + } + + public String getNameForAreaMode(String mode) { + return areaNames.get(mode); + } + + public String getNameForAreaModeOrUnknown(String mode) { + return areaNames.getOrDefault(mode, "Unknown"); + } + + public void untrackWaypoint() { + trackWaypoint((JsonObject) null); + } + + public JsonObject getTrackedWaypoint() { + return currentlyTrackedWaypoint; + } + + public String getIsland() { + return island; + } + + public String getDisplayName() { + return displayName; + } + + public BlockPos getPosition() { + return position; + } + + public String getInternalname() { + return internalname; + } + + private void updateData() { + if (currentlyTrackedWaypoint == null) { + position = null; + island = null; + displayName = null; + nextTeleporter = null; + internalname = null; + return; + } + position = new BlockPos( + currentlyTrackedWaypoint.get("x").getAsDouble(), + currentlyTrackedWaypoint.get("y").getAsDouble(), + currentlyTrackedWaypoint.get("z").getAsDouble() + ); + internalname = currentlyTrackedWaypoint.get("internalname").getAsString(); + island = currentlyTrackedWaypoint.get("island").getAsString(); + displayName = currentlyTrackedWaypoint.get("displayname").getAsString(); + recalculateNextTeleporter(SBInfo.getInstance().mode); + } + + @SubscribeEvent + public void onLocationChange(LocationChangeEvent event) { + recalculateNextTeleporter(event.newLocation); + } + + public Teleporter recalculateNextTeleporter(String from) { + String to = island; + if (from == null || to == null) return null; + List<Teleporter> nextTeleporter = findNextTeleporter0(from, to, new HashSet<>()); + if (nextTeleporter == null || nextTeleporter.isEmpty()) { + this.nextTeleporter = null; + } else { + this.nextTeleporter = nextTeleporter.get(0); + } + return this.nextTeleporter; + } + + private List<Teleporter> findNextTeleporter0(String from, String to, Set<String> visited) { + if (from.equals(to)) return new ArrayList<>(); + if (visited.contains(from)) return null; + visited.add(from); + int minPathLength = 0; + List<Teleporter> minPath = null; + for (Teleporter teleporter : teleporters) { + if (!teleporter.from.equals(from)) continue; + List<Teleporter> nextTeleporter0 = findNextTeleporter0(teleporter.to, to, visited); + if (nextTeleporter0 == null) continue; + if (minPath == null || nextTeleporter0.size() < minPathLength) { + minPathLength = nextTeleporter0.size(); + nextTeleporter0.add(0, teleporter); + minPath = nextTeleporter0; + } + } + visited.remove(from); + return minPath; + } + + private void showError(String message, boolean log) { + EntityPlayerSP player = Minecraft.getMinecraft().thePlayer; + if (player != null) + player.addChatMessage(new ChatComponentText(EnumChatFormatting.DARK_RED + + "[NEU-Waypoint] " + message)); + if (log) + new RuntimeException("[NEU-Waypoint] " + message).printStackTrace(); + } + + @SubscribeEvent + public void onEvent(TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.END && currentlyTrackedWaypoint != null + && NotEnoughUpdates.INSTANCE.config.misc.untrackCloseWaypoints + && island.equals(SBInfo.getInstance().mode)) { + EntityPlayerSP thePlayer = Minecraft.getMinecraft().thePlayer; + if (thePlayer != null && thePlayer.getDistanceSq(position) < 16) + untrackWaypoint(); + } + } + + @SubscribeEvent + public void onRenderLast(RenderWorldLastEvent event) { + if (currentlyTrackedWaypoint != null) { + if (island.equals(SBInfo.getInstance().mode)) { + RenderUtils.renderWayPoint(displayName, position, event.partialTicks); + } else if (nextTeleporter != null) { + String to = nextTeleporter.to; + String toName = getNameForAreaModeOrUnknown(to); + RenderUtils.renderWayPoint( + Arrays.asList("Teleporter to " + toName, "(towards " + displayName + "§r)"), + new BlockPos( + nextTeleporter.x, + nextTeleporter.y, + nextTeleporter.z + ), event.partialTicks + ); + } + } + } +} |