aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/io/github/moulberry/notenoughupdates/miscfeatures')
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneWarning.java215
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AntiCoopAdd.java62
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionBINWarning.java158
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java178
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionSortModeWarning.java107
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/BetterContainers.java38
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CollectionLogManager.java44
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java115
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java573
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalOverlay.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java1108
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java147
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomSkulls.java38
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DamageCommas.java48
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DwarvenMinesWaypoints.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java210
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FairySouls.java492
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FancyPortals.java293
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FishingHelper.java157
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCustomizeManager.java27
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemRarityHalo.java25
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NPCRetexturing.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/Navigation.java418
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NewApiKeyHelper.java43
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java489
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java173
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SlotLocking.java118
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StorageManager.java126
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StreamerMode.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SunTzu.java22
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CrystalHollowsTextures.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBiomes.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBlockSounds.java20
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/DwarvenMinesTextures.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/IslandZoneSubdivider.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/LocationChangeEvent.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/SpecialBlockZone.java19
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/AgeModifier.java49
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/ChargedModifier.java36
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewer.java245
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewerModifier.java27
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EquipmentModifier.java91
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/GUIClientPlayer.java70
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/HorseModifier.java85
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/InvisibleModifier.java31
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/NameModifier.java34
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/RidingModifier.java33
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/SkinModifier.java65
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/WitherModifier.java48
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java256
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/LinuxBasedUpdater.java47
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/SCUCompatUpdater.java100
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/UpdateLoader.java141
56 files changed, 5778 insertions, 1271 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneWarning.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneWarning.java
new file mode 100644
index 00000000..67ac4f4c
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneWarning.java
@@ -0,0 +1,215 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.GuiElement;
+import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils;
+import io.github.moulberry.notenoughupdates.core.util.render.TextRenderUtils;
+import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
+import io.github.moulberry.notenoughupdates.util.ItemUtils;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.gui.inventory.GuiChest;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.util.List;
+
+public class AbiphoneWarning extends GuiElement {
+ private static final AbiphoneWarning INSTANCE = new AbiphoneWarning();
+
+ private boolean showWarning = false;
+ private String contactName = null;
+ private int contactSlot = -1;
+
+ public static AbiphoneWarning getInstance() {
+ return INSTANCE;
+ }
+
+ private boolean shouldPerformCheck() {
+ if (!NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) {
+ showWarning = false;
+ return false;
+ }
+
+ if (Utils.getOpenChestName().startsWith("Abiphone ")) {
+ return true;
+ } else {
+ showWarning = false;
+ return false;
+ }
+ }
+
+ public boolean shouldShow() {
+ return shouldPerformCheck() && showWarning;
+ }
+
+ @SubscribeEvent
+ public void onMouseClick(SlotClickEvent event) {
+ if (!shouldPerformCheck()) return;
+ if (!NotEnoughUpdates.INSTANCE.config.misc.abiphoneWarning) return;
+ if (event.slotId == -999) return;
+ if (event.clickedButton == 0) return;
+
+ GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
+
+ ItemStack clickedContact = chest.inventorySlots.getSlot(event.slotId).getStack();
+ if (clickedContact == null) return;
+
+ List<String> list = ItemUtils.getLore(clickedContact);
+ if (list.isEmpty()) return;
+
+ String last = list.get(list.size() - 1);
+ if (last.contains("Right-click to remove contact!")) {
+ showWarning = true;
+ contactName = clickedContact.getDisplayName();
+ contactSlot = event.slotId;
+ event.setCanceled(true);
+ }
+ }
+
+ public void overrideIsMouseOverSlot(Slot slot, int mouseX, int mouseY, CallbackInfoReturnable<Boolean> cir) {
+ if (shouldShow()) {
+ cir.setReturnValue(false);
+ }
+ }
+
+ @Override
+ public void render() {
+ final ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft());
+ final int width = scaledResolution.getScaledWidth();
+ final int height = scaledResolution.getScaledHeight();
+
+ GlStateManager.disableLighting();
+
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(0, 0, 500);
+
+ Gui.drawRect(0, 0, width, height, 0x80000000);
+
+ RenderUtils.drawFloatingRectDark(width / 2 - 90, height / 2 - 45, 180, 90);
+
+ int neuLength = Minecraft.getMinecraft().fontRendererObj.getStringWidth("\u00a7lNEU");
+ Minecraft.getMinecraft().fontRendererObj.drawString(
+ "\u00a7lNEU",
+ width / 2 + 90 - neuLength - 3,
+ height / 2 - 45 + 4,
+ 0xff000000
+ );
+
+ TextRenderUtils.drawStringCenteredScaledMaxWidth("Are you SURE?", Minecraft.getMinecraft().fontRendererObj,
+ width / 2, height / 2 - 45 + 10, false, 170, 0xffff4040
+ );
+
+ String sellLine = "\u00a77[ \u00a7r" + contactName + "\u00a77 ]";
+
+ TextRenderUtils.drawStringCenteredScaledMaxWidth(sellLine, Minecraft.getMinecraft().fontRendererObj,
+ width / 2, height / 2 - 45 + 25, false, 170, 0xffffffff
+ );
+
+ TextRenderUtils.drawStringCenteredScaledMaxWidth(
+ "Continue removing this contact?",
+ Minecraft.getMinecraft().fontRendererObj,
+ width / 2,
+ height / 2 - 45 + 50,
+ false,
+ 170,
+ 0xffa0a0a0
+ );
+
+ RenderUtils.drawFloatingRectDark(width / 2 - 43, height / 2 + 23, 40, 16, false);
+ RenderUtils.drawFloatingRectDark(width / 2 + 3, height / 2 + 23, 40, 16, false);
+
+ TextRenderUtils.drawStringCenteredScaledMaxWidth(
+ EnumChatFormatting.GREEN + "[Y]es",
+ Minecraft.getMinecraft().fontRendererObj,
+ width / 2 - 23,
+ height / 2 + 31,
+ true,
+ 36,
+ 0xff00ff00
+ );
+ TextRenderUtils.drawStringCenteredScaledMaxWidth(
+ EnumChatFormatting.RED + "[N]o",
+ Minecraft.getMinecraft().fontRendererObj,
+ width / 2 + 23,
+ height / 2 + 31,
+ true,
+ 36,
+ 0xffff0000
+ );
+
+ GlStateManager.popMatrix();
+ }
+
+ @Override
+ public boolean mouseInput(int mouseX, int mouseY) {
+ final ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft());
+ final int width = scaledResolution.getScaledWidth();
+ final int height = scaledResolution.getScaledHeight();
+
+ if (Mouse.getEventButtonState()) {
+ if (mouseY >= height / 2 + 23 && mouseY <= height / 2 + 23 + 16) {
+ if (mouseX >= width / 2 - 43 && mouseX <= width / 2 - 3) {
+ makeClick();
+ }
+ showWarning = false;
+ }
+
+ if (mouseX < width / 2 - 90 || mouseX > width / 2 + 90 ||
+ mouseY < height / 2 - 45 || mouseY > height / 2 + 45) {
+ showWarning = false;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean keyboardInput() {
+ if (!Keyboard.getEventKeyState()) {
+ if (Keyboard.getEventKey() == Keyboard.KEY_Y || Keyboard.getEventKey() == Keyboard.KEY_RETURN) {
+ makeClick();
+ }
+ showWarning = false;
+ }
+
+ return false;
+ }
+
+ private void makeClick() {
+ if (contactSlot != -1) {
+ GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
+ Minecraft.getMinecraft().playerController.windowClick(chest.inventorySlots.windowId,
+ contactSlot, 1, 0, Minecraft.getMinecraft().thePlayer
+ );
+ contactSlot = -1;
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AntiCoopAdd.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AntiCoopAdd.java
new file mode 100644
index 00000000..70ac4489
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AntiCoopAdd.java
@@ -0,0 +1,62 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.event.ClickEvent;
+import net.minecraft.event.HoverEvent;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+public class AntiCoopAdd {
+
+ @SubscribeEvent
+ public void onMouseClick(SlotClickEvent event) {
+ if (!NotEnoughUpdates.INSTANCE.config.misc.coopWarning) return;
+ if (event.slotId == -999) return;
+ if (!Utils.getOpenChestName().contains("Profile")) return;
+
+ ItemStack stack = event.slot.getStack();
+ if (stack == null) return;
+ if (stack.getItem() == Items.diamond && stack.getDisplayName() != null && stack.getDisplayName().contains(
+ "Co-op Request")) {
+ String ign = Utils.getOpenChestName().split("'s Profile")[0];
+ ChatComponentText storageMessage = new ChatComponentText(
+ EnumChatFormatting.YELLOW + "[NEU] " + EnumChatFormatting.YELLOW +
+ "You just clicked on the Co-op add button. If you want to coop add this person, click this chat message");
+ storageMessage.setChatStyle(Utils.createClickStyle(ClickEvent.Action.RUN_COMMAND, "/coopadd " + ign));
+ storageMessage.setChatStyle(storageMessage.getChatStyle().setChatHoverEvent(
+ new HoverEvent(
+ HoverEvent.Action.SHOW_TEXT,
+ new ChatComponentText(EnumChatFormatting.YELLOW + "Click to add " + ign + " to your coop")
+ )));
+ ChatComponentText storageChatMessage = new ChatComponentText("");
+ storageChatMessage.appendSibling(storageMessage);
+ Minecraft.getMinecraft().thePlayer.addChatMessage(storageChatMessage);
+ event.setCanceled(true);
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionBINWarning.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionBINWarning.java
index c76a22b4..ad9df7af 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionBINWarning.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionBINWarning.java
@@ -1,3 +1,22 @@
+/*
+ * 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.JsonObject;
@@ -5,7 +24,7 @@ import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.core.GuiElement;
import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils;
import io.github.moulberry.notenoughupdates.core.util.render.TextRenderUtils;
-import io.github.moulberry.notenoughupdates.util.SBInfo;
+import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
@@ -15,6 +34,7 @@ import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -35,19 +55,20 @@ public class AuctionBINWarning extends GuiElement {
private boolean showWarning = false;
private List<String> sellingTooltip;
private String sellingName;
- private int sellingPrice;
- private int lowestPrice;
+ private long sellingPrice;
+ private long lowestPrice;
+ private long buyPercentage;
+ private int sellStackAmount;
+ private boolean isALoss = true;
private boolean shouldPerformCheck() {
- if (!NotEnoughUpdates.INSTANCE.config.ahTweaks.enableBINWarning ||
- !NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) {
+ if (!NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) {
sellingTooltip = null;
showWarning = false;
return false;
}
- if (Minecraft.getMinecraft().currentScreen instanceof GuiChest &&
- SBInfo.getInstance().lastOpenContainerName.startsWith("Create BIN Auction")) {
+ if (Utils.getOpenChestName().startsWith("Create BIN Auction")) {
return true;
} else {
sellingTooltip = null;
@@ -60,65 +81,73 @@ public class AuctionBINWarning extends GuiElement {
return shouldPerformCheck() && showWarning;
}
- public boolean onMouseClick(Slot slotIn, int slotId, int clickedButton, int clickType) {
- if (!shouldPerformCheck()) return false;
+ @SubscribeEvent
+ public void onMouseClick(SlotClickEvent event) {
+ if (!shouldPerformCheck()) return;
- if (slotId == 29) {
- GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
+ if (event.slotId != 29) {
+ return;
+ }
- sellingPrice = -1;
+ sellingPrice = -1;
- ItemStack priceStack = chest.inventorySlots.getSlot(31).getStack();
- if (priceStack != null) {
- String displayName = priceStack.getDisplayName();
- Matcher priceMatcher = ITEM_PRICE_REGEX.matcher(displayName);
+ ItemStack priceStack = event.guiContainer.inventorySlots.getSlot(31).getStack();
+ if (priceStack != null) {
+ String displayName = priceStack.getDisplayName();
+ Matcher priceMatcher = ITEM_PRICE_REGEX.matcher(displayName);
- if (priceMatcher.matches()) {
- try {
- sellingPrice = Integer.parseInt(priceMatcher.group(1).replace(",", ""));
- } catch (NumberFormatException ignored) {
- }
+ if (priceMatcher.matches()) {
+ try {
+ sellingPrice = Long.parseLong(priceMatcher.group(1).replace(",", ""));
+ } catch (NumberFormatException ignored) {
}
}
+ }
- ItemStack sellStack = chest.inventorySlots.getSlot(13).getStack();
- String internalname = NotEnoughUpdates.INSTANCE.manager.getInternalNameForItem(sellStack);
-
- if (internalname == null) {
- return false;
- }
+ ItemStack sellStack = event.guiContainer.inventorySlots.getSlot(13).getStack();
+ String internalname = NotEnoughUpdates.INSTANCE.manager.getInternalNameForItem(sellStack);
+ sellStackAmount = sellStack.stackSize;
- JsonObject itemInfo = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(internalname);
- if (itemInfo == null || !itemInfo.has("displayname")) {
- sellingName = internalname;
- } else {
- sellingName = itemInfo.get("displayname").getAsString();
- }
+ if (internalname == null) {
+ return;
+ }
- sellingTooltip = sellStack.getTooltip(
- Minecraft.getMinecraft().thePlayer,
- Minecraft.getMinecraft().gameSettings.advancedItemTooltips
- );
+ JsonObject itemInfo = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(internalname);
+ if (itemInfo == null || !itemInfo.has("displayname")) {
+ sellingName = internalname;
+ } else {
+ sellingName = itemInfo.get("displayname").getAsString();
+ }
- lowestPrice = NotEnoughUpdates.INSTANCE.manager.auctionManager.getLowestBin(internalname);
- if (lowestPrice <= 0) {
- lowestPrice = (int) NotEnoughUpdates.INSTANCE.manager.auctionManager.getItemAvgBin(internalname);
- }
+ sellingTooltip = sellStack.getTooltip(
+ Minecraft.getMinecraft().thePlayer,
+ Minecraft.getMinecraft().gameSettings.advancedItemTooltips
+ );
- //TODO: Add option for warning if lowest price does not exist
+ lowestPrice = NotEnoughUpdates.INSTANCE.manager.auctionManager.getLowestBin(internalname);
+ if (lowestPrice <= 0) {
+ lowestPrice = (int) NotEnoughUpdates.INSTANCE.manager.auctionManager.getItemAvgBin(internalname);
+ }
- float factor = 1 - NotEnoughUpdates.INSTANCE.config.ahTweaks.warningThreshold / 100;
- if (factor < 0) factor = 0;
- if (factor > 1) factor = 1;
+ float undercutFactor = 1 - NotEnoughUpdates.INSTANCE.config.ahTweaks.warningThreshold / 100;
+ if (undercutFactor < 0) undercutFactor = 0;
+ if (undercutFactor > 1) undercutFactor = 1;
+ float overcutFactor = 1 - NotEnoughUpdates.INSTANCE.config.ahTweaks.overcutWarningThreshold / 100;
+ if (overcutFactor < 0) overcutFactor = 0;
+ if (overcutFactor > 1) overcutFactor = 1;
- if (sellingPrice > 0 && lowestPrice > 0 && sellingPrice < lowestPrice * factor) {
- showWarning = true;
- return true;
- } else {
- return false;
- }
+ if (lowestPrice == -1) {
+ return;
+ }
+ if (NotEnoughUpdates.INSTANCE.config.ahTweaks.underCutWarning &&
+ (sellingPrice > 0 && lowestPrice > 0 && sellingPrice < sellStackAmount * lowestPrice * undercutFactor)) {
+ showWarning = true;
+ event.setCanceled(true);
+ } else if (NotEnoughUpdates.INSTANCE.config.ahTweaks.overCutWarning &&
+ (sellingPrice > 0 && lowestPrice > 0 && sellingPrice > sellStackAmount * lowestPrice * (overcutFactor + 1))) {
+ showWarning = true;
+ event.setCanceled(true);
}
- return false;
}
public void overrideIsMouseOverSlot(Slot slot, int mouseX, int mouseY, CallbackInfoReturnable<Boolean> cir) {
@@ -155,10 +184,10 @@ public class AuctionBINWarning extends GuiElement {
);
String lowestPriceStr;
- if (lowestPrice > 999) {
- lowestPriceStr = Utils.shortNumberFormat(lowestPrice, 0);
+ if (lowestPrice * sellStackAmount > 999) {
+ lowestPriceStr = Utils.shortNumberFormat(lowestPrice * sellStackAmount, 0);
} else {
- lowestPriceStr = "" + lowestPrice;
+ lowestPriceStr = "" + lowestPrice * sellStackAmount;
}
String sellingPriceStr;
@@ -174,7 +203,9 @@ public class AuctionBINWarning extends GuiElement {
width / 2, height / 2 - 45 + 25, false, 170, 0xffffffff
);
TextRenderUtils.drawStringCenteredScaledMaxWidth(
- "has a lowest BIN of \u00a76" + lowestPriceStr + "\u00a7r coins",
+ (lowestPrice > 0
+ ? "has a lowest BIN of \u00a76" + lowestPriceStr + "\u00a7r coins"
+ : "\u00a7cWarning: No lowest BIN found!"),
Minecraft.getMinecraft().fontRendererObj,
width / 2,
height / 2 - 45 + 34,
@@ -183,8 +214,14 @@ public class AuctionBINWarning extends GuiElement {
0xffa0a0a0
);
- int buyPercentage = 100 - sellingPrice * 100 / lowestPrice;
- if (buyPercentage <= 0) buyPercentage = 1;
+ if (sellingPrice > lowestPrice * sellStackAmount) {
+ buyPercentage = sellingPrice * 100 / (lowestPrice * sellStackAmount);
+ isALoss = false;
+ } else if (sellingPrice < lowestPrice * sellStackAmount) {
+ buyPercentage = 100 - sellingPrice * 100 / (lowestPrice * sellStackAmount);
+ if (buyPercentage <= 0) buyPercentage = 1;
+ isALoss = true;
+ }
TextRenderUtils.drawStringCenteredScaledMaxWidth(
"Continue selling it for",
@@ -196,7 +233,8 @@ public class AuctionBINWarning extends GuiElement {
0xffa0a0a0
);
TextRenderUtils.drawStringCenteredScaledMaxWidth(
- "\u00a76" + sellingPriceStr + "\u00a7r coins? (\u00a7c-" + buyPercentage + "%\u00a7r)",
+ "\u00a76" + sellingPriceStr + "\u00a7r coins?" +
+ (lowestPrice > 0 ? "(\u00a7" + (isALoss ? "c-" : "a+") + buyPercentage + "%\u00a7r)" : ""),
Minecraft.getMinecraft().fontRendererObj,
width / 2,
height / 2 - 45 + 59,
@@ -218,7 +256,7 @@ public class AuctionBINWarning extends GuiElement {
0xff00ff00
);
TextRenderUtils.drawStringCenteredScaledMaxWidth(
- EnumChatFormatting.RED + "[n]o",
+ EnumChatFormatting.RED + "[N]o",
Minecraft.getMinecraft().fontRendererObj,
width / 2 + 23,
height / 2 + 31,
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java
new file mode 100644
index 00000000..88ca0cc8
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java
@@ -0,0 +1,178 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.util.StringUtils;
+import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.inventory.GuiChest;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.inventory.Container;
+import net.minecraft.inventory.ContainerChest;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.client.event.GuiScreenEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import org.lwjgl.opengl.GL11;
+
+public class AuctionProfit {
+
+ public static final ResourceLocation auctionProfitImage =
+ new ResourceLocation("notenoughupdates:auction_profit.png");
+
+ @SubscribeEvent
+ public void onDrawBackground(GuiScreenEvent.BackgroundDrawnEvent event) {
+ if (!inAuctionPage()) return;
+
+ Minecraft minecraft = Minecraft.getMinecraft();
+ Container inventoryContainer = minecraft.thePlayer.openContainer;
+
+ if (!(Minecraft.getMinecraft().currentScreen instanceof GuiChest)) return;
+ Gui gui = event.gui;
+ int xSize = ((AccessorGuiContainer) gui).getXSize();
+ int guiLeft = ((AccessorGuiContainer) gui).getGuiLeft();
+ int guiTop = ((AccessorGuiContainer) gui).getGuiTop();
+ minecraft.getTextureManager().bindTexture(auctionProfitImage);
+ GL11.glColor4f(1, 1, 1, 1);
+ GlStateManager.disableLighting();
+ Utils.drawTexturedRect(guiLeft + xSize + 4, guiTop, 180, 101, 0, 180 / 256f, 0, 101 / 256f, GL11.GL_NEAREST);
+
+ double coinsToCollect = 0;
+ double coinsIfAllSold = 0;
+ int expiredAuctions = 0;
+ int unclaimedAuctions = 0;
+ for (ItemStack itemStack : inventoryContainer.getInventory()) {
+ boolean isBin = false;
+ if (itemStack == null || !itemStack.hasTagCompound()) continue;
+
+ NBTTagCompound tag = itemStack.getTagCompound();
+ if (tag == null) continue;
+ NBTTagCompound display = tag.getCompoundTag("display");
+ if (!display.hasKey("Lore", 9)) continue;
+ NBTTagList lore = itemStack.getTagCompound().getCompoundTag("display").getTagList("Lore", 8);
+
+ int coinsToCheck = 0;
+ for (int i = 0; i < lore.tagCount(); i++) {
+ String line = lore.getStringTagAt(i);
+ if (line.contains("§7Buy it now")) {
+ isBin = true;
+ String s = line.split("§7Buy it now: ")[1];
+ String coinsString = s.split("coins")[0];
+ int coins = tryParse(EnumChatFormatting.getTextWithoutFormattingCodes(coinsString.trim()));
+ if (coins != 0) {
+ coinsToCheck += coins;
+ }
+ }
+
+ if (line.contains("§7Top bid: ")) {
+ String s = line.split("§7Top bid: ")[1];
+ String coinsString = s.split("coins")[0];
+ String textWithoutFormattingCodes = EnumChatFormatting.getTextWithoutFormattingCodes(coinsString.trim());
+ int coins = tryParse(textWithoutFormattingCodes);
+ if (coins != 0) {
+ coinsToCheck += coins;
+ }
+ }
+
+ if (line.contains("§7Sold for: ")) {
+ String s = line.split("§7Sold for: ")[1];
+ String coinsString = s.split("coins")[0];
+ int coins = tryParse(EnumChatFormatting.getTextWithoutFormattingCodes(coinsString.trim()));
+ if (coins != 0) {
+ if (coins > 1000000) {
+ coins /= 1.1;
+ }
+ coinsToCollect += coins;
+ }
+ }
+
+ if (line.contains("§7Status: §aSold!") || line.contains("§7Status: §aEnded!")) {
+ if (coinsToCheck != 0) {
+ if (coinsToCheck > 1000000) {
+ coinsToCheck /= 1.1;
+ }
+ coinsToCollect += coinsToCheck;
+ coinsToCheck = 0;
+ }
+ unclaimedAuctions++;
+ } else if (line.contains("§7Status: §cExpired!")) {
+ expiredAuctions++;
+ }
+
+ if (isBin && line.contains("§7Ends in") && coinsToCheck != 0) {
+ coinsIfAllSold += coinsToCheck;
+ coinsToCheck = 0;
+ }
+
+ }
+
+ }
+ int a = guiLeft + xSize + 4;
+ String unclaimedAuctionsStr = EnumChatFormatting.DARK_GREEN.toString()
+ + unclaimedAuctions + EnumChatFormatting.BOLD + EnumChatFormatting.DARK_GRAY + " Unclaimed auctions";
+ String expiredAuctionsStr =
+ EnumChatFormatting.RED.toString() + expiredAuctions + EnumChatFormatting.BOLD + EnumChatFormatting.DARK_GRAY +
+ " Expired auctions";
+
+ FontRenderer fontRendererObj = minecraft.fontRendererObj;
+ fontRendererObj.drawString(unclaimedAuctionsStr, a + 6, guiTop + 6, -1, false);
+ fontRendererObj.drawString(expiredAuctionsStr, a + 6, guiTop + 16, -1, false);
+
+ String coinsToCollectStr =
+ EnumChatFormatting.BOLD + EnumChatFormatting.DARK_GRAY.toString() + "Coins to collect: " +
+ EnumChatFormatting.RESET + EnumChatFormatting.DARK_GREEN + "" +
+ StringUtils.shortNumberFormat(coinsToCollect);
+ String valueIfSoldStr = EnumChatFormatting.BOLD + EnumChatFormatting.DARK_GRAY.toString() + "Value if all sold: " +
+ EnumChatFormatting.RESET + EnumChatFormatting.DARK_GREEN + "" +
+ StringUtils.shortNumberFormat(coinsIfAllSold);
+
+ fontRendererObj.drawString(coinsToCollectStr, a + 6, guiTop + 32, -1, false);
+ fontRendererObj.drawString(valueIfSoldStr, a + 6, guiTop + 42, -1, false);
+ }
+
+ public static Integer tryParse(String s) {
+ try {
+ return Integer.parseInt(s.replace(",", ""));
+ } catch (NumberFormatException exception) {
+ return 0;
+ }
+ }
+
+ public static boolean inAuctionPage() {
+ if (!NotEnoughUpdates.INSTANCE.config.ahTweaks.enableAhSellValue
+ || !NotEnoughUpdates.INSTANCE.isOnSkyblock()) return false;
+
+ Minecraft minecraft = Minecraft.getMinecraft();
+ if (minecraft == null || minecraft.thePlayer == null) return false;
+
+ Container inventoryContainer = minecraft.thePlayer.openContainer;
+ if (!(inventoryContainer instanceof ContainerChest)) return false;
+ ContainerChest containerChest = (ContainerChest) inventoryContainer;
+ return containerChest.getLowerChestInventory().getDisplayName()
+ .getUnformattedText().equalsIgnoreCase("Manage Auctions");
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionSortModeWarning.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionSortModeWarning.java
index 4b526185..ed05ee79 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionSortModeWarning.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionSortModeWarning.java
@@ -1,9 +1,28 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils;
import io.github.moulberry.notenoughupdates.core.util.render.TextRenderUtils;
-import io.github.moulberry.notenoughupdates.util.SBInfo;
+import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiChest;
@@ -21,55 +40,51 @@ public class AuctionSortModeWarning {
private boolean isAuctionBrowser() {
return NotEnoughUpdates.INSTANCE.config.ahTweaks.enableSortWarning &&
- Minecraft.getMinecraft().currentScreen instanceof GuiChest &&
- (SBInfo.getInstance().lastOpenContainerName.startsWith("Auctions Browser") ||
- SBInfo.getInstance().lastOpenContainerName.startsWith("Auctions: \""));
+ (Utils.getOpenChestName().startsWith("Auctions Browser") ||
+ Utils.getOpenChestName().startsWith("Auctions: \""));
}
public void onPostGuiRender() {
- if (isAuctionBrowser()) {
- GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
-
- ItemStack stack = chest.inventorySlots.getSlot(50).getStack();
-
- if (stack != null) {
- List<String> tooltip = stack.getTooltip(Minecraft.getMinecraft().thePlayer, false);
-
- String selectedSort = null;
- for (String line : tooltip) {
- if (line.startsWith("\u00a75\u00a7o\u00a7b\u25B6 ")) {
- selectedSort = Utils.cleanColour(line.substring("\u00a75\u00a7o\u00a7b\u25B6 ".length()));
- }
- }
-
- if (selectedSort != null) {
- if (!selectedSort.trim().equals("Lowest Price")) {
- GlStateManager.disableLighting();
- GlStateManager.pushMatrix();
- GlStateManager.translate(0, 0, 500);
-
- String selectedColour = "\u00a7e";
-
- if (selectedSort.trim().equals("Highest Price")) {
- selectedColour = "\u00a7c";
- }
-
- String warningText = "\u00a7aSort: " + selectedColour + selectedSort;
- int warningLength = Minecraft.getMinecraft().fontRendererObj.getStringWidth(warningText);
-
- int centerX = chest.guiLeft + chest.xSize / 2 + 9;
- int centerY = chest.guiTop + 26;
-
- RenderUtils.drawFloatingRectDark(centerX - warningLength / 2 - 4, centerY - 6,
- warningLength + 8, 12, false
- );
- TextRenderUtils.drawStringCenteredScaledMaxWidth(warningText, Minecraft.getMinecraft().fontRendererObj,
- centerX, centerY, true, chest.width / 2, 0xffffffff
- );
- GlStateManager.popMatrix();
- }
- }
+ if (!isAuctionBrowser()) return;
+ GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
+
+ ItemStack stack = chest.inventorySlots.getSlot(50).getStack();
+
+ if (stack == null) return;
+ List<String> tooltip = stack.getTooltip(Minecraft.getMinecraft().thePlayer, false);
+
+ String selectedSort = null;
+ for (String line : tooltip) {
+ if (line.startsWith("\u00a75\u00a7o\u00a7b\u25B6 ")) {
+ selectedSort = Utils.cleanColour(line.substring("\u00a75\u00a7o\u00a7b\u25B6 ".length()));
}
}
+
+ if (selectedSort == null) return;
+ if (selectedSort.trim().equals("Lowest Price")) return;
+ GlStateManager.disableLighting();
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(0, 0, 500);
+
+ String selectedColour = "\u00a7e";
+
+ if (selectedSort.trim().equals("Highest Price")) {
+ selectedColour = "\u00a7c";
+ }
+
+ String warningText = "\u00a7aSort: " + selectedColour + selectedSort;
+ int warningLength = Minecraft.getMinecraft().fontRendererObj.getStringWidth(warningText);
+
+ int centerX =
+ ((AccessorGuiContainer) chest).getGuiLeft() + ((AccessorGuiContainer) chest).getXSize() / 2 + 9;
+ int centerY = ((AccessorGuiContainer) chest).getGuiTop() + 26;
+
+ RenderUtils.drawFloatingRectDark(centerX - warningLength / 2 - 4, centerY - 6,
+ warningLength + 8, 12, false
+ );
+ TextRenderUtils.drawStringCenteredScaledMaxWidth(warningText, Minecraft.getMinecraft().fontRendererObj,
+ centerX, centerY, true, chest.width / 2, 0xffffffff
+ );
+ GlStateManager.popMatrix();
}
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/BetterContainers.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/BetterContainers.java
index c1ce229c..ad0b238a 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/BetterContainers.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/BetterContainers.java
@@ -1,8 +1,28 @@
+/*
+ * 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.JsonObject;
-import io.github.moulberry.notenoughupdates.NEUEventListener;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
+import io.github.moulberry.notenoughupdates.listener.RenderListener;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiChest;
import net.minecraft.client.renderer.GlStateManager;
@@ -18,6 +38,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import javax.imageio.ImageIO;
import java.awt.*;
@@ -69,7 +90,7 @@ public class BetterContainers {
public static void bindHook(TextureManager textureManager, ResourceLocation location) {
long currentMillis = System.currentTimeMillis();
- if (isChestOpen() && NEUEventListener.inventoryLoaded) {
+ if (isChestOpen() && RenderListener.inventoryLoaded) {
int invHashcode = lastInvHashcode;
if (currentMillis - lastHashcodeCheck > 50) {
@@ -544,4 +565,17 @@ public class BetterContainers {
return 0;
}
}
+
+ @SubscribeEvent
+ public void onMouseClick(SlotClickEvent event) {
+ if (!isOverriding()) return;
+ boolean isBlankStack = BetterContainers.isBlankStack(event.slot.slotNumber, event.slot.getStack());
+ if (!(isBlankStack ||
+ BetterContainers.isButtonStack(event.slot.slotNumber, event.slot.getStack()))) return;
+ clickSlot(event.slotId);
+ if (isBlankStack) {
+ event.usePickblockInstead();
+ }
+ }
+
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CollectionLogManager.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CollectionLogManager.java
deleted file mode 100644
index c550ce53..00000000
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CollectionLogManager.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package io.github.moulberry.notenoughupdates.miscfeatures;
-
-import io.github.moulberry.notenoughupdates.collectionlog.CollectionConstant;
-import io.github.moulberry.notenoughupdates.util.Constants;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.multiplayer.WorldClient;
-import net.minecraft.entity.Entity;
-import net.minecraft.entity.item.EntityArmorStand;
-
-import java.util.regex.Matcher;
-
-public class CollectionLogManager {
- private static final CollectionLogManager INSTANCE = new CollectionLogManager();
-
- public static CollectionLogManager getInstance() {
- return INSTANCE;
- }
-
- public void onEntityMetadataUpdated(int entityId) {
- System.out.println("entity created:" + entityId);
- WorldClient world = Minecraft.getMinecraft().theWorld;
- if (world != null) {
- Entity entity = world.getEntityByID(entityId);
-
- if (entity instanceof EntityArmorStand && entity.hasCustomName()) {
- String customName = entity.getName();
- System.out.println("got name:" + customName);
- for (CollectionConstant.DropEntry entry : Constants.COLLECTIONLOG.dropdata) {
- System.out.println("iter entry");
- if (entry.type.equalsIgnoreCase("itemdrop")) {
- Matcher matcher = entry.regex.matcher(customName);
- if (matcher.matches()) {
- System.out.println("Match found!");
- System.out.println("Count: " + matcher.group("count"));
- System.out.println("Name: " + matcher.group("itemname"));
- } else {
- System.out.println("Doesn't match: " + customName);
- }
- }
- }
- }
- }
- }
-}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java
new file mode 100644
index 00000000..f21d0c50
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java
@@ -0,0 +1,115 @@
+/*
+ * 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.common.collect.Lists;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.mixins.AccessorGuiPlayerTabOverlay;
+import io.github.moulberry.notenoughupdates.util.NotificationHandler;
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+
+public class CookieWarning {
+
+ private static boolean hasNotified;
+
+ public static void resetNotification() {
+ hasNotified = false;
+ NotificationHandler.cancelNotification();
+ }
+
+ /**
+ * Checks the tab list for a cookie timer, and sends a notification if the timer is within the tolerance
+ */
+ public static void checkCookie() {
+ if (NotEnoughUpdates.INSTANCE.config.notifications.doBoosterNotif &&
+ NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) {
+ String[] lines;
+ try {
+ lines = ((AccessorGuiPlayerTabOverlay) Minecraft.getMinecraft().ingameGUI.getTabList())
+ .getFooter()
+ .getUnformattedText()
+ .split("\n");
+ } catch (NullPointerException e) {
+ return; // if the footer is null or somehow doesn't exist, stop
+ }
+ boolean hasCookie = true;
+ String timeLine = null; // the line that contains the cookie timer
+ for (int i = 0; i < lines.length; i++) {
+ if (lines[i].startsWith("Cookie Buff")) {
+ timeLine = lines[i + 1]; // the line after the "Cookie Buff" line
+ }
+ if (lines[i].startsWith("Not active! Obtain booster cookies from the")) {
+ hasCookie = false;
+ }
+ }
+ if (!hasCookie) {
+ if (!hasNotified) {
+ NotificationHandler.displayNotification(Lists.newArrayList(
+ "\u00a7cBooster Cookie Ran Out!",
+ "\u00a77Your Booster Cookie expired!",
+ "\u00a77",
+ "\u00a77Press X on your keyboard to close this notification"
+ ), true, true);
+ hasNotified = true;
+ }
+ return;
+ }
+ if (timeLine != null) {
+ String[] digits = timeLine.split(" ");
+ int minutes = 0;
+ try {
+ for (String digit : digits) {
+ if (digit.endsWith("y")) {
+ digit = digit.substring(0, digit.length() - 1);
+ minutes += Integer.parseInt(digit) * 525600;
+ } else if (digit.endsWith("d")) {
+ digit = digit.substring(0, digit.length() - 1);
+ minutes += Integer.parseInt(digit) * 1440;
+ } else if (digit.endsWith("h")) {
+ digit = digit.substring(0, digit.length() - 1);
+ minutes += Integer.parseInt(digit) * 60;
+ } else if (digit.endsWith("m")) {
+ digit = digit.substring(0, digit.length() - 1);
+ minutes += Integer.parseInt(digit);
+ } // ignore seconds
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.RED +
+ "NEU ran into an issue when retrieving the Booster Cookie Timer. Check the logs for details."));
+ hasNotified = true;
+ }
+ if (minutes < NotEnoughUpdates.INSTANCE.config.notifications.boosterCookieWarningMins && !hasNotified) {
+ NotificationHandler.displayNotification(Lists.newArrayList(
+ "\u00a7cBooster Cookie Running Low!",
+ "\u00a77Your Booster Cookie will expire in " + timeLine,
+ "\u00a77",
+ "\u00a77Press X on your keyboard to close this notification"
+ ), true, true);
+ hasNotified = true;
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java
index 4586d190..ab9345cb 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java
@@ -1,32 +1,157 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.util.Vec3Comparable;
import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils;
+import io.github.moulberry.notenoughupdates.options.customtypes.NEUDebugFlag;
+import io.github.moulberry.notenoughupdates.util.NEUDebugLogger;
import io.github.moulberry.notenoughupdates.util.SBInfo;
import net.minecraft.client.Minecraft;
-import net.minecraft.util.*;
+import net.minecraft.entity.item.EntityArmorStand;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.IChatComponent;
+import net.minecraft.util.Vec3i;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.stream.Collectors;
public class CrystalMetalDetectorSolver {
+ enum SolutionState {
+ NOT_STARTED,
+ MULTIPLE,
+ MULTIPLE_KNOWN,
+ FOUND,
+ FOUND_KNOWN,
+ FAILED,
+ INVALID,
+ }
+
private static final Minecraft mc = Minecraft.getMinecraft();
- private static BlockPos prevPlayerPos;
- private static double prevDistToTreasure = 0;
- private static List<BlockPos> possibleBlocks = new ArrayList<>();
- private static final List<BlockPos> locations = new ArrayList<>();
- private static Boolean chestRecentlyFound = false;
- private static long chestLastFoundMillis = 0;
+ private static Vec3Comparable prevPlayerPos;
+ private static double prevDistToTreasure;
+ private static HashSet<BlockPos> possibleBlocks = new HashSet<>();
+ private static final HashMap<Vec3Comparable, Double> evaluatedPlayerPositions = new HashMap<>();
+ private static boolean chestRecentlyFound;
+ private static long chestLastFoundMillis;
+ private static final HashSet<BlockPos> openedChestPositions = new HashSet<>();
+
+ // Keeper and Mines of Divan center location info
+ private static Vec3i minesCenter;
+ private static boolean debugDoNotUseCenter = false;
+ private static boolean visitKeeperMessagePrinted;
+ private static final String KEEPER_OF_STRING = "Keeper of ";
+ private static final String DIAMOND_STRING = "diamond";
+ private static final String LAPIS_STRING = "lapis";
+ private static final String EMERALD_STRING = "emerald";
+ private static final String GOLD_STRING = "gold";
+ private static final HashMap<String, Vec3i> keeperOffsets = new HashMap<String, Vec3i>() {{
+ put(DIAMOND_STRING, new Vec3i(33, 0, 3));
+ put(LAPIS_STRING, new Vec3i(-33, 0, -3));
+ put(EMERALD_STRING, new Vec3i(-3, 0, 33));
+ put(GOLD_STRING, new Vec3i(3, 0, -33));
+ }};
+
+ // Chest offsets from center
+ private static final HashSet<Long> knownChestOffsets = new HashSet<>(Arrays.asList(
+ -10171958951910L, // x=-38, y=-22, z=26
+ 10718829084646L, // x=38, y=-22, z=-26
+ -10721714765806L, // x=-40, y=-22, z=18
+ -10996458455018L, // x=-41, y=-20, z=22
+ -1100920913904L, // x=-5, y=-21, z=16
+ 11268584898530L, // x=40, y=-22, z=-30
+ -11271269253148L, // x=-42, y=-20, z=-28
+ -11546281377832L, // x=-43, y=-22, z=-40
+ 11818542038999L, // x=42, y=-19, z=-41
+ 12093285728240L, // x=43, y=-21, z=-16
+ -1409286164L, // x=-1, y=-22, z=-20
+ 1922736062492L, // x=6, y=-21, z=28
+ 2197613969419L, // x=7, y=-21, z=11
+ 2197613969430L, // x=7, y=-21, z=22
+ -3024999153708L, // x=-12, y=-21, z=-44
+ 3571936395295L, // x=12, y=-22, z=31
+ 3572003504106L, // x=12, y=-22, z=-22
+ 3572003504135L, // x=12, y=-21, z=7
+ 3572070612949L, // x=12, y=-21, z=-43
+ -3574822076373L, // x=-14, y=-21, z=43
+ -3574822076394L, // x=-14, y=-21, z=22
+ -4399455797228L, // x=-17, y=-21, z=20
+ -5224156626944L, // x=-20, y=-22, z=0
+ 548346527764L, // x=1, y=-21, z=20
+ 5496081743901L, // x=19, y=-22, z=29
+ 5770959650816L, // x=20, y=-22, z=0
+ 5771093868518L, // x=20, y=-21, z=-26
+ -6048790347736L, // x=-23, y=-22, z=40
+ 6320849682418L, // x=22, y=-21, z=-14
+ -6323668254708L, // x=-24, y=-22, z=12
+ 6595593371674L, // x=23, y=-22, z=26
+ 6595660480473L, // x=23, y=-22, z=-39
+ 6870471278619L, // x=24, y=-22, z=27
+ 7145349185553L, // x=25, y=-22, z=17
+ 8244995030996L, // x=29, y=-21, z=-44
+ -8247679385612L, // x=-31, y=-21, z=-12
+ -8247679385640L, // x=-31, y=-21, z=-40
+ 8519872937959L, // x=30, y=-21, z=-25
+ -8522557292584L, // x=-32, y=-21, z=-40
+ -9622068920278L, // x=-36, y=-20, z=42
+ -9896946827278L, // x=-37, y=-21, z=-14
+ -9896946827286L // x=-37, y=-21, z=-22
+ ));
+
+ static Predicate<BlockPos> treasureAllowedPredicate = CrystalMetalDetectorSolver::treasureAllowed;
+ static SolutionState currentState = SolutionState.NOT_STARTED;
+ static SolutionState previousState = SolutionState.NOT_STARTED;
+
+ public interface Predicate<BlockPos> {
+ boolean check(BlockPos blockPos);
+ }
public static void process(IChatComponent message) {
+ if (SBInfo.getInstance().getLocation() == null ||
+ !NotEnoughUpdates.INSTANCE.config.mining.metalDetectorEnabled ||
+ !SBInfo.getInstance().getLocation().equals("crystal_hollows") ||
+ !message.getUnformattedText().contains("TREASURE: ")) {
+ return;
+ }
+
+ boolean centerNewlyDiscovered = locateMinesCenterIfNeeded();
+
+ double distToTreasure = Double.parseDouble(message
+ .getUnformattedText()
+ .split("TREASURE: ")[1].split("m")[0].replaceAll("(?!\\.)\\D", ""));
+
// Delay to keep old chest location from being treated as the new chest location
if (chestRecentlyFound) {
long currentTimeMillis = System.currentTimeMillis();
if (chestLastFoundMillis == 0) {
chestLastFoundMillis = currentTimeMillis;
return;
- } else if (currentTimeMillis - chestLastFoundMillis < 1000) {
+ } else if (currentTimeMillis - chestLastFoundMillis < 1000 && distToTreasure < 5.0) {
return;
}
@@ -34,80 +159,185 @@ public class CrystalMetalDetectorSolver {
chestRecentlyFound = false;
}
- if (SBInfo.getInstance().getLocation() != null && SBInfo.getInstance().getLocation().equals("crystal_hollows")
- && message.getUnformattedText().contains("TREASURE: ")) {
- double distToTreasure = Double.parseDouble(message
- .getUnformattedText()
- .split("TREASURE: ")[1].split("m")[0].replaceAll("(?!\\.)\\D", ""));
- if (NotEnoughUpdates.INSTANCE.config.mining.metalDetectorEnabled && prevDistToTreasure == distToTreasure &&
- prevPlayerPos.getX() == mc.thePlayer.getPosition().getX() &&
- prevPlayerPos.getY() == mc.thePlayer.getPosition().getY() &&
- prevPlayerPos.getZ() == mc.thePlayer.getPosition().getZ() && !locations.contains(mc.thePlayer.getPosition())) {
- if (possibleBlocks.size() == 0) {
- locations.add(mc.thePlayer.getPosition());
- for (int zOffset = (int) Math.floor(-distToTreasure); zOffset <= Math.ceil(distToTreasure); zOffset++) {
- for (int y = 65; y <= 75; y++) {
- double calculatedDist = 0;
- int xOffset = 0;
- while (calculatedDist < distToTreasure) {
- BlockPos pos = new BlockPos(Math.floor(mc.thePlayer.posX) + xOffset,
- y, Math.floor(mc.thePlayer.posZ) + zOffset
- );
- calculatedDist = getPlayerPos().distanceTo(new Vec3(pos).addVector(0D, 1D, 0D));
- if (round(calculatedDist, 1) == distToTreasure && !possibleBlocks.contains(pos) &&
- treasureAllowed(pos) && mc.theWorld.
- getBlockState(pos.add(0, 1, 0)).getBlock().getRegistryName().equals("minecraft:air")) {
- possibleBlocks.add(pos);
- }
- xOffset++;
- }
- xOffset = 0;
- calculatedDist = 0;
- while (calculatedDist < distToTreasure) {
- BlockPos pos = new BlockPos(Math.floor(mc.thePlayer.posX) - xOffset,
- y, Math.floor(mc.thePlayer.posZ) + zOffset
- );
- calculatedDist = getPlayerPos().distanceTo(new Vec3(pos).addVector(0D, 1D, 0D));
- if (round(calculatedDist, 1) == distToTreasure && !possibleBlocks.contains(pos) &&
- treasureAllowed(pos) && mc.theWorld.
- getBlockState(pos.add(0, 1, 0)).getBlock().getRegistryName().equals("minecraft:air")) {
- possibleBlocks.add(pos);
- }
- xOffset++;
+ SolutionState originalState = currentState;
+ int originalCount = possibleBlocks.size();
+ Vec3Comparable adjustedPlayerPos = getPlayerPosAdjustedForEyeHeight();
+ findPossibleSolutions(distToTreasure, adjustedPlayerPos, centerNewlyDiscovered);
+ if (currentState != originalState || originalCount != possibleBlocks.size()) {
+ switch (currentState) {
+ case FOUND_KNOWN:
+ NEUDebugLogger.log(NEUDebugFlag.METAL, "Known location identified.");
+ // falls through
+ case FOUND:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "[NEU] Found solution."));
+ if (NEUDebugFlag.METAL.isSet() &&
+ (previousState == SolutionState.INVALID || previousState == SolutionState.FAILED)) {
+ NEUDebugLogger.log(
+ NEUDebugFlag.METAL,
+ EnumChatFormatting.AQUA + "Solution coordinates: " +
+ EnumChatFormatting.WHITE + possibleBlocks.iterator().next().toString()
+ );
+ }
+ break;
+ case INVALID:
+ mc.thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.RED + "[NEU] Previous solution is invalid."));
+ logDiagnosticData(false);
+ resetSolution(false);
+ break;
+ case FAILED:
+ mc.thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.RED + "[NEU] Failed to find a solution."));
+ logDiagnosticData(false);
+ resetSolution(false);
+ break;
+ case MULTIPLE_KNOWN:
+ NEUDebugLogger.log(NEUDebugFlag.METAL, "Multiple known locations identified:");
+ // falls through
+ case MULTIPLE:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Need another position to find solution. Possible blocks: " + possibleBlocks.size()));
+ break;
+ default:
+ throw new IllegalStateException("Metal detector is in invalid state");
+ }
+ }
+ }
+
+ static void findPossibleSolutions(double distToTreasure, Vec3Comparable playerPos, boolean centerNewlyDiscovered) {
+ if (prevDistToTreasure == distToTreasure && prevPlayerPos.equals(playerPos) &&
+ !evaluatedPlayerPositions.containsKey(playerPos)) {
+ evaluatedPlayerPositions.put(playerPos, distToTreasure);
+ if (possibleBlocks.size() == 0) {
+ for (int zOffset = (int) Math.floor(-distToTreasure); zOffset <= Math.ceil(distToTreasure); zOffset++) {
+ for (int y = 65; y <= 75; y++) {
+ double calculatedDist = 0;
+ int xOffset = 0;
+ while (calculatedDist < distToTreasure) {
+ BlockPos pos = new BlockPos(Math.floor(playerPos.xCoord) + xOffset,
+ y, Math.floor(playerPos.zCoord) + zOffset
+ );
+ calculatedDist = playerPos.distanceTo(new Vec3Comparable(pos).addVector(0D, 1D, 0D));
+ if (round(calculatedDist, 1) == distToTreasure && treasureAllowedPredicate.check(pos)) {
+ possibleBlocks.add(pos);
}
+ xOffset++;
}
- }
- sendMessage();
- } else if (possibleBlocks.size() != 1) {
- locations.add(mc.thePlayer.getPosition());
- List<BlockPos> temp = new ArrayList<>();
- for (BlockPos pos : possibleBlocks) {
- if (round(getPlayerPos().distanceTo(new Vec3(pos).addVector(0D, 1D, 0D)), 1) == distToTreasure) {
- temp.add(pos);
+ xOffset = 0;
+ calculatedDist = 0;
+ while (calculatedDist < distToTreasure) {
+ BlockPos pos = new BlockPos(Math.floor(playerPos.xCoord) - xOffset,
+ y, Math.floor(playerPos.zCoord) + zOffset
+ );
+ calculatedDist = playerPos.distanceTo(new Vec3Comparable(pos).addVector(0D, 1D, 0D));
+ if (round(calculatedDist, 1) == distToTreasure && treasureAllowedPredicate.check(pos)) {
+ possibleBlocks.add(pos);
+ }
+ xOffset++;
}
}
- possibleBlocks = temp;
- sendMessage();
- } else {
- BlockPos pos = possibleBlocks.get(0);
- if (Math.abs(distToTreasure - (getPlayerPos().distanceTo(new Vec3(pos)))) > 5) {
- mc.thePlayer.addChatMessage(new ChatComponentText(
- EnumChatFormatting.RED + "[NEU] Previous solution is invalid."));
- reset(false);
+ }
+
+ updateSolutionState();
+ } else if (possibleBlocks.size() != 1) {
+ HashSet<BlockPos> temp = new HashSet<>();
+ for (BlockPos pos : possibleBlocks) {
+ if (round(playerPos.distanceTo(new Vec3Comparable(pos).addVector(0D, 1D, 0D)), 1) == distToTreasure) {
+ temp.add(pos);
}
}
+ possibleBlocks = temp;
+ updateSolutionState();
+ } else {
+ BlockPos pos = possibleBlocks.iterator().next();
+ if (Math.abs(distToTreasure - (playerPos.distanceTo(new Vec3Comparable(pos)))) > 5) {
+ currentState = SolutionState.INVALID;
+ }
+ }
+ } else if (centerNewlyDiscovered && possibleBlocks.size() > 1) {
+ updateSolutionState();
+ }
+
+ prevPlayerPos = playerPos;
+ prevDistToTreasure = distToTreasure;
+ }
+
+ public static void setDebugDoNotUseCenter(boolean val) {
+ debugDoNotUseCenter = val;
+ }
+
+ private static String getFriendlyBlockPositions(Collection<BlockPos> positions) {
+ if (!NEUDebugLogger.isFlagEnabled(NEUDebugFlag.METAL) || positions.size() == 0) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n");
+ for (BlockPos blockPos : positions) {
+ sb.append("Absolute: ");
+ sb.append(blockPos.toString());
+ if (minesCenter != Vec3i.NULL_VECTOR) {
+ BlockPos relativeOffset = blockPos.subtract(minesCenter);
+ sb.append(", Relative: ");
+ sb.append(relativeOffset.toString());
+ sb.append(" (" + relativeOffset.toLong() + ")");
+ }
+ sb.append("\n");
+ }
+
+ return sb.toString();
+ }
+
+ private static String getFriendlyEvaluatedPositions() {
+ if (!NEUDebugLogger.isFlagEnabled(NEUDebugFlag.METAL) || evaluatedPlayerPositions.size() == 0) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n");
+ for (Vec3Comparable vec : evaluatedPlayerPositions.keySet()) {
+ sb.append("Absolute: " + vec.toString());
+ if (minesCenter != Vec3i.NULL_VECTOR) {
+ BlockPos positionBlockPos = new BlockPos(vec);
+ BlockPos relativeOffset = positionBlockPos.subtract(minesCenter);
+ sb.append(", Relative: " + relativeOffset.toString() + " (" + relativeOffset.toLong() + ")");
}
- prevPlayerPos = mc.thePlayer.getPosition();
- prevDistToTreasure = distToTreasure;
+ sb.append(" Distance: ");
+ sb.append(evaluatedPlayerPositions.get(vec));
+
+ sb.append("\n");
}
+
+ return sb.toString();
}
- public static void reset(Boolean chestFound) {
+ public static void resetSolution(Boolean chestFound) {
+ if (chestFound) {
+ prevPlayerPos = null;
+ prevDistToTreasure = 0;
+ if (possibleBlocks.size() == 1) {
+ openedChestPositions.add(possibleBlocks.iterator().next().getImmutable());
+ }
+ }
+
chestRecentlyFound = chestFound;
possibleBlocks.clear();
- locations.clear();
+ evaluatedPlayerPositions.clear();
+ previousState = currentState;
+ currentState = SolutionState.NOT_STARTED;
+ }
+
+ public static void initWorld() {
+ minesCenter = Vec3i.NULL_VECTOR;
+ visitKeeperMessagePrinted = false;
+ openedChestPositions.clear();
+ chestLastFoundMillis = 0;
+ prevDistToTreasure = 0;
+ prevPlayerPos = null;
+ currentState = SolutionState.NOT_STARTED;
+ resetSolution(false);
}
public static void render(float partialTicks) {
@@ -117,10 +347,10 @@ public class CrystalMetalDetectorSolver {
SBInfo.getInstance().location.equals("Mines of Divan")) {
if (possibleBlocks.size() == 1) {
- BlockPos block = possibleBlocks.get(0);
+ BlockPos block = possibleBlocks.iterator().next();
RenderUtils.renderBeaconBeam(block.add(0, 1, 0), beaconRGB, 1.0f, partialTicks);
- RenderUtils.renderWayPoint("Treasure", possibleBlocks.get(0).add(0, 2.5, 0), partialTicks);
+ RenderUtils.renderWayPoint("Treasure", possibleBlocks.iterator().next().add(0, 2.5, 0), partialTicks);
} else if (possibleBlocks.size() > 1 && NotEnoughUpdates.INSTANCE.config.mining.metalDetectorShowPossible) {
for (BlockPos block : possibleBlocks) {
RenderUtils.renderBeaconBeam(block.add(0, 1, 0), beaconRGB, 1.0f, partialTicks);
@@ -130,33 +360,106 @@ public class CrystalMetalDetectorSolver {
}
}
+ private static boolean locateMinesCenterIfNeeded() {
+ if (minesCenter != Vec3i.NULL_VECTOR) {
+ return false;
+ }
+
+ List<EntityArmorStand> keeperEntities = mc.theWorld.getEntities(EntityArmorStand.class, (entity) -> {
+ if (!entity.hasCustomName()) return false;
+ return entity.getCustomNameTag().contains(KEEPER_OF_STRING);
+ });
+
+ if (keeperEntities.size() == 0) {
+ if (!visitKeeperMessagePrinted) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Approach a Keeper while holding the metal detector to enable faster treasure hunting."));
+ visitKeeperMessagePrinted = true;
+ }
+ return false;
+ }
+
+ EntityArmorStand keeperEntity = keeperEntities.get(0);
+ String keeperName = keeperEntity.getCustomNameTag();
+ NEUDebugLogger.log(NEUDebugFlag.METAL, "Locating center using Keeper: " +
+ EnumChatFormatting.WHITE + keeperEntity);
+ String keeperType = keeperName.substring(keeperName.indexOf(KEEPER_OF_STRING) + KEEPER_OF_STRING.length());
+ minesCenter = keeperEntity.getPosition().add(keeperOffsets.get(keeperType.toLowerCase()));
+ NEUDebugLogger.log(NEUDebugFlag.METAL, "Mines center: " +
+ EnumChatFormatting.WHITE + minesCenter.toString());
+ mc.thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.YELLOW + "[NEU] Faster treasure hunting is now enabled based on Keeper location."));
+ return true;
+ }
+
+ public static void setMinesCenter(BlockPos center) {
+ minesCenter = center;
+ }
+
private static double round(double value, int precision) {
int scale = (int) Math.pow(10, precision);
return (double) Math.round(value * scale) / scale;
}
- private static void sendMessage() {
- if (possibleBlocks.size() > 1) {
- mc.thePlayer.addChatMessage(new ChatComponentText(
- EnumChatFormatting.YELLOW + "[NEU] Need another position to find solution. Possible blocks: "
- + possibleBlocks.size()));
- } else if (possibleBlocks.size() == 0) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[NEU] Failed to find solution."));
- reset(false);
- } else {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "[NEU] Found solution."));
+ private static void updateSolutionState() {
+ previousState = currentState;
+
+ if (possibleBlocks.size() == 0) {
+ currentState = SolutionState.FAILED;
+ return;
+ }
+
+ if (possibleBlocks.size() == 1) {
+ currentState = SolutionState.FOUND;
+ return;
+ }
+
+ // Narrow solutions using known locations if the mines center is known
+ if (minesCenter.equals(BlockPos.NULL_VECTOR) || debugDoNotUseCenter) {
+ currentState = SolutionState.MULTIPLE;
+ return;
+ }
+
+ HashSet<BlockPos> temp =
+ possibleBlocks.stream()
+ .filter(block -> knownChestOffsets.contains(block.subtract(minesCenter).toLong()))
+ .collect(Collectors.toCollection(HashSet::new));
+ if (temp.size() == 0) {
+ currentState = SolutionState.MULTIPLE;
+ return;
}
+
+ if (temp.size() == 1) {
+ possibleBlocks = temp;
+ currentState = SolutionState.FOUND_KNOWN;
+ return;
+
+ }
+
+ currentState = SolutionState.MULTIPLE_KNOWN;
}
- private static Vec3 getPlayerPos() {
- return new Vec3(
+ public static BlockPos getSolution() {
+ if (CrystalMetalDetectorSolver.possibleBlocks.size() != 1) {
+ return BlockPos.ORIGIN;
+ }
+
+ return CrystalMetalDetectorSolver.possibleBlocks.stream().iterator().next();
+ }
+
+ private static Vec3Comparable getPlayerPosAdjustedForEyeHeight() {
+ return new Vec3Comparable(
mc.thePlayer.posX,
mc.thePlayer.posY + (mc.thePlayer.getEyeHeight() - mc.thePlayer.getDefaultEyeHeight()),
mc.thePlayer.posZ
);
}
- private static boolean treasureAllowed(BlockPos pos) {
+ static boolean isKnownOffset(BlockPos pos) {
+ return knownChestOffsets.contains(pos.subtract(minesCenter).toLong());
+ }
+
+ static boolean isAllowedBlockType(BlockPos pos) {
return mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:gold_block") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:prismarine") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:chest") ||
@@ -165,4 +468,106 @@ public class CrystalMetalDetectorSolver {
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:wool") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:stained_hardened_clay");
}
+
+ static boolean isAirAbove(BlockPos pos) {
+ return mc.theWorld.
+ getBlockState(pos.add(0, 1, 0)).getBlock().getRegistryName().equals("minecraft:air");
+ }
+
+ private static boolean treasureAllowed(BlockPos pos) {
+ boolean airAbove = isAirAbove(pos);
+ boolean allowedBlockType = isAllowedBlockType(pos);
+ return isKnownOffset(pos) || (airAbove && allowedBlockType);
+ }
+
+ static private String getDiagnosticMessage() {
+ StringBuilder diagsMessage = new StringBuilder();
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Mines Center: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((minesCenter.equals(Vec3i.NULL_VECTOR)) ? "<NOT DISCOVERED>" : minesCenter.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Current Solution State: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(currentState.name());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Previous Solution State: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(previousState.name());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Previous Player Position: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((prevPlayerPos == null) ? "<NONE>" : prevPlayerPos.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Previous Distance To Treasure: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((prevDistToTreasure == 0) ? "<NONE>" : prevDistToTreasure);
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Current Possible Blocks: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(possibleBlocks.size());
+ diagsMessage.append(getFriendlyBlockPositions(possibleBlocks));
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Evaluated player positions: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(evaluatedPlayerPositions.size());
+ diagsMessage.append(getFriendlyEvaluatedPositions());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Chest locations not on known list:\n");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ if (minesCenter != Vec3i.NULL_VECTOR) {
+ HashSet<BlockPos> locationsNotOnKnownList = openedChestPositions
+ .stream()
+ .filter(block -> !knownChestOffsets.contains(block.subtract(minesCenter).toLong()))
+ .map(block -> block.subtract(minesCenter))
+ .collect(Collectors.toCollection(HashSet::new));
+ if (locationsNotOnKnownList.size() > 0) {
+ for (BlockPos blockPos : locationsNotOnKnownList) {
+ diagsMessage.append(String.format(
+ "%dL,\t\t// x=%d, y=%d, z=%d",
+ blockPos.toLong(),
+ blockPos.getX(),
+ blockPos.getY(),
+ blockPos.getZ()
+ ));
+ }
+ }
+ } else {
+ diagsMessage.append("<REQUIRES MINES CENTER>");
+ }
+
+ return diagsMessage.toString();
+ }
+
+ public static void logDiagnosticData(boolean outputAlways) {
+ if (!SBInfo.getInstance().checkForSkyblockLocation()) {
+ return;
+ }
+
+ if (!NotEnoughUpdates.INSTANCE.config.mining.metalDetectorEnabled) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Metal Detector Solver is not enabled."));
+ return;
+ }
+
+ boolean metalDebugFlagSet = NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.contains(NEUDebugFlag.METAL);
+ if (outputAlways || metalDebugFlagSet) {
+ NEUDebugLogger.logAlways(getDiagnosticMessage());
+ }
+ }
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalOverlay.java
index 2b095c42..c0653742 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalOverlay.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java
new file mode 100644
index 00000000..0ee29b4f
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java
@@ -0,0 +1,1108 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.util.Line;
+import io.github.moulberry.notenoughupdates.core.util.Vec3Comparable;
+import io.github.moulberry.notenoughupdates.options.NEUConfig;
+import io.github.moulberry.notenoughupdates.options.customtypes.NEUDebugFlag;
+import io.github.moulberry.notenoughupdates.util.NEUDebugLogger;
+import io.github.moulberry.notenoughupdates.util.SBInfo;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.event.ClickEvent;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.EnumParticleTypes;
+import net.minecraft.util.Vec3i;
+import net.minecraftforge.client.ClientCommandHandler;
+import net.minecraftforge.event.entity.player.PlayerInteractEvent;
+import net.minecraftforge.event.world.WorldEvent;
+import net.minecraftforge.fml.common.Loader;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.function.BooleanSupplier;
+import java.util.function.LongSupplier;
+
+public class CrystalWishingCompassSolver {
+ enum SolverState {
+ NOT_STARTED,
+ PROCESSING_FIRST_USE,
+ NEED_SECOND_COMPASS,
+ PROCESSING_SECOND_USE,
+ SOLVED,
+ FAILED_EXCEPTION,
+ FAILED_TIMEOUT_NO_REPEATING,
+ FAILED_TIMEOUT_NO_PARTICLES,
+ FAILED_INTERSECTION_CALCULATION,
+ FAILED_INVALID_SOLUTION,
+ }
+
+ enum CompassTarget {
+ GOBLIN_QUEEN,
+ GOBLIN_KING,
+ BAL,
+ JUNGLE_TEMPLE,
+ ODAWA,
+ PRECURSOR_CITY,
+ MINES_OF_DIVAN,
+ CRYSTAL_NUCLEUS,
+ }
+
+ enum Crystal {
+ AMBER,
+ AMETHYST,
+ JADE,
+ SAPPHIRE,
+ TOPAZ,
+ }
+
+ enum HollowsZone {
+ CRYSTAL_NUCLEUS,
+ JUNGLE,
+ MITHRIL_DEPOSITS,
+ GOBLIN_HOLDOUT,
+ PRECURSOR_REMNANTS,
+ MAGMA_FIELDS,
+ }
+
+ private static final CrystalWishingCompassSolver INSTANCE = new CrystalWishingCompassSolver();
+
+ public static CrystalWishingCompassSolver getInstance() {
+ return INSTANCE;
+ }
+
+ private static final Minecraft mc = Minecraft.getMinecraft();
+ private static boolean isSkytilsPresent = false;
+ private static final ArrayDeque<ParticleData> seenParticles = new ArrayDeque<>();
+
+ // There is a small set of breakable blocks above the nucleus at Y > 181. While this zone is reported
+ // as the Crystal Nucleus by Hypixel, for wishing compass purposes it is in the appropriate quadrant.
+ private static final AxisAlignedBB NUCLEUS_BB = new AxisAlignedBB(462, 63, 461, 564, 181, 565);
+ // Bounding box around all breakable blocks in the crystal hollows, appears as bedrock in-game
+ private static final AxisAlignedBB HOLLOWS_BB = new AxisAlignedBB(201, 30, 201, 824, 189, 824);
+
+ // Zone bounding boxes
+ private static final AxisAlignedBB PRECURSOR_REMNANTS_BB = new AxisAlignedBB(512, 63, 512, 824, 189, 824);
+ private static final AxisAlignedBB MITHRIL_DEPOSITS_BB = new AxisAlignedBB(512, 63, 201, 824, 189, 513);
+ private static final AxisAlignedBB GOBLIN_HOLDOUT_BB = new AxisAlignedBB(201, 63, 512, 513, 189, 824);
+ private static final AxisAlignedBB JUNGLE_BB = new AxisAlignedBB(201, 63, 201, 513, 189, 513);
+ private static final AxisAlignedBB MAGMA_FIELDS_BB = new AxisAlignedBB(201, 30, 201, 824, 64, 824);
+
+ // Structure bounding boxes (size + 2 in each dimension to make it an actual bounding box)
+ private static final AxisAlignedBB PRECURSOR_CITY_BB = new AxisAlignedBB(0, 0, 0, 107, 122, 107);
+ private static final AxisAlignedBB GOBLIN_KING_BB = new AxisAlignedBB(0, 0, 0, 59, 53, 56);
+ private static final AxisAlignedBB GOBLIN_QUEEN_BB = new AxisAlignedBB(0, 0, 0, 108, 114, 108);
+ private static final AxisAlignedBB JUNGLE_TEMPLE_BB = new AxisAlignedBB(0, 0, 0, 108, 120, 108);
+ private static final AxisAlignedBB ODAWA_BB = new AxisAlignedBB(0, 0, 0, 53, 46, 54);
+ private static final AxisAlignedBB MINES_OF_DIVAN_BB = new AxisAlignedBB(0, 0, 0, 108, 125, 108);
+ private static final AxisAlignedBB KHAZAD_DUM_BB = new AxisAlignedBB(0, 0, 0, 110, 46, 108);
+
+ private static final Vec3Comparable JUNGLE_DOOR_OFFSET_FROM_CRYSTAL = new Vec3Comparable(-57, 36, -21);
+
+ private static final double MAX_DISTANCE_BETWEEN_PARTICLES = 0.6;
+ private static final double MAX_DISTANCE_FROM_USE_TO_FIRST_PARTICLE = 9.0;
+
+ // 64.0 is an arbitrary value but seems to work well
+ private static final double MINIMUM_DISTANCE_SQ_BETWEEN_COMPASSES = 64.0;
+
+ // All particles typically arrive in < 3500, so 5000 should be enough buffer
+ public static final long ALL_PARTICLES_MAX_MILLIS = 5000L;
+
+ public LongSupplier currentTimeMillis = System::currentTimeMillis;
+ public BooleanSupplier kingsScentPresent = this::isKingsScentPresent;
+ public BooleanSupplier keyInInventory = this::isKeyInInventory;
+
+ public interface CrystalEnumSetSupplier {
+ EnumSet<Crystal> getAsCrystalEnumSet();
+ }
+
+ public CrystalEnumSetSupplier foundCrystals = this::getFoundCrystals;
+
+ private SolverState solverState;
+ private Compass firstCompass;
+ private Compass secondCompass;
+ private Line solutionIntersectionLine;
+ private EnumSet<CompassTarget> possibleTargets;
+ private Vec3Comparable solution;
+ private Vec3Comparable originalSolution;
+ private EnumSet<CompassTarget> solutionPossibleTargets;
+
+ public SolverState getSolverState() {
+ return solverState;
+ }
+
+ public Vec3i getSolutionCoords() {
+ return new Vec3i(solution.xCoord, solution.yCoord, solution.zCoord);
+ }
+
+ public EnumSet<CompassTarget> getPossibleTargets() {
+ return possibleTargets;
+ }
+
+ public static HollowsZone getZoneForCoords(BlockPos blockPos) {
+ return getZoneForCoords(new Vec3Comparable(blockPos));
+ }
+
+ public static HollowsZone getZoneForCoords(Vec3Comparable coords) {
+ if (NUCLEUS_BB.isVecInside(coords)) return HollowsZone.CRYSTAL_NUCLEUS;
+ if (JUNGLE_BB.isVecInside(coords)) return HollowsZone.JUNGLE;
+ if (MITHRIL_DEPOSITS_BB.isVecInside(coords)) return HollowsZone.MITHRIL_DEPOSITS;
+ if (GOBLIN_HOLDOUT_BB.isVecInside(coords)) return HollowsZone.GOBLIN_HOLDOUT;
+ if (PRECURSOR_REMNANTS_BB.isVecInside(coords)) return HollowsZone.PRECURSOR_REMNANTS;
+ if (MAGMA_FIELDS_BB.isVecInside(coords)) return HollowsZone.MAGMA_FIELDS;
+ throw new IllegalArgumentException("Coordinates do not fall in known zone: " + coords.toString());
+ }
+
+ private void resetForNewTarget() {
+ NEUDebugLogger.log(NEUDebugFlag.WISHING, "Resetting for new target");
+ solverState = SolverState.NOT_STARTED;
+ firstCompass = null;
+ secondCompass = null;
+ solutionIntersectionLine = null;
+ possibleTargets = null;
+ solution = null;
+ originalSolution = null;
+ solutionPossibleTargets = null;
+ }
+
+ public void initWorld() {
+ resetForNewTarget();
+ }
+
+ @SubscribeEvent
+ public void onWorldLoad(WorldEvent.Unload event) {
+ initWorld();
+ isSkytilsPresent = Loader.isModLoaded("skytils");
+ }
+
+ @SubscribeEvent
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ if (!NotEnoughUpdates.INSTANCE.config.mining.wishingCompassSolver ||
+ SBInfo.getInstance().getLocation() == null ||
+ !SBInfo.getInstance().getLocation().equals("crystal_hollows") ||
+ event.entityPlayer != mc.thePlayer ||
+ (event.action != PlayerInteractEvent.Action.RIGHT_CLICK_AIR &&
+ event.action != PlayerInteractEvent.Action.RIGHT_CLICK_BLOCK)
+ ) {
+ return;
+ }
+
+ ItemStack heldItem = event.entityPlayer.getHeldItem();
+ if (heldItem == null || heldItem.getItem() != Items.skull) {
+ return;
+ }
+
+ String heldInternalName = NotEnoughUpdates.INSTANCE.manager.getInternalNameForItem(heldItem);
+ if (heldInternalName == null || !heldInternalName.equals("WISHING_COMPASS")) {
+ return;
+ }
+
+ BlockPos playerPos = mc.thePlayer.getPosition().getImmutable();
+
+ try {
+ HandleCompassResult result = handleCompassUse(playerPos);
+ switch (result) {
+ case SUCCESS:
+ return;
+ case STILL_PROCESSING_PRIOR_USE:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Wait a little longer before using the wishing compass again."));
+ event.setCanceled(true);
+ break;
+ case LOCATION_TOO_CLOSE:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Move a little further before using the wishing compass again."));
+ event.setCanceled(true);
+ break;
+ case POSSIBLE_TARGETS_CHANGED:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Possible wishing compass targets have changed. Solver has been reset."));
+ event.setCanceled(true);
+ break;
+ case NO_PARTICLES_FOR_PREVIOUS_COMPASS:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] No particles detected for prior compass use. Need another position to solve."));
+ break;
+ case PLAYER_IN_NUCLEUS:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Wishing compass must be used outside the nucleus for accurate results."));
+ event.setCanceled(true);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected wishing compass solver state: \n" + getDiagnosticMessage());
+ }
+ } catch (Exception e) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Error processing wishing compass action - see log for details"));
+ e.printStackTrace();
+ event.setCanceled(true);
+ solverState = SolverState.FAILED_EXCEPTION;
+ }
+ }
+
+ public HandleCompassResult handleCompassUse(BlockPos playerPos) {
+ long lastCompassUsedMillis = 0;
+ switch (solverState) {
+ case PROCESSING_SECOND_USE:
+ if (secondCompass != null) {
+ lastCompassUsedMillis = secondCompass.whenUsedMillis;
+ }
+ case PROCESSING_FIRST_USE:
+ if (lastCompassUsedMillis == 0 && firstCompass != null) {
+ lastCompassUsedMillis = firstCompass.whenUsedMillis;
+ }
+ if (lastCompassUsedMillis != 0 &&
+ (currentTimeMillis.getAsLong() > lastCompassUsedMillis + ALL_PARTICLES_MAX_MILLIS)) {
+ return HandleCompassResult.NO_PARTICLES_FOR_PREVIOUS_COMPASS;
+ }
+
+ return HandleCompassResult.STILL_PROCESSING_PRIOR_USE;
+ case SOLVED:
+ case FAILED_EXCEPTION:
+ case FAILED_TIMEOUT_NO_REPEATING:
+ case FAILED_TIMEOUT_NO_PARTICLES:
+ case FAILED_INTERSECTION_CALCULATION:
+ case FAILED_INVALID_SOLUTION:
+ resetForNewTarget();
+ // falls through, NOT_STARTED is the state when resetForNewTarget returns
+ case NOT_STARTED:
+ if (NUCLEUS_BB.isVecInside(new Vec3Comparable(playerPos.getX(), playerPos.getY(), playerPos.getZ()))) {
+ return HandleCompassResult.PLAYER_IN_NUCLEUS;
+ }
+
+ firstCompass = new Compass(playerPos, currentTimeMillis.getAsLong());
+ seenParticles.clear();
+ solverState = SolverState.PROCESSING_FIRST_USE;
+ possibleTargets = calculatePossibleTargets(playerPos);
+ return HandleCompassResult.SUCCESS;
+ case NEED_SECOND_COMPASS:
+ if (firstCompass.whereUsed.distanceSq(playerPos) < MINIMUM_DISTANCE_SQ_BETWEEN_COMPASSES) {
+ return HandleCompassResult.LOCATION_TOO_CLOSE;
+ }
+
+ HollowsZone firstCompassZone = getZoneForCoords(firstCompass.whereUsed);
+ HollowsZone playerZone = getZoneForCoords(playerPos);
+ if (!possibleTargets.equals(calculatePossibleTargets(playerPos)) ||
+ firstCompassZone != playerZone) {
+ resetForNewTarget();
+ return HandleCompassResult.POSSIBLE_TARGETS_CHANGED;
+ }
+
+ secondCompass = new Compass(playerPos, currentTimeMillis.getAsLong());
+ solverState = SolverState.PROCESSING_SECOND_USE;
+ return HandleCompassResult.SUCCESS;
+ }
+
+ throw new IllegalStateException("Unexpected compass state");
+ }
+
+ /*
+ * Processes particles if the wishing compass was used within the last 5 seconds.
+ *
+ * The first and the last particles are used to create a line for each wishing compass
+ * use that is then used to calculate the target.
+ *
+ * Once two lines have been calculated, the shortest line between the two is calculated
+ * with the midpoint on that line being the wishing compass target. The accuracy of this
+ * seems to be very high.
+ *
+ * The target location varies based on various criteria, including, but not limited to:
+ * Topaz Crystal (Khazad-dûm) Magma Fields
+ * Odawa (Jungle Village) Jungle w/no Jungle Key in inventory
+ * Amethyst Crystal (Jungle Temple) Jungle w/Jungle Key in inventory
+ * Sapphire Crystal (Lost Precursor City) Precursor Remnants
+ * Jade Crystal (Mines of Divan) Mithril Deposits
+ * King Yolkar Goblin Holdout without "King's Scent I" effect
+ * Goblin Queen Goblin Holdout with "King's Scent I" effect
+ * Crystal Nucleus All Crystals found and none placed
+ * per-area structure missing, or because Hypixel.
+ * Always within 1 block of X=513 Y=106 Z=551.
+ */
+ public void onSpawnParticle(
+ EnumParticleTypes particleType,
+ double x,
+ double y,
+ double z
+ ) {
+ if (!NotEnoughUpdates.INSTANCE.config.mining.wishingCompassSolver ||
+ particleType != EnumParticleTypes.VILLAGER_HAPPY ||
+ !SBInfo.getInstance().getLocation().equals("crystal_hollows")) {
+ return;
+ }
+
+ // Capture particle troubleshooting info for two minutes starting when the first compass is used.
+ // This list is reset each time the first compass is used from a NOT_STARTED state.
+ if (firstCompass != null && !solverState.equals(SolverState.SOLVED) &&
+ System.currentTimeMillis() < firstCompass.whenUsedMillis + 2 * 60 * 1000) {
+ seenParticles.add(new ParticleData(new Vec3Comparable(x, y, z), System.currentTimeMillis()));
+ }
+
+ try {
+ SolverState originalSolverState = solverState;
+ solveUsingParticle(x, y, z, currentTimeMillis.getAsLong());
+ if (solverState != originalSolverState) {
+ switch (solverState) {
+ case SOLVED:
+ showSolution();
+ break;
+ case FAILED_EXCEPTION:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Unable to determine wishing compass target."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_TIMEOUT_NO_REPEATING:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Timed out waiting for repeat set of compass particles."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_TIMEOUT_NO_PARTICLES:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Timed out waiting for compass particles."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_INTERSECTION_CALCULATION:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Unable to determine intersection of wishing compasses."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_INVALID_SOLUTION:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Failed to find solution."));
+ logDiagnosticData(false);
+ break;
+ case NEED_SECOND_COMPASS:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Need another position to determine wishing compass target."));
+ break;
+ }
+ }
+ } catch (Exception e) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Exception while calculating wishing compass solution - see log for details"));
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * @param x Particle x coordinate
+ * @param y Particle y coordinate
+ * @param z Particle z coordinate
+ */
+ public void solveUsingParticle(double x, double y, double z, long currentTimeMillis) {
+ Compass currentCompass;
+ switch (solverState) {
+ case PROCESSING_FIRST_USE:
+ currentCompass = firstCompass;
+ break;
+ case PROCESSING_SECOND_USE:
+ currentCompass = secondCompass;
+ break;
+ default:
+ return;
+ }
+
+ currentCompass.processParticle(x, y, z, currentTimeMillis);
+ switch (currentCompass.compassState) {
+ case FAILED_TIMEOUT_NO_PARTICLES:
+ solverState = SolverState.FAILED_TIMEOUT_NO_PARTICLES;
+ return;
+ case FAILED_TIMEOUT_NO_REPEATING:
+ solverState = SolverState.FAILED_TIMEOUT_NO_REPEATING;
+ return;
+ case WAITING_FOR_FIRST_PARTICLE:
+ case COMPUTING_LAST_PARTICLE:
+ return;
+ case COMPLETED:
+ if (solverState == SolverState.NEED_SECOND_COMPASS) {
+ return;
+ }
+ if (solverState == SolverState.PROCESSING_FIRST_USE) {
+ solverState = SolverState.NEED_SECOND_COMPASS;
+ return;
+ }
+ break;
+ }
+
+ // First and Second compasses have completed
+ solutionIntersectionLine = firstCompass.line.getIntersectionLineSegment(secondCompass.line);
+
+ if (solutionIntersectionLine == null) {
+ solverState = SolverState.FAILED_INTERSECTION_CALCULATION;
+ return;
+ }
+
+ solution = new Vec3Comparable(solutionIntersectionLine.getMidpoint());
+
+ Vec3Comparable firstDirection = firstCompass.getDirection();
+ Vec3Comparable firstSolutionDirection = firstCompass.getDirectionTo(solution);
+ Vec3Comparable secondDirection = secondCompass.getDirection();
+ Vec3Comparable secondSolutionDirection = secondCompass.getDirectionTo(solution);
+ if (!firstDirection.signumEquals(firstSolutionDirection) ||
+ !secondDirection.signumEquals(secondSolutionDirection) ||
+ !HOLLOWS_BB.isVecInside(solution)) {
+ solverState = SolverState.FAILED_INVALID_SOLUTION;
+ return;
+ }
+
+ solutionPossibleTargets = getSolutionTargets(
+ getZoneForCoords(firstCompass.whereUsed),
+ foundCrystals.getAsCrystalEnumSet(),
+ possibleTargets,
+ solution
+ );
+
+ // Adjust the Jungle Temple solution coordinates
+ if (solutionPossibleTargets.size() == 1 &&
+ solutionPossibleTargets.contains(CompassTarget.JUNGLE_TEMPLE)) {
+ originalSolution = solution;
+ solution = solution.add(JUNGLE_DOOR_OFFSET_FROM_CRYSTAL);
+ }
+
+ solverState = SolverState.SOLVED;
+ }
+
+ private boolean isKeyInInventory() {
+ for (ItemStack item : mc.thePlayer.inventory.mainInventory) {
+ if (item != null && item.getDisplayName().contains("Jungle Key")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isKingsScentPresent() {
+ return SBInfo.getInstance().footer.getUnformattedText().contains("King's Scent I");
+ }
+
+ private EnumSet<Crystal> getFoundCrystals() {
+ EnumSet<Crystal> foundCrystals = EnumSet.noneOf(Crystal.class);
+ NEUConfig.HiddenProfileSpecific perProfileConfig = NotEnoughUpdates.INSTANCE.config.getProfileSpecific();
+ if (perProfileConfig == null) return foundCrystals;
+ HashMap<String, Integer> crystals = perProfileConfig.crystals;
+ for (String crystalName : crystals.keySet()) {
+ Integer crystalState = crystals.get(crystalName);
+ if (crystalState != null && crystalState > 0) {
+ foundCrystals.add(Crystal.valueOf(crystalName.toUpperCase(Locale.US).replace("İ", "I")));
+ }
+ }
+
+ return foundCrystals;
+ }
+
+ // Returns candidates based on:
+ // - Structure Y levels observed in various lobbies. It is assumed
+ // that structures other than Khazad Dum cannot have any portion
+ // in the Magma Fields.
+ //
+ // - Structure sizes & offsets into other zones that assume at least
+ // one block must be in the correct zone.
+ //
+ // - An assumption that any structure could be missing with a
+ // special exception for the Jungle Temple since it often conflicts
+ // with Bal and a lobby with a missing Jungle Temple has not been
+ // observed. This exception will remove Bal as a target if:
+ // - Target candidates include both Bal & the Jungle Temple.
+ // - The Amethyst crystal has not been acquired.
+ // - The zone that the compass was used in is the Jungle.
+ //
+ // - If the solution is the Crystal Nucleus then a copy of the
+ // passed in possible targets is returned.
+ //
+ // |----------|------------|
+ // | Jungle | Mithril |
+ // | | Deposits |
+ // |----------|----------- |
+ // | Goblin | Precursor |
+ // | Holdout | Deposits |
+ // |----------|------------|
+ static public EnumSet<CompassTarget> getSolutionTargets(
+ HollowsZone compassUsedZone,
+ EnumSet<Crystal> foundCrystals,
+ EnumSet<CompassTarget> possibleTargets,
+ Vec3Comparable solution
+ ) {
+ EnumSet<CompassTarget> solutionPossibleTargets;
+ solutionPossibleTargets = possibleTargets.clone();
+
+ HollowsZone solutionZone = getZoneForCoords(solution);
+ if (solutionZone == HollowsZone.CRYSTAL_NUCLEUS) {
+ return solutionPossibleTargets;
+ }
+
+ solutionPossibleTargets.remove(CompassTarget.CRYSTAL_NUCLEUS);
+
+ // Y coordinates are 43-71 from 13 samples
+ // Y=41/74 is the absolute min/max based on structure size if
+ // the center of the topaz crystal has to be in magma fields.
+ if (solutionPossibleTargets.contains(CompassTarget.BAL) &&
+ solution.yCoord > 75) {
+ solutionPossibleTargets.remove(CompassTarget.BAL);
+ }
+
+ // Y coordinates are 93-157 from 15 samples.
+ // Y=83/167 is the absolute min/max based on structure size
+ if (solutionPossibleTargets.contains(CompassTarget.GOBLIN_KING) &&
+ solution.yCoord < 82 || solution.yCoord > 168) {
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_KING);
+ }
+
+ // Y coordinates are 129-139 from 10 samples
+ // Y=126/139 is the absolute min/max based on structure size
+ if (solutionPossibleTargets.contains(CompassTarget.GOBLIN_QUEEN) &&
+ (solution.yCoord < 125 || solution.yCoord > 140)) {
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_QUEEN);
+ }
+
+ // Y coordinates are 72-80 from 10 samples
+ // Y=73/80 is the absolute min/max based on structure size
+ if (solutionPossibleTargets.contains(CompassTarget.JUNGLE_TEMPLE) &&
+ (solution.yCoord < 72 || solution.yCoord > 81)) {
+ solutionPossibleTargets.remove(CompassTarget.JUNGLE_TEMPLE);
+ }
+
+ // Y coordinates are 87-155 from 7 samples
+ // Y=74/155 is the absolute min/max solution based on structure size
+ if (solutionPossibleTargets.contains(CompassTarget.ODAWA) &&
+ (solution.yCoord < 73 || solution.yCoord > 155)) {
+ solutionPossibleTargets.remove(CompassTarget.ODAWA);
+ }
+
+ // Y coordinates are 122-129 from 8 samples
+ // Y=122/129 is the absolute min/max based on structure size
+ if (solutionPossibleTargets.contains(CompassTarget.PRECURSOR_CITY) &&
+ (solution.yCoord < 121 || solution.yCoord > 130)) {
+ solutionPossibleTargets.remove(CompassTarget.PRECURSOR_CITY);
+ }
+
+ // Y coordinates are 98-102 from 15 samples
+ // Y=98/100 is the absolute min/max based on structure size,
+ // but 102 has been seen - possibly with earlier code that rounded up
+ if (solutionPossibleTargets.contains(CompassTarget.MINES_OF_DIVAN) &&
+ (solution.yCoord < 97 || solution.yCoord > 102)) {
+ solutionPossibleTargets.remove(CompassTarget.MINES_OF_DIVAN);
+ }
+
+ // Now filter by structure offset
+ if (solutionPossibleTargets.contains(CompassTarget.GOBLIN_KING) &&
+ (solution.xCoord > GOBLIN_HOLDOUT_BB.maxX + GOBLIN_KING_BB.maxX ||
+ solution.zCoord < GOBLIN_HOLDOUT_BB.minZ - GOBLIN_KING_BB.maxZ)) {
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_KING);
+ }
+
+ if (solutionPossibleTargets.contains(CompassTarget.GOBLIN_QUEEN) &&
+ (solution.xCoord > GOBLIN_HOLDOUT_BB.maxX + GOBLIN_QUEEN_BB.maxX ||
+ solution.zCoord < GOBLIN_HOLDOUT_BB.minZ - GOBLIN_QUEEN_BB.maxZ)) {
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_QUEEN);
+ }
+
+ if (solutionPossibleTargets.contains(CompassTarget.JUNGLE_TEMPLE) &&
+ (solution.xCoord > JUNGLE_BB.maxX + JUNGLE_TEMPLE_BB.maxX ||
+ solution.zCoord > JUNGLE_BB.maxZ + JUNGLE_TEMPLE_BB.maxZ)) {
+ solutionPossibleTargets.remove(CompassTarget.JUNGLE_TEMPLE);
+ }
+
+ if (solutionPossibleTargets.contains(CompassTarget.ODAWA) &&
+ (solution.xCoord > JUNGLE_BB.maxX + ODAWA_BB.maxX ||
+ solution.zCoord > JUNGLE_BB.maxZ + ODAWA_BB.maxZ)) {
+ solutionPossibleTargets.remove(CompassTarget.ODAWA);
+ }
+
+ if (solutionPossibleTargets.contains(CompassTarget.PRECURSOR_CITY) &&
+ (solution.xCoord < PRECURSOR_REMNANTS_BB.minX - PRECURSOR_CITY_BB.maxX ||
+ solution.zCoord < PRECURSOR_REMNANTS_BB.minZ - PRECURSOR_CITY_BB.maxZ)) {
+ solutionPossibleTargets.remove(CompassTarget.PRECURSOR_CITY);
+ }
+
+ if (solutionPossibleTargets.contains(CompassTarget.MINES_OF_DIVAN) &&
+ (solution.xCoord < MITHRIL_DEPOSITS_BB.minX - MINES_OF_DIVAN_BB.maxX ||
+ solution.zCoord > MITHRIL_DEPOSITS_BB.maxZ + MINES_OF_DIVAN_BB.maxZ)) {
+ solutionPossibleTargets.remove(CompassTarget.MINES_OF_DIVAN);
+ }
+
+ // Special case the Jungle Temple
+ if (solutionPossibleTargets.contains(CompassTarget.JUNGLE_TEMPLE) &&
+ solutionPossibleTargets.contains(CompassTarget.BAL) &&
+ !foundCrystals.contains(Crystal.AMETHYST) &&
+ compassUsedZone == HollowsZone.JUNGLE) {
+ solutionPossibleTargets.remove(CompassTarget.BAL);
+ }
+
+ return solutionPossibleTargets;
+ }
+
+ private EnumSet<CompassTarget> calculatePossibleTargets(BlockPos playerPos) {
+ EnumSet<CompassTarget> candidateTargets = EnumSet.of(CompassTarget.CRYSTAL_NUCLEUS);
+ EnumSet<Crystal> foundCrystals = this.foundCrystals.getAsCrystalEnumSet();
+
+ // Add targets based on missing crystals.
+ // NOTE:
+ // We used to assume that only the adjacent zone's targets could be returned. That turned
+ // out to be incorrect (e.g. a compass in the jungle pointed to the Precursor City when
+ // the king would have been a valid target). Now we assume that any structure could be
+ // missing (because Hypixel) and depend on the solution coordinates to filter the list.
+ for (Crystal crystal : Crystal.values()) {
+ if (foundCrystals.contains(crystal)) {
+ continue;
+ }
+
+ switch (crystal) {
+ case JADE:
+ candidateTargets.add(CompassTarget.MINES_OF_DIVAN);
+ break;
+ case AMBER:
+ candidateTargets.add(
+ kingsScentPresent.getAsBoolean() ? CompassTarget.GOBLIN_QUEEN : CompassTarget.GOBLIN_KING);
+ break;
+ case TOPAZ:
+ candidateTargets.add(CompassTarget.BAL);
+ break;
+ case AMETHYST:
+ candidateTargets.add(
+ keyInInventory.getAsBoolean() ? CompassTarget.JUNGLE_TEMPLE : CompassTarget.ODAWA);
+ break;
+ case SAPPHIRE:
+ candidateTargets.add(CompassTarget.PRECURSOR_CITY);
+ break;
+ }
+ }
+
+ return candidateTargets;
+ }
+
+ private String getFriendlyNameForCompassTarget(CompassTarget compassTarget) {
+ switch (compassTarget) {
+ case BAL:
+ return EnumChatFormatting.RED + "Bal";
+ case ODAWA:
+ return EnumChatFormatting.GREEN + "Odawa";
+ case JUNGLE_TEMPLE:
+ return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.GREEN + "Jungle Temple";
+ case GOBLIN_KING:
+ return EnumChatFormatting.GOLD + "King Yolkar";
+ case GOBLIN_QUEEN:
+ return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.YELLOW + "Goblin Queen";
+ case PRECURSOR_CITY:
+ return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.WHITE + "Precursor City";
+ case MINES_OF_DIVAN:
+ return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.BLUE + "Mines of Divan";
+ default:
+ return EnumChatFormatting.WHITE + "an undetermined location";
+ }
+ }
+
+ private String getNameForCompassTarget(CompassTarget compassTarget) {
+ boolean useSkytilsNames = (NotEnoughUpdates.INSTANCE.config.mining.wishingCompassWaypointNames == 1);
+ switch (compassTarget) {
+ case BAL:
+ return useSkytilsNames ? "internal_bal" : "Bal";
+ case ODAWA:
+ return "Odawa";
+ case JUNGLE_TEMPLE:
+ return useSkytilsNames ? "internal_temple" : "Temple";
+ case GOBLIN_KING:
+ return useSkytilsNames ? "internal_king" : "King";
+ case GOBLIN_QUEEN:
+ return useSkytilsNames ? "internal_den" : "Queen";
+ case PRECURSOR_CITY:
+ return useSkytilsNames ? "internal_city" : "City";
+ case MINES_OF_DIVAN:
+ return useSkytilsNames ? "internal_mines" : "Mines";
+ default:
+ return "WishingTarget";
+ }
+ }
+
+ private String getSolutionCoordsText() {
+ return solution == null ? "" :
+ String.format("%.0f %.0f %.0f", solution.xCoord, solution.yCoord, solution.zCoord);
+ }
+
+ private String getWishingCompassDestinationsMessage() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(EnumChatFormatting.YELLOW);
+ sb.append("[NEU] ");
+ sb.append(EnumChatFormatting.AQUA);
+ sb.append("Wishing compass points to ");
+ int index = 1;
+ for (CompassTarget target : solutionPossibleTargets) {
+ if (index > 1) {
+ sb.append(EnumChatFormatting.AQUA);
+ if (index == solutionPossibleTargets.size()) {
+ sb.append(" or ");
+ } else {
+ sb.append(", ");
+ }
+ }
+ sb.append(getFriendlyNameForCompassTarget(target));
+ index++;
+ }
+
+ sb.append(EnumChatFormatting.AQUA);
+ sb.append(" (");
+ sb.append(getSolutionCoordsText());
+ sb.append(")");
+ return sb.toString();
+ }
+
+ private void showSolution() {
+ if (solution == null) return;
+
+ if (NUCLEUS_BB.isVecInside(solution)) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "[NEU] " +
+ EnumChatFormatting.AQUA + "Wishing compass target is the Crystal Nucleus"));
+ return;
+ }
+
+ String destinationMessage = getWishingCompassDestinationsMessage();
+
+ if (!isSkytilsPresent) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(destinationMessage));
+ return;
+ }
+
+ String targetNameForSkytils = solutionPossibleTargets.size() == 1 ?
+ getNameForCompassTarget(solutionPossibleTargets.iterator().next()) :
+ "WishingTarget";
+ String skytilsCommand = String.format("/sthw add %s %s", getSolutionCoordsText(), targetNameForSkytils);
+ if (NotEnoughUpdates.INSTANCE.config.mining.wishingCompassAutocreateKnownWaypoints &&
+ solutionPossibleTargets.size() == 1) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(destinationMessage));
+ int commandResult = ClientCommandHandler.instance.executeCommand(mc.thePlayer, skytilsCommand);
+ if (commandResult == 1) {
+ return;
+ }
+ mc.thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.RED + "[NEU] Failed to automatically run /sthw"));
+ }
+
+ destinationMessage += EnumChatFormatting.YELLOW + " [Add Skytils Waypoint]";
+ ChatComponentText chatMessage = new ChatComponentText(destinationMessage);
+ chatMessage.setChatStyle(Utils.createClickStyle(
+ ClickEvent.Action.RUN_COMMAND,
+ skytilsCommand,
+ EnumChatFormatting.YELLOW + "Set waypoint for wishing target"
+ ));
+ mc.thePlayer.addChatMessage(chatMessage);
+ }
+
+ private String getDiagnosticMessage() {
+ StringBuilder diagsMessage = new StringBuilder();
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Solver State: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(solverState.name());
+ diagsMessage.append("\n");
+
+ if (firstCompass == null) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("First Compass: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append("<NONE>");
+ diagsMessage.append("\n");
+ } else {
+ firstCompass.appendCompassDiagnostics(diagsMessage, "First Compass");
+ }
+
+ if (secondCompass == null) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Second Compass: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append("<NONE>");
+ diagsMessage.append("\n");
+ } else {
+ secondCompass.appendCompassDiagnostics(diagsMessage, "Second Compass");
+ }
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Intersection Line: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((solutionIntersectionLine == null) ? "<NONE>" : solutionIntersectionLine);
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Jungle Key in Inventory: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(isKeyInInventory());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("King's Scent Present: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(isKingsScentPresent());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("First Compass Targets: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(possibleTargets == null ? "<NONE>" : possibleTargets.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Current Calculated Targets: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(calculatePossibleTargets(mc.thePlayer.getPosition()));
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Found Crystals: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(getFoundCrystals());
+ diagsMessage.append("\n");
+
+ if (originalSolution != null) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Original Solution: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(originalSolution);
+ diagsMessage.append("\n");
+ }
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Solution: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((solution == null) ? "<NONE>" : solution.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Solution Targets: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((solutionPossibleTargets == null) ? "<NONE>" : solutionPossibleTargets.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Seen particles:\n");
+ for (ParticleData particleData : seenParticles) {
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(particleData);
+ diagsMessage.append("\n");
+ }
+
+ return diagsMessage.toString();
+ }
+
+ public void logDiagnosticData(boolean outputAlways) {
+ if (!SBInfo.getInstance().checkForSkyblockLocation()) {
+ return;
+ }
+
+ if (!NotEnoughUpdates.INSTANCE.config.mining.wishingCompassSolver) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Wishing Compass Solver is not enabled."));
+ return;
+ }
+
+ boolean wishingDebugFlagSet = NEUDebugFlag.WISHING.isSet();
+ if (outputAlways || wishingDebugFlagSet) {
+ NEUDebugLogger.logAlways(getDiagnosticMessage());
+ }
+ }
+
+ enum CompassState {
+ WAITING_FOR_FIRST_PARTICLE,
+ COMPUTING_LAST_PARTICLE,
+ COMPLETED,
+ FAILED_TIMEOUT_NO_REPEATING,
+ FAILED_TIMEOUT_NO_PARTICLES,
+ }
+
+ enum HandleCompassResult {
+ SUCCESS,
+ LOCATION_TOO_CLOSE,
+ STILL_PROCESSING_PRIOR_USE,
+ POSSIBLE_TARGETS_CHANGED,
+ NO_PARTICLES_FOR_PREVIOUS_COMPASS,
+ PLAYER_IN_NUCLEUS
+ }
+
+ static class Compass {
+ public CompassState compassState;
+ public Line line = null;
+
+ private final BlockPos whereUsed;
+ private final long whenUsedMillis;
+ private Vec3Comparable firstParticle = null;
+ private Vec3Comparable previousParticle = null;
+ private Vec3Comparable lastParticle = null;
+ private final ArrayList<ProcessedParticle> processedParticles;
+
+ Compass(BlockPos whereUsed, long whenUsedMillis) {
+ this.whereUsed = whereUsed;
+ this.whenUsedMillis = whenUsedMillis;
+ compassState = CompassState.WAITING_FOR_FIRST_PARTICLE;
+ processedParticles = new ArrayList<>();
+ }
+
+ public Vec3Comparable getDirection() {
+ if (firstParticle == null || lastParticle == null) {
+ return null;
+ }
+
+ return new Vec3Comparable(firstParticle.subtractReverse(lastParticle).normalize());
+ }
+
+ public Vec3Comparable getDirectionTo(Vec3Comparable target) {
+ if (firstParticle == null || target == null) {
+ return null;
+ }
+
+ return new Vec3Comparable(firstParticle.subtractReverse(target).normalize());
+ }
+
+ public double particleSpread() {
+ if (firstParticle == null || lastParticle == null) {
+ return 0.0;
+ }
+ return firstParticle.distanceTo(lastParticle);
+ }
+
+ public void processParticle(double x, double y, double z, long particleTimeMillis) {
+ if (compassState == CompassState.FAILED_TIMEOUT_NO_REPEATING ||
+ compassState == CompassState.FAILED_TIMEOUT_NO_PARTICLES ||
+ compassState == CompassState.COMPLETED) {
+ throw new UnsupportedOperationException("processParticle should not be called in a failed or completed state");
+ }
+
+ if (particleTimeMillis - this.whenUsedMillis > ALL_PARTICLES_MAX_MILLIS) {
+ // Assume we have failed if we're still trying to process particles
+ compassState = CompassState.FAILED_TIMEOUT_NO_REPEATING;
+ return;
+ }
+
+ Vec3Comparable currentParticle = new Vec3Comparable(x, y, z);
+ if (compassState == CompassState.WAITING_FOR_FIRST_PARTICLE) {
+ if (currentParticle.distanceTo(new Vec3Comparable(whereUsed)) < MAX_DISTANCE_FROM_USE_TO_FIRST_PARTICLE) {
+ processedParticles.add(new ProcessedParticle(currentParticle, particleTimeMillis));
+ firstParticle = currentParticle;
+ previousParticle = currentParticle;
+ compassState = CompassState.COMPUTING_LAST_PARTICLE;
+ }
+ return;
+ }
+
+ // State is COMPUTING_LAST_PARTICLE, keep updating the previousParticle until
+ // the first particle in the second sequence is seen.
+ if (currentParticle.distanceTo(previousParticle) <= MAX_DISTANCE_BETWEEN_PARTICLES) {
+ processedParticles.add(new ProcessedParticle(currentParticle, particleTimeMillis));
+ previousParticle = currentParticle;
+ return;
+ }
+
+ if (currentParticle.distanceTo(firstParticle) > MAX_DISTANCE_BETWEEN_PARTICLES) {
+ return;
+ }
+
+ // It's a repeating particle
+ processedParticles.add(new ProcessedParticle(currentParticle, particleTimeMillis));
+ lastParticle = previousParticle;
+ line = new Line(firstParticle, lastParticle);
+ compassState = CompassState.COMPLETED;
+ }
+
+ public void appendCompassDiagnostics(StringBuilder diagsMessage, String compassName) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Compass State: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(compassState.name());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Used Millis: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(whenUsedMillis);
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Used Position: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((whereUsed == null) ? "<NONE>" : whereUsed.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" All Seen Particles: \n");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ for (ProcessedParticle particle : processedParticles) {
+ diagsMessage.append(particle.toString());
+ diagsMessage.append("\n");
+ }
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Particle Spread: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(particleSpread());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Compass Line: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((line == null) ? "<NONE>" : line.toString());
+ diagsMessage.append("\n");
+ }
+
+ static class ProcessedParticle {
+ Vec3Comparable coords;
+ long particleTimeMillis;
+
+ ProcessedParticle(Vec3Comparable coords, long particleTimeMillis) {
+ this.coords = coords;
+ this.particleTimeMillis = particleTimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return coords.toString() + " " + particleTimeMillis;
+ }
+ }
+ }
+
+ private static class ParticleData {
+ Vec3Comparable particleLocation;
+ long systemTime;
+
+ public ParticleData(Vec3Comparable particleLocation, long systemTime) {
+ this.particleLocation = particleLocation;
+ this.systemTime = systemTime;
+ }
+
+ public String toString() {
+ return "Location: " + particleLocation.toString() + ", systemTime: " + systemTime;
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java
index e621cf78..452f8a9b 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
@@ -11,7 +30,11 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.multiplayer.WorldClient;
-import net.minecraft.client.renderer.*;
+import net.minecraft.client.renderer.BlockRendererDispatcher;
+import net.minecraft.client.renderer.EntityRenderer;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
@@ -24,7 +47,13 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
-import net.minecraft.util.*;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.MovingObjectPosition;
+import net.minecraft.util.Vec3;
+import net.minecraft.util.Vec3i;
import net.minecraftforge.client.event.DrawBlockHighlightEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
@@ -36,8 +65,14 @@ import org.lwjgl.util.vector.Vector3f;
import java.awt.*;
import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
-import java.util.*;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -178,12 +213,14 @@ public class CustomItemEffects {
@SubscribeEvent
public void onGameTick(TickEvent.ClientTickEvent event) {
if (event.phase != TickEvent.Phase.END) return;
+ EntityPlayerSP player = Minecraft.getMinecraft().thePlayer;
+ if (player == null) return;
if (!usingEtherwarp && wasUsingEtherwarp) {
- if (Minecraft.getMinecraft().thePlayer.rotationYaw > 0) {
- Minecraft.getMinecraft().thePlayer.rotationYaw -= 0.000001;
+ if (player.rotationYaw > 0) {
+ player.rotationYaw -= 0.000001;
} else {
- Minecraft.getMinecraft().thePlayer.rotationYaw += 0.000001;
+ player.rotationYaw += 0.000001;
}
}
wasUsingEtherwarp = usingEtherwarp;
@@ -238,6 +275,7 @@ public class CustomItemEffects {
private boolean usingEtherwarp = false;
private RaycastResult etherwarpRaycast = null;
private int lastEtherwarpUse = 0;
+ private String denyTpReason = null;
@SubscribeEvent
public void onOverlayDrawn(RenderGameOverlayEvent.Post event) {
@@ -246,40 +284,50 @@ public class CustomItemEffects {
ItemStack held = Minecraft.getMinecraft().thePlayer.getHeldItem();
String heldInternal = NotEnoughUpdates.INSTANCE.manager.getInternalNameForItem(held);
- if (usingEtherwarp && NotEnoughUpdates.INSTANCE.config.itemOverlays.enableEtherwarpHelperOverlay) {
- String denyTpReason = null;
+ WorldClient world = Minecraft.getMinecraft().theWorld;
+ if (usingEtherwarp) {
+ denyTpReason = null;
if (etherwarpRaycast == null) {
denyTpReason = "Too far!";
} else {
BlockPos pos = etherwarpRaycast.pos;
- if (!etherwarpRaycast.state.getBlock().isCollidable() ||
- etherwarpRaycast.state.getBlock().getCollisionBoundingBox(
- Minecraft.getMinecraft().theWorld,
- etherwarpRaycast.pos,
- etherwarpRaycast.state
- ) == null) {
+ Block block = etherwarpRaycast.state.getBlock();
+ if (!block.isCollidable() ||
+ //Don't allow teleport at this block
+ block == Blocks.carpet || block == Blocks.skull ||
+ block.getCollisionBoundingBox(world, etherwarpRaycast.pos, etherwarpRaycast.state) == null &&
+ //Allow teleport at this block
+ block != Blocks.wall_sign && block != Blocks.standing_sign) {
denyTpReason = "Not solid!";
} else {
- WorldClient world = Minecraft.getMinecraft().theWorld;
- Block above = world.getBlockState(pos.add(0, 1, 0)).getBlock();
- if (above != Blocks.air && above.isCollidable() &&
- above.getCollisionBoundingBox(Minecraft.getMinecraft().theWorld, pos.add(0, 1, 0),
- world.getBlockState(pos.add(0, 1, 0))
- ) != null ||
- world.getBlockState(pos.add(0, 2, 0)).getBlock() != Blocks.air) {
+ BlockPos blockPosAbove = pos.add(0, 1, 0);
+ Block blockAbove = world.getBlockState(blockPosAbove).getBlock();
+
+ Block twoBlockAbove = world.getBlockState(pos.add(0, 2, 0)).getBlock();
+ if (blockAbove != Blocks.air &&
+ //Allow teleport to the block below this block
+ blockAbove != Blocks.carpet && blockAbove != Blocks.skull && blockAbove.isCollidable() &&
+ blockAbove.getCollisionBoundingBox(world, blockPosAbove, world.getBlockState(blockPosAbove)) != null ||
+ //Don't allow teleport to the block below this block
+ blockAbove == Blocks.wall_sign || block == Blocks.standing_sign ||
+ //Allow teleport to the block 2 blocks below this block
+ twoBlockAbove != Blocks.air && twoBlockAbove != Blocks.double_plant && twoBlockAbove != Blocks.carpet &&
+ blockAbove != Blocks.skull) {
denyTpReason = "No air above!";
}
}
}
- if (denyTpReason != null) {
- ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft());
- Utils.drawStringCentered(EnumChatFormatting.RED + "Can't TP: " + denyTpReason,
- Minecraft.getMinecraft().fontRendererObj,
- scaledResolution.getScaledWidth() / 2f, scaledResolution.getScaledHeight() / 2f + 10, true, 0
- );
- GlStateManager.color(1, 1, 1, 1);
+ if (NotEnoughUpdates.INSTANCE.config.itemOverlays.enableEtherwarpHelperOverlay) {
+ if (denyTpReason != null) {
+ ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft());
+ Utils.drawStringCentered(EnumChatFormatting.RED + "Can't TP: " + denyTpReason,
+ Minecraft.getMinecraft().fontRendererObj,
+ scaledResolution.getScaledWidth() / 2f, scaledResolution.getScaledHeight() / 2f + 10, true, 0
+ );
+ GlStateManager.color(1, 1, 1, 1);
+ }
}
}
@@ -291,7 +339,7 @@ public class CustomItemEffects {
Minecraft.getMinecraft().objectMouseOver.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK &&
onPrivateIsland) {
- IBlockState hover = Minecraft.getMinecraft().theWorld.getBlockState(
+ IBlockState hover = world.getBlockState(
Minecraft.getMinecraft().objectMouseOver.getBlockPos().offset(
Minecraft.getMinecraft().objectMouseOver.sideHit, 1));
if (hover.getBlock() == Blocks.air) {
@@ -303,14 +351,14 @@ public class CustomItemEffects {
TreeMap<Float, Set<BlockPos>> candidatesOldSorted = new TreeMap<>();
IBlockState match =
- Minecraft.getMinecraft().theWorld.getBlockState(Minecraft.getMinecraft().objectMouseOver.getBlockPos());
+ world.getBlockState(Minecraft.getMinecraft().objectMouseOver.getBlockPos());
Item matchItem = Item.getItemFromBlock(match.getBlock());
if (matchItem != null) {
ItemStack matchStack = new ItemStack(matchItem, 1,
match
.getBlock()
.getDamageValue(
- Minecraft.getMinecraft().theWorld,
+ world,
Minecraft.getMinecraft().objectMouseOver.getBlockPos()
)
);
@@ -568,7 +616,8 @@ public class CustomItemEffects {
double d1 = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double) event.partialTicks;
double d2 = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * (double) event.partialTicks;
- if (tick - lastEtherwarpUse > 10) {
+ //Don't need to wait 10 ticks when zoom is disabled
+ if (tick - lastEtherwarpUse > 10 || !NotEnoughUpdates.INSTANCE.config.itemOverlays.etherwarpZoom) {
boolean aotv = Minecraft.getMinecraft().thePlayer.isSneaking() &&
(heldInternal.equals("ASPECT_OF_THE_VOID") || heldInternal.equals("ASPECT_OF_THE_END"));
if (aotv || heldInternal.equals("ETHERWARP_CONDUIT")) {
@@ -597,20 +646,30 @@ public class CustomItemEffects {
if (etherwarpRaycast != null &&
NotEnoughUpdates.INSTANCE.config.itemOverlays.enableEtherwarpBlockOverlay) {
- AxisAlignedBB bb = etherwarpRaycast.state.getBlock().getSelectedBoundingBox(
- Minecraft.getMinecraft().theWorld,
- etherwarpRaycast.pos
- )
- .expand(0.01D, 0.01D, 0.01D).offset(-d0, -d1, -d2);
- drawFilledBoundingBox(bb, 1f, NotEnoughUpdates.INSTANCE.config.itemOverlays.etherwarpHighlightColour);
+ if (denyTpReason == null || !NotEnoughUpdates.INSTANCE.config.itemOverlays.disableOverlayWhenFailed) {
+ AxisAlignedBB box = etherwarpRaycast.state.getBlock().getSelectedBoundingBox(
+ Minecraft.getMinecraft().theWorld,
+ etherwarpRaycast.pos
+ );
+ AxisAlignedBB bb = box.expand(0.01D, 0.01D, 0.01D).offset(-d0, -d1, -d2);
+ drawFilledBoundingBox(
+ bb,
+ 1f,
+ NotEnoughUpdates.INSTANCE.config.itemOverlays.etherwarpHighlightColour
+ );
- GlStateManager.disableDepth();
- drawOutlineBoundingBox(bb, 2f, NotEnoughUpdates.INSTANCE.config.itemOverlays.etherwarpHighlightColour);
- GlStateManager.enableDepth();
+ GlStateManager.disableDepth();
+ drawOutlineBoundingBox(
+ bb,
+ 2f,
+ NotEnoughUpdates.INSTANCE.config.itemOverlays.etherwarpHighlightColour
+ );
+ GlStateManager.enableDepth();
- GlStateManager.depthMask(true);
- GlStateManager.enableTexture2D();
- GlStateManager.disableBlend();
+ GlStateManager.depthMask(true);
+ GlStateManager.enableTexture2D();
+ GlStateManager.disableBlend();
+ }
if (NotEnoughUpdates.INSTANCE.config.itemOverlays.etherwarpZoom) {
float distFactor = 1 -
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomSkulls.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomSkulls.java
index 1157e73a..95ec0f7a 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomSkulls.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomSkulls.java
@@ -1,3 +1,22 @@
+/*
+ * 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.common.collect.Maps;
@@ -14,8 +33,17 @@ import net.minecraft.client.model.ModelSkeletonHead;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
-import net.minecraft.client.renderer.block.model.*;
-import net.minecraft.client.renderer.texture.*;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.block.model.BlockPart;
+import net.minecraft.client.renderer.block.model.BlockPartFace;
+import net.minecraft.client.renderer.block.model.FaceBakery;
+import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
+import net.minecraft.client.renderer.block.model.ModelBlock;
+import net.minecraft.client.renderer.texture.AbstractTexture;
+import net.minecraft.client.renderer.texture.IIconCreator;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener;
@@ -31,7 +59,11 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class CustomSkulls implements IResourceManagerReloadListener {
private static final CustomSkulls INSTANCE = new CustomSkulls();
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DamageCommas.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DamageCommas.java
index 9c93b9c5..b1ab11c1 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DamageCommas.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DamageCommas.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
@@ -26,15 +45,18 @@ public class DamageCommas {
};
private static final char STAR = '\u2727';
+ private static final char OVERLOAD_STAR = '\u272F';
private static final Pattern PATTERN_CRIT = Pattern.compile(
- "\u00a7f" + STAR + "((?:\u00a7.\\d)+)\u00a7." + STAR + "(.*)");
- private static final Pattern PATTERN_NO_CRIT = Pattern.compile("\u00a77(\\d+)(.*)");
+ "\u00a7f" + STAR + "((?:\u00a7.\\d(?:§.,)?)+)\u00a7." + STAR + "(.*)");
+ private static final Pattern PATTERN_NO_CRIT = Pattern.compile("(\u00a7.)([\\d+,]*)(.*)");
+ private static final Pattern OVERLOAD_PATTERN = Pattern.compile("(\u00a7.)" + OVERLOAD_STAR + "((?:\u00a7.[\\d,])+)(\u00a7.)" + OVERLOAD_STAR + "\u00a7r");
public static IChatComponent replaceName(EntityLivingBase entity) {
if (!entity.hasCustomName()) return entity.getDisplayName();
IChatComponent name = entity.getDisplayName();
- if (NotEnoughUpdates.INSTANCE.config.misc.damageIndicatorStyle == 0) return name;
+ if (!NotEnoughUpdates.INSTANCE.config.misc.damageIndicatorStyle2) return name;
+ if (!NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) return name;
if (replacementMap.containsKey(entity)) {
ChatComponentText component = replacementMap.get(entity);
@@ -50,17 +72,23 @@ public class DamageCommas {
String suffix;
Matcher matcherCrit = PATTERN_CRIT.matcher(formatted);
+ Matcher matcherOverload = OVERLOAD_PATTERN.matcher(formatted);
if (matcherCrit.matches()) {
crit = true;
- numbers = StringUtils.cleanColour(matcherCrit.group(1));
+ numbers = StringUtils.cleanColour(matcherCrit.group(1)).replace(",", "");
prefix = "\u00a7f" + STAR;
suffix = "\u00a7f" + STAR + matcherCrit.group(2);
- } else {
+ } else if (matcherOverload.matches()) {
+ crit = true;
+ numbers = StringUtils.cleanColour(matcherOverload.group(2)).replace(",", "");
+ prefix = matcherOverload.group(1) + OVERLOAD_STAR;
+ suffix = matcherOverload.group(3) + OVERLOAD_STAR + "\u00a7r";
+ } else {
Matcher matcherNoCrit = PATTERN_NO_CRIT.matcher(formatted);
if (matcherNoCrit.matches()) {
- numbers = matcherNoCrit.group(1);
- prefix = "\u00A77";
- suffix = "\u00A7r" + matcherNoCrit.group(2);
+ numbers = matcherNoCrit.group(2).replace(",", "");
+ prefix = matcherNoCrit.group(1);
+ suffix = "\u00A7r" + matcherNoCrit.group(3);
} else {
replacementMap.put(entity, null);
return name;
@@ -72,10 +100,10 @@ public class DamageCommas {
try {
int number = Integer.parseInt(numbers);
- if (number > 999 && NotEnoughUpdates.INSTANCE.config.misc.damageIndicatorStyle == 2) {
+ if (number > 999) {
newFormatted.append(Utils.shortNumberFormat(number, 0));
} else {
- newFormatted.append(NumberFormat.getIntegerInstance().format(number));
+ return name;
}
} catch (NumberFormatException e) {
replacementMap.put(entity, null);
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DwarvenMinesWaypoints.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DwarvenMinesWaypoints.java
index f7f9003c..c1c7dca1 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DwarvenMinesWaypoints.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DwarvenMinesWaypoints.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java
index 16b59b05..d5cbfdde 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java
@@ -1,6 +1,26 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiChest;
@@ -17,13 +37,18 @@ import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
+import org.lwjgl.input.Keyboard;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
public class EnchantingSolvers {
- private static SolverType currentSolver = SolverType.NONE;
+ public static SolverType currentSolver = SolverType.NONE;
- private enum SolverType {
+ public enum SolverType {
NONE,
CHRONOMATRON,
ULTRASEQUENCER,
@@ -72,20 +97,14 @@ public class EnchantingSolvers {
return;
}
- if (event.gui instanceof GuiChest) {
- GuiChest chest = (GuiChest) event.gui;
- ContainerChest container = (ContainerChest) chest.inventorySlots;
- String containerName = container.getLowerChestInventory().getDisplayName().getUnformattedText();
- String lower = containerName.toLowerCase();
-
- if (!lower.contains("stakes")) {
- if (lower.startsWith("chronomatron")) {
- currentSolver = SolverType.CHRONOMATRON;
- } else if (lower.startsWith("ultrasequencer")) {
- currentSolver = SolverType.ULTRASEQUENCER;
- } else if (lower.startsWith("superpairs")) {
- currentSolver = SolverType.SUPERPAIRS;
- }
+ String openChestName = Utils.getOpenChestName();
+ if (!openChestName.contains("Stakes")) {
+ if (openChestName.startsWith("Chronomatron")) {
+ currentSolver = SolverType.CHRONOMATRON;
+ } else if (openChestName.startsWith("Ultrasequencer")) {
+ currentSolver = SolverType.ULTRASEQUENCER;
+ } else if (openChestName.startsWith("Superpairs")) {
+ currentSolver = SolverType.SUPERPAIRS;
}
}
}
@@ -290,97 +309,88 @@ public class EnchantingSolvers {
return false;
}
- public static boolean onStackClick(ItemStack stack, int windowId, int slotId, int mouseButtonClicked, int mode) {
- if (!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.enableEnchantingSolvers) {
- return false;
+ @SubscribeEvent
+ public void onStackClick(SlotClickEvent event) {
+ if (!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.enableEnchantingSolvers
+ || !NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) {
+ return;
}
- if (!NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) {
- return false;
+ ItemStack stack = event.slot.getStack();
+ if (stack == null || stack.getDisplayName() == null) {
+ return;
}
+ String displayName = stack.getDisplayName();
+ if (!(Minecraft.getMinecraft().currentScreen instanceof GuiChest)) {
+ return;
+ }
+ GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
+ ContainerChest container = (ContainerChest) chest.inventorySlots;
+ IInventory lower = container.getLowerChestInventory();
+
+ if (currentSolver == SolverType.CHRONOMATRON) {
+ ItemStack timerStack = lower.getStackInSlot(lower.getSizeInventory() - 5);
+ if (timerStack == null) {
+ return;
+ }
- if (stack != null && stack.getDisplayName() != null) {
- String displayName = stack.getDisplayName();
- if (Minecraft.getMinecraft().currentScreen instanceof GuiChest) {
- GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
- ContainerChest container = (ContainerChest) chest.inventorySlots;
- IInventory lower = container.getLowerChestInventory();
-
- if (currentSolver == SolverType.CHRONOMATRON) {
- ItemStack timerStack = lower.getStackInSlot(lower.getSizeInventory() - 5);
- if (timerStack == null) {
- return false;
- }
-
- boolean yepClock = timerStack.getItem() == Items.clock;
- if (timerStack.getItem() == Item.getItemFromBlock(Blocks.glowstone) ||
- (yepClock && (!addToChronomatron || chronomatronOrder.size() < lastChronomatronSize + 1))) {
- return true;
- } else if (yepClock) {
- long currentTime = System.currentTimeMillis();
- if (currentTime - millisLastClick < 150) {
- return true;
- }
+ boolean yepClock = timerStack.getItem() == Items.clock;
+ if (timerStack.getItem() == Item.getItemFromBlock(Blocks.glowstone) ||
+ (yepClock && (!addToChronomatron || chronomatronOrder.size() < lastChronomatronSize + 1))) {
+ event.setCanceled(true);
+ return;
+ }
+ if (yepClock) {
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - millisLastClick < 150) {
+ event.setCanceled(true);
+ return;
+ }
- if (chronomatronReplayIndex < chronomatronOrder.size()) {
- String chronomatronCurrent = chronomatronOrder.get(chronomatronReplayIndex);
- if (!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.preventMisclicks1 ||
- chronomatronCurrent.equals(displayName)) {
- chronomatronReplayIndex++;
- Minecraft.getMinecraft().playerController.windowClick(windowId, slotId,
- 2, mode, Minecraft.getMinecraft().thePlayer
- );
- millisLastClick = currentTime;
- }
- /*if (chronomatronCurrent.equals(displayName)) {
- chronomatronReplayIndex++;
- }
- Minecraft.getMinecraft().playerController.windowClick(windowId, slotId,
- 2, mode, Minecraft.getMinecraft().thePlayer);
- millisLastClick = currentTime;*/
- }
- return true;
- }
- } else if (currentSolver == SolverType.ULTRASEQUENCER) {
- ItemStack timerStack = lower.getStackInSlot(lower.getSizeInventory() - 5);
- if (timerStack == null) {
- return false;
+ if (chronomatronReplayIndex < chronomatronOrder.size()) {
+ String chronomatronCurrent = chronomatronOrder.get(chronomatronReplayIndex);
+ if ((!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.preventMisclicks1 ||
+ chronomatronCurrent.equals(displayName) || Keyboard.getEventKey() == Keyboard.KEY_LSHIFT) &&
+ stack.getItem() != Item.getItemFromBlock(Blocks.stained_glass_pane) && event.slotId != 4 &&
+ event.slotId != 49) {
+ chronomatronReplayIndex++;
+ millisLastClick = currentTime;
+ event.usePickblockInstead();
+ return;
}
+ }
+ event.setCanceled(true);
+ return;
+ }
+ }
+ if (currentSolver == SolverType.ULTRASEQUENCER) {
+ ItemStack timerStack = lower.getStackInSlot(lower.getSizeInventory() - 5);
+ if (timerStack == null) {
+ return;
+ }
- boolean yepClock = timerStack.getItem() == Items.clock;
- if (yepClock) {
- UltrasequencerItem current = ultraSequencerOrder.get(ultrasequencerReplayIndex);
- if (current == null) {
- return true;
- }
- long currentTime = System.currentTimeMillis();
- if (currentTime - millisLastClick > 150 &&
- (!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.preventMisclicks1 ||
- current.containerIndex == slotId)) {
- ultrasequencerReplayIndex++;
- Minecraft.getMinecraft().playerController.windowClick(windowId, slotId,
- 2, mode, Minecraft.getMinecraft().thePlayer
- );
- millisLastClick = currentTime;
- }
- /*if (currentTime - millisLastClick > 150) {
- if (current.containerIndex == slotId) {
- ultrasequencerReplayIndex++;
- }
- Minecraft.getMinecraft().playerController.windowClick(windowId, slotId,
- 2, mode, Minecraft.getMinecraft().thePlayer);
- millisLastClick = currentTime;
- }*/
- return true;
- } else {
- return true;
- }
- } else if (currentSolver == SolverType.SUPERPAIRS) {
- lastSlotClicked = slotId;
+ boolean yepClock = timerStack.getItem() == Items.clock;
+ if (yepClock) {
+ UltrasequencerItem current = ultraSequencerOrder.get(ultrasequencerReplayIndex);
+ long currentTime = System.currentTimeMillis();
+ if (current == null) {
+ event.setCanceled(true);
+ return;
+ }
+ if (currentTime - millisLastClick > 150 &&
+ (!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.preventMisclicks1 ||
+ current.containerIndex == event.slotId || Keyboard.getEventKey() == Keyboard.KEY_LSHIFT) &&
+ (event.slotId < 45 && event.slotId > 8)) {
+ ultrasequencerReplayIndex++;
+ millisLastClick = currentTime;
+ event.usePickblockInstead();
+ return;
}
}
+ event.setCanceled(true);
+ } else if (currentSolver == SolverType.SUPERPAIRS) {
+ lastSlotClicked = event.slotId;
}
- return false;
}
public static void processInventoryContents(boolean fromTick) {
@@ -573,4 +583,8 @@ public class EnchantingSolvers {
processInventoryContents(true);
}
+
+ public static boolean disableButtons() {
+ return currentSolver != SolverType.NONE && NotEnoughUpdates.INSTANCE.config.enchantingSolvers.hideButtons;
+ }
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FairySouls.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FairySouls.java
index 725d4b9d..75f1b427 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FairySouls.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FairySouls.java
@@ -1,3 +1,22 @@
+/*
+ * 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.common.reflect.TypeToken;
@@ -21,70 +40,305 @@ import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TreeMap;
import java.util.stream.Collectors;
public class FairySouls {
+ private static FairySouls instance = null;
private static final String unknownProfile = "unknown";
- private static List<BlockPos> currentSoulList = null;
- private static List<BlockPos> currentSoulListClose = null;
- private static HashMap<String, HashMap<String, Set<Integer>>> loadedFoundSouls = new HashMap<>();
- private static HashMap<String, Set<Integer>> getFoundSoulsForProfile() {
+ private boolean trackSouls;
+ private boolean showSouls;
+ private HashMap<String, HashMap<String, Set<Integer>>> allProfilesFoundSouls = new HashMap<>();
+ private List<BlockPos> allSoulsInCurrentLocation;
+ private Set<Integer> foundSoulsInLocation;
+ private TreeMap<Double, BlockPos> missingSoulsDistanceSqMap;
+ private List<BlockPos> closestMissingSouls;
+ private String currentLocation;
+ private BlockPos lastPlayerPos;
+
+ public static FairySouls getInstance() {
+ if (instance == null) {
+ instance = new FairySouls();
+ }
+ return instance;
+ }
+
+ @SubscribeEvent
+ public void onWorldLoad(WorldEvent.Load event) {
+ currentLocation = null;
+ trackSouls = NotEnoughUpdates.INSTANCE.config.misc.trackFairySouls;
+ showSouls = NotEnoughUpdates.INSTANCE.config.misc.fariySoul;
+ }
+
+ public void initializeLocation() {
+ if (!trackSouls || currentLocation == null) {
+ return;
+ }
+
+ foundSoulsInLocation = null;
+ closestMissingSouls = new ArrayList<>();
+ missingSoulsDistanceSqMap = new TreeMap<>();
+ lastPlayerPos = BlockPos.ORIGIN;
+
+ allSoulsInCurrentLocation = loadLocationFairySoulsFromConfig(currentLocation);
+ if (allSoulsInCurrentLocation == null) {
+ return;
+ }
+
+ foundSoulsInLocation = getFoundSoulsForProfile()
+ .computeIfAbsent(currentLocation, k -> new HashSet<>());
+ refreshMissingSoulInfo(true);
+ }
+
+ private void refreshMissingSoulInfo(boolean force) {
+ if (allSoulsInCurrentLocation == null) return;
+
+ BlockPos currentPlayerPos = Minecraft.getMinecraft().thePlayer.getPosition();
+ if (lastPlayerPos.equals(currentPlayerPos) && !force) {
+ return;
+ }
+ lastPlayerPos = currentPlayerPos;
+
+ missingSoulsDistanceSqMap.clear();
+ for (int i = 0; i < allSoulsInCurrentLocation.size(); i++) {
+ if (foundSoulsInLocation.contains(i)) {
+ continue;
+ }
+ BlockPos pos = allSoulsInCurrentLocation.get(i);
+ double distSq = pos.distanceSq(lastPlayerPos);
+ missingSoulsDistanceSqMap.put(distSq, pos);
+ }
+ closestMissingSouls.clear();
+ if (missingSoulsDistanceSqMap.isEmpty()) {
+ return;
+ }
+
+ // rebuild the list of the closest ones
+ int maxSouls = 15;
+ int souls = 0;
+ for (BlockPos pos : missingSoulsDistanceSqMap.values()) {
+ closestMissingSouls.add(pos);
+ if (++souls >= maxSouls) break;
+ }
+ }
+
+ private int interpolateColors(int color1, int color2, double factor) {
+ int r1 = ((color1 >> 16) & 0xff);
+ int g1 = ((color1 >> 8) & 0xff);
+ int b1 = (color1 & 0xff);
+
+ int r2 = (color2 >> 16) & 0xff;
+ int g2 = (color2 >> 8) & 0xff;
+ int b2 = color2 & 0xff;
+
+ int r3 = r1 + (int) Math.round(factor * (r2 - r1));
+ int g3 = g1 + (int) Math.round(factor * (g2 - g1));
+ int b3 = b1 + (int) Math.round(factor * (b2 - b1));
+
+ return (r3 & 0xff) << 16 |
+ (g3 & 0xff) << 8 |
+ (b3 & 0xff);
+ }
+
+ private double normalize(double value, double min, double max) {
+ return ((value - min) / (max - min));
+ }
+
+ @SubscribeEvent
+ public void onRenderLast(RenderWorldLastEvent event) {
+ if (!showSouls || !trackSouls || currentLocation == null || closestMissingSouls.isEmpty()) {
+ return;
+ }
+
+ int closeColor = 0x772991; // 0xa839ce
+ int farColor = 0xCEB4D1;
+ double farSoulDistSq = lastPlayerPos.distanceSq(closestMissingSouls.get(closestMissingSouls.size() - 1));
+ for (BlockPos currentSoul : closestMissingSouls) {
+ double currentDistSq = lastPlayerPos.distanceSq(currentSoul);
+ double factor = normalize(currentDistSq, 0.0, farSoulDistSq);
+ int rgb = interpolateColors(closeColor, farColor, Math.min(0.40, factor));
+ RenderUtils.renderBeaconBeamOrBoundingBox(currentSoul, rgb, 1.0f, event.partialTicks);
+ }
+ }
+
+ public void setShowFairySouls(boolean enabled) {
+ NotEnoughUpdates.INSTANCE.config.misc.fariySoul = enabled;
+ showSouls = enabled;
+ }
+
+ public void setTrackFairySouls(boolean enabled) {
+ NotEnoughUpdates.INSTANCE.config.misc.trackFairySouls = enabled;
+ trackSouls = enabled;
+ }
+
+ public void markClosestSoulFound() {
+ if (!trackSouls) return;
+ int closestIndex = -1;
+ double closestDistSq = 10 * 10;
+ for (int i = 0; i < allSoulsInCurrentLocation.size(); i++) {
+ BlockPos pos = allSoulsInCurrentLocation.get(i);
+
+ double distSq = pos.distanceSq(Minecraft.getMinecraft().thePlayer.getPosition());
+
+ if (distSq < closestDistSq) {
+ closestDistSq = distSq;
+ closestIndex = i;
+ }
+ }
+ if (closestIndex != -1) {
+ foundSoulsInLocation.add(closestIndex);
+ refreshMissingSoulInfo(true);
+ }
+ }
+
+ public void markAllAsFound() {
+ if (!trackSouls) {
+ print(EnumChatFormatting.RED + "Fairy soul tracking is turned off, turn it on using /neu");
+ return;
+ }
+ if (allSoulsInCurrentLocation == null) {
+ print(EnumChatFormatting.RED + "No fairy souls found in your current world");
+ return;
+ }
+ for (int i = 0; i < allSoulsInCurrentLocation.size(); i++) {
+ foundSoulsInLocation.add(i);
+ }
+ refreshMissingSoulInfo(true);
+
+ print(EnumChatFormatting.DARK_PURPLE + "Marked all fairy souls as found");
+ }
+
+ public void markAllAsMissing() {
+ if (!trackSouls) {
+ print(EnumChatFormatting.RED + "Fairy soul tracking is turned off, turn it on using /neu");
+ return;
+ }
+ if (allSoulsInCurrentLocation == null) {
+ print(EnumChatFormatting.RED + "No fairy souls found in your current world");
+ return;
+ }
+ foundSoulsInLocation.clear();
+ refreshMissingSoulInfo(true);
+
+ print(EnumChatFormatting.DARK_PURPLE + "Marked all fairy souls as not found");
+ }
+
+ private HashMap<String, Set<Integer>> getFoundSoulsForProfile() {
String profile = SBInfo.getInstance().currentProfile;
if (profile == null) {
- if (loadedFoundSouls.containsKey(unknownProfile))
- return loadedFoundSouls.get(unknownProfile);
+ if (allProfilesFoundSouls.containsKey(unknownProfile))
+ return allProfilesFoundSouls.get(unknownProfile);
} else {
profile = profile.toLowerCase(Locale.getDefault());
- if (loadedFoundSouls.containsKey(unknownProfile)) {
- HashMap<String, Set<Integer>> unknownProfileData = loadedFoundSouls.remove(unknownProfile);
- loadedFoundSouls.put(profile, unknownProfileData);
+ if (allProfilesFoundSouls.containsKey(unknownProfile)) {
+ HashMap<String, Set<Integer>> unknownProfileData = allProfilesFoundSouls.remove(unknownProfile);
+ allProfilesFoundSouls.put(profile, unknownProfileData);
return unknownProfileData;
}
- if (loadedFoundSouls.containsKey(profile)) {
- return loadedFoundSouls.get(profile);
+ if (allProfilesFoundSouls.containsKey(profile)) {
+ return allProfilesFoundSouls.get(profile);
} else {
- //create a new entry for this profile
+ // Create a new entry for this profile
HashMap<String, Set<Integer>> profileData = new HashMap<>();
- loadedFoundSouls.put(profile, profileData);
+ allProfilesFoundSouls.put(profile, profileData);
return profileData;
}
}
return new HashMap<>();
}
- public static void load(File file, Gson gson) {
- loadedFoundSouls = new HashMap<>();
+ private static List<BlockPos> loadLocationFairySoulsFromConfig(String currentLocation) {
+ JsonObject fairySoulList = Constants.FAIRYSOULS;
+ if (fairySoulList == null) {
+ return null;
+ }
+
+ if (!fairySoulList.has(currentLocation) || !fairySoulList.get(currentLocation).isJsonArray()) {
+ return null;
+ }
+
+ JsonArray locations = fairySoulList.get(currentLocation).getAsJsonArray();
+ List<BlockPos> locationSouls = new ArrayList<>();
+ for (int i = 0; i < locations.size(); i++) {
+ try {
+ String coord = locations.get(i).getAsString();
+
+ String[] split = coord.split(",");
+ if (split.length == 3) {
+ String xS = split[0];
+ String yS = split[1];
+ String zS = split[2];
+
+ int x = Integer.parseInt(xS);
+ int y = Integer.parseInt(yS);
+ int z = Integer.parseInt(zS);
+
+ locationSouls.add(new BlockPos(x, y, z));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ if (locationSouls.size() == 0) {
+ return null;
+ }
+
+ return locationSouls;
+ }
+
+ public void loadFoundSoulsForAllProfiles(File file, Gson gson) {
+ allProfilesFoundSouls = new HashMap<>();
String fileContent;
try {
fileContent = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining(System.lineSeparator()));
} catch (FileNotFoundException e) {
+ // it is possible that the collected_fairy_souls.json won't exist
return;
}
try {
//noinspection UnstableApiUsage
Type multiProfileSoulsType = new TypeToken<HashMap<String, HashMap<String, Set<Integer>>>>() {}.getType();
- loadedFoundSouls = gson.fromJson(fileContent, multiProfileSoulsType);
+ allProfilesFoundSouls = gson.fromJson(fileContent, multiProfileSoulsType);
+ if (allProfilesFoundSouls == null){
+ allProfilesFoundSouls = new HashMap<>();
+ }
} catch (JsonSyntaxException e) {
//The file is in the old format, convert it to the new one and set the profile to unknown
try {
//noinspection UnstableApiUsage
Type singleProfileSoulsType = new TypeToken<HashMap<String, Set<Integer>>>() {}.getType();
- loadedFoundSouls.put(unknownProfile, gson.fromJson(fileContent, singleProfileSoulsType));
+ allProfilesFoundSouls.put(unknownProfile, gson.fromJson(fileContent, singleProfileSoulsType));
} catch (JsonSyntaxException e2) {
System.err.println("Can't read file containing collected fairy souls, resetting.");
}
}
}
- public static void save(File file, Gson gson) {
+ public void saveFoundSoulsForAllProfiles(File file, Gson gson) {
try {
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
@@ -95,78 +349,24 @@ public class FairySouls {
StandardCharsets.UTF_8
))
) {
- writer.write(gson.toJson(loadedFoundSouls));
+ writer.write(gson.toJson(allProfilesFoundSouls));
}
- } catch (IOException ignored) {
+ } catch (IOException e) {
+ e.printStackTrace();
}
}
- public static void tick() {
- if (!NotEnoughUpdates.INSTANCE.config.misc.fariySoul) return;
-
- if (Minecraft.getMinecraft().theWorld == null) {
- currentSoulList = null;
- return;
- }
-
- JsonObject fairySouls = Constants.FAIRYSOULS;
- if (fairySouls == null) return;
-
+ public void tick() {
+ if (!trackSouls) return;
String location = SBInfo.getInstance().getLocation();
- if (location == null) {
- currentSoulList = null;
- return;
- }
+ if (location == null || location.isEmpty()) return;
- if (currentSoulList == null) {
- if (fairySouls.has(location) && fairySouls.get(location).isJsonArray()) {
- JsonArray locations = fairySouls.get(location).getAsJsonArray();
- currentSoulList = new ArrayList<>();
- for (int i = 0; i < locations.size(); i++) {
- try {
- String coord = locations.get(i).getAsString();
-
- String[] split = coord.split(",");
- if (split.length == 3) {
- String xS = split[0];
- String yS = split[1];
- String zS = split[2];
-
- int x = Integer.parseInt(xS);
- int y = Integer.parseInt(yS);
- int z = Integer.parseInt(zS);
-
- currentSoulList.add(new BlockPos(x, y, z));
- }
- } catch (Exception ignored) {
- }
- }
- }
+ if (!location.equals(currentLocation)) {
+ currentLocation = location;
+ initializeLocation();
}
- if (currentSoulList != null && !currentSoulList.isEmpty()) {
- TreeMap<Double, BlockPos> distanceSqMap = new TreeMap<>();
-
- HashMap<String, Set<Integer>> foundSouls = getFoundSoulsForProfile();
-
- Set<Integer> found = foundSouls.computeIfAbsent(location, k -> new HashSet<>());
-
- for (int i = 0; i < currentSoulList.size(); i++) {
- if (found.contains(i)) continue;
-
- BlockPos pos = currentSoulList.get(i);
- double distSq = pos.distanceSq(Minecraft.getMinecraft().thePlayer.getPosition());
- distanceSqMap.put(distSq, pos);
- }
-
- int maxSouls = 15;
- int souls = 0;
- currentSoulListClose = new ArrayList<>();
- for (BlockPos pos : distanceSqMap.values()) {
- currentSoulListClose.add(pos);
- if (++souls >= maxSouls) break;
- }
- }
+ refreshMissingSoulInfo(false);
}
private static void print(String s) {
@@ -186,63 +386,24 @@ public class FairySouls {
}
print(EnumChatFormatting.GOLD.toString() + EnumChatFormatting.BOLD + " Commands:");
print(EnumChatFormatting.YELLOW + "/neusouls help - Display this message");
- print(EnumChatFormatting.YELLOW + "/neusouls on/off - Enable/disable the waypoint markers");
+ print(EnumChatFormatting.YELLOW + "/neusouls on/off - Enable/disable showing waypoint markers");
print(EnumChatFormatting.YELLOW +
"/neusouls clear/unclear - Marks every waypoint in your current world as completed/uncompleted");
print("");
}
@SubscribeEvent
- public void onWorldUnload(WorldEvent.Unload event) {
- currentSoulList = null;
- }
-
- @SubscribeEvent
public void onChatReceived(ClientChatReceivedEvent event) {
- if (currentSoulList == null) return;
+ if (!trackSouls || event.type == 2) return;
- if (event.message.getFormattedText().equals("\u00A7r\u00A7dYou have already found that Fairy Soul!\u00A7r") ||
- event.message.getFormattedText().equals(
- "\u00A7d\u00A7lSOUL! \u00A7fYou found a \u00A7r\u00A7dFairy Soul\u00A7r\u00A7f!\u00A7r")) {
- String location = SBInfo.getInstance().getLocation();
- if (location == null) return;
-
- int closestIndex = -1;
- double closestDistSq = 10 * 10;
- for (int i = 0; i < currentSoulList.size(); i++) {
- BlockPos pos = currentSoulList.get(i);
-
- double distSq = pos.distanceSq(Minecraft.getMinecraft().thePlayer.getPosition());
-
- if (distSq < closestDistSq) {
- closestDistSq = distSq;
- closestIndex = i;
- }
- }
- if (closestIndex != -1) {
- HashMap<String, Set<Integer>> foundSouls = getFoundSoulsForProfile();
- Set<Integer> found = foundSouls.computeIfAbsent(location, k -> new HashSet<>());
- found.add(closestIndex);
- }
- }
- }
-
- @SubscribeEvent
- public void onRenderLast(RenderWorldLastEvent event) {
- if (!NotEnoughUpdates.INSTANCE.config.misc.fariySoul) return;
-
- String location = SBInfo.getInstance().getLocation();
- if (location == null) return;
- if (currentSoulList == null || currentSoulList.isEmpty()) return;
-
- int rgb = 0xa839ce;
- for (BlockPos currentSoul : currentSoulListClose) {
- RenderUtils.renderBeaconBeamOrBoundingBox(currentSoul, rgb, 1.0f, event.partialTicks);
+ if (event.message.getUnformattedText().equals("You have already found that Fairy Soul!") ||
+ event.message.getUnformattedText().equals(
+ "SOUL! You found a Fairy Soul!")) {
+ markClosestSoulFound();
}
}
public static class FairySoulsCommand extends ClientCommandBase {
-
public FairySoulsCommand() {
super("neusouls");
}
@@ -258,73 +419,36 @@ public class FairySouls {
printHelp();
return;
}
- String subcommand = args[0].toLowerCase();
+ String subcommand = args[0].toLowerCase();
switch (subcommand) {
case "help":
printHelp();
- return;
+ break;
case "on":
case "enable":
+ if (!FairySouls.instance.trackSouls) {
+ print(
+ EnumChatFormatting.RED + "Fairy soul tracking is off, enable it using /neu before using this command");
+ return;
+ }
print(EnumChatFormatting.DARK_PURPLE + "Enabled fairy soul waypoints");
- NotEnoughUpdates.INSTANCE.config.misc.fariySoul = true;
- return;
+ FairySouls.getInstance().setShowFairySouls(true);
+ break;
case "off":
case "disable":
+ FairySouls.getInstance().setShowFairySouls(false);
print(EnumChatFormatting.DARK_PURPLE + "Disabled fairy soul waypoints");
- NotEnoughUpdates.INSTANCE.config.misc.fariySoul = false;
- return;
- case "clear": {
- String location = SBInfo.getInstance().getLocation();
- if (currentSoulList == null || location == null) {
- print(EnumChatFormatting.RED + "No fairy souls found in your current world");
- } else {
- HashMap<String, Set<Integer>> foundSouls = getFoundSoulsForProfile();
- Set<Integer> found = foundSouls.computeIfAbsent(location, k -> new HashSet<>());
- for (int i = 0; i < currentSoulList.size(); i++) {
- found.add(i);
- }
- String profileName = SBInfo.getInstance().currentProfile;
- if (profileName == null) {
- if (loadedFoundSouls.containsKey(unknownProfile)) {
- loadedFoundSouls.get(unknownProfile).put(location, found);
- }
- } else {
- profileName = profileName.toLowerCase();
- if (!loadedFoundSouls.containsKey(profileName)) {
- HashMap<String, Set<Integer>> profileData = new HashMap<>();
- loadedFoundSouls.put(profileName, profileData);
- }
- loadedFoundSouls.get(profileName).put(location, found);
- }
- print(EnumChatFormatting.DARK_PURPLE + "Marked all fairy souls as found");
- }
- }
- return;
+ break;
+ case "clear":
+ FairySouls.getInstance().markAllAsFound();
+ break;
case "unclear":
- String location = SBInfo.getInstance().getLocation();
- if (location == null) {
- print(EnumChatFormatting.RED + "No fairy souls found in your current world");
- } else {
- String profileName = SBInfo.getInstance().currentProfile;
- if (profileName == null) {
- if (loadedFoundSouls.containsKey(unknownProfile)) {
- loadedFoundSouls.get(unknownProfile).remove(location);
- }
- } else {
- profileName = profileName.toLowerCase();
- if (!loadedFoundSouls.containsKey(profileName)) {
- HashMap<String, Set<Integer>> profileData = new HashMap<>();
- loadedFoundSouls.put(profileName, profileData);
- }
- loadedFoundSouls.get(profileName).remove(location);
- }
- print(EnumChatFormatting.DARK_PURPLE + "Marked all fairy souls as not found");
- }
- return;
+ FairySouls.getInstance().markAllAsMissing();
+ break;
+ default:
+ print(EnumChatFormatting.RED + "Unknown subcommand: " + subcommand);
}
-
- print(EnumChatFormatting.RED + "Unknown subcommand: " + subcommand);
}
}
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FancyPortals.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FancyPortals.java
deleted file mode 100644
index 12e65e65..00000000
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FancyPortals.java
+++ /dev/null
@@ -1,293 +0,0 @@
-package io.github.moulberry.notenoughupdates.miscfeatures;
-
-import io.github.moulberry.notenoughupdates.util.Utils;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.Gui;
-import net.minecraft.client.renderer.GlStateManager;
-import net.minecraft.client.renderer.RenderGlobal;
-import net.minecraft.client.renderer.WorldRenderer;
-import net.minecraft.client.renderer.chunk.CompiledChunk;
-import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
-import net.minecraft.client.renderer.vertex.VertexFormat;
-import net.minecraft.client.renderer.vertex.VertexFormatElement;
-import net.minecraft.entity.Entity;
-import net.minecraft.entity.EntityLivingBase;
-import net.minecraft.network.play.server.S07PacketRespawn;
-import net.minecraft.util.MathHelper;
-import net.minecraft.util.ResourceLocation;
-import net.minecraftforge.client.event.RenderLivingEvent;
-import net.minecraftforge.client.event.RenderWorldLastEvent;
-import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
-import org.lwjgl.input.Keyboard;
-import org.lwjgl.opengl.GL11;
-import org.lwjgl.util.glu.Project;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-
-public class FancyPortals {
- private static final ResourceLocation[] RENDERS = new ResourceLocation[6];
-
- static {
- for (int i = 0; i < 6; i++) {
- RENDERS[i] = new ResourceLocation("notenoughupdates:portal_panoramas/nether/pansc-" + (i + 1) + ".png");
- }
- }
-
- public static int perspectiveId = -1;
-
- public static boolean overridePerspective() {
- if (perspectiveId >= 0 && !Keyboard.isKeyDown(Keyboard.KEY_K)) {
- if (perspectiveId == 0) {
- GlStateManager.matrixMode(5889);
- GlStateManager.loadIdentity();
- GlStateManager.ortho(0.0D, 7, 7, 0.0D, -100D, 100D);
- GlStateManager.scale(1, 1, -1);
- GlStateManager.matrixMode(5888);
- GlStateManager.loadIdentity();
- GlStateManager.translate(3.5F, 3.5F, -1.0F);
- GlStateManager.rotate(-90, 1, 0, 0);
- } else if (perspectiveId <= 4) {
- GlStateManager.matrixMode(5889);
- GlStateManager.loadIdentity();
- Project.gluPerspective(90, 1, 0.05F, 160 * MathHelper.SQRT_2);
- GlStateManager.matrixMode(5888);
- GlStateManager.loadIdentity();
- GlStateManager.rotate(perspectiveId * 90, 0, 1, 0);
- GlStateManager.translate(0, -3.5f, 0);
- } else {
- GlStateManager.matrixMode(5889);
- GlStateManager.loadIdentity();
- Project.gluPerspective(90, 1, 0.05F, 160 * MathHelper.SQRT_2);
- GlStateManager.matrixMode(5888);
- GlStateManager.loadIdentity();
- GlStateManager.rotate(-90, 1, 0, 0);
- GlStateManager.translate(0, -3.5f, 0);
- }
-
- return true;
- }
- return false;
- }
-
- private static WorldRenderer surfaceWorldRenderer = null;
-
- private static WorldRenderer getSurfaceWorldRenderer() {
- if (surfaceWorldRenderer != null && !Keyboard.isKeyDown(Keyboard.KEY_O)) {
- return surfaceWorldRenderer;
- }
-
- surfaceWorldRenderer = createSurfaceWorldRenderer();
-
- return surfaceWorldRenderer;
- }
-
- private static void drawPoint(WorldRenderer worldRenderer, int x, int y) {
- float xDist = 1 - Math.abs(x - 50) / 50f;
- float yDist = 1 - Math.abs(y - 50) / 50f;
- float distToEdge = Math.min(xDist, yDist);
-
- float z = 0.4142f;
- if (distToEdge < 1 / 3.5f) {
- if (y > 50 && yDist < xDist) {
- float circleH = 1.414f - distToEdge * 3.5f * 1.414f;
- z = (float) Math.sqrt(2f - circleH * circleH);
- z *= 0.4142f / 1.4142f;
- } else {
- float circleH = 1 - distToEdge * 3.5f;
- z = (float) Math.sqrt(2f - circleH * circleH) - 1;
- }
- }
-
- worldRenderer.pos(x * 7 / 100f, y * 7 / 100f, z).tex(x / 100f, y / 100f).endVertex();
- }
-
- private static WorldRenderer createSurfaceWorldRenderer() {
- WorldRenderer worldRenderer = new WorldRenderer(20 * 100 * 100);
- worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
-
- for (int x = 0; x < 100; x++) {
- for (int y = 0; y < 100; y++) {
- drawPoint(worldRenderer, x, y);
- drawPoint(worldRenderer, x, y + 1);
- drawPoint(worldRenderer, x + 1, y + 1);
- drawPoint(worldRenderer, x + 1, y);
- }
- }
-
- return worldRenderer;
- }
-
- private static long overridingRenderMillis = -1;
-
- public static void onRespawnPacket(S07PacketRespawn packet) {
- if (true) return;
- if (packet.getDimensionID() != Minecraft.getMinecraft().thePlayer.dimension) {
- overridingRenderMillis = System.currentTimeMillis();
- }
- }
-
- public static boolean shouldRenderLoadingScreen() {
- return false;
- }
-
- public static boolean shouldRenderWorldOverlay() {
- if (overridingRenderMillis > 0) {
- if (Minecraft.getMinecraft().theWorld != null && Minecraft.getMinecraft().thePlayer != null) {
- RenderGlobal renderGlobal = Minecraft.getMinecraft().renderGlobal;
- int loaded = 0;
- for (RenderGlobal.ContainerLocalRenderInformation info : renderGlobal.renderInfos) {
- CompiledChunk compiledchunk = info.renderChunk.compiledChunk;
-
- if (compiledchunk != CompiledChunk.DUMMY && !compiledchunk.isEmpty()) {
- if (++loaded >= 5) {
- overridingRenderMillis = -1;
- return false;
- }
- }
- }
- }
- if (System.currentTimeMillis() - overridingRenderMillis > 1000) {
- overridingRenderMillis = -1;
- return false;
- }
- return true;
- }
- return false;
- }
-
- public static void onUpdateCameraAndRender(float partialTicks, long nanoTime) {
- if (overridingRenderMillis > 0) {
- if (Minecraft.getMinecraft().theWorld != null && Minecraft.getMinecraft().thePlayer != null) {
- Minecraft.getMinecraft().thePlayer.timeInPortal = 0.3f;
- Minecraft.getMinecraft().thePlayer.prevTimeInPortal = 0.3f;
- }
-
- GlStateManager.rotate(90, 0, 1, 0);
- renderWorld();
-
- Minecraft.getMinecraft().ingameGUI.renderGameOverlay(partialTicks);
- }
- }
-
- @SubscribeEvent
- public void onRenderEntityYeeter(RenderLivingEvent.Pre<EntityLivingBase> event) {
- /*if(!Keyboard.isKeyDown(Keyboard.KEY_G)) return;
- event.setCanceled(true);
- if(event.entity instanceof EntityPlayer) {
- EntityPlayer player = (EntityPlayer) event.entity;
- if(player.getUniqueID().version() == 4) {
- event.setCanceled(true);
- }
- }*/
- }
-
- private static void renderWorld() {
- for (int i = 5; i >= 0; i--) {
- GlStateManager.pushMatrix();
-
- GlStateManager.disableDepth();
- GlStateManager.disableLighting();
-
- GlStateManager.rotate(180, 0, 0, 1);
- GlStateManager.rotate(-90, 0, 1, 0);
-
- if (i != 0) GlStateManager.translate(0, -3.49, 0);
-
- switch (i) {
- case 1:
- GlStateManager.rotate(90.0F, 0.0F, 1.0F, 0.0F);
- break;
- case 2:
- GlStateManager.rotate(180.0F, 0.0F, 1.0F, 0.0F);
- break;
- case 3:
- GlStateManager.rotate(-90.0F, 0.0F, 1.0F, 0.0F);
- break;
- case 5:
- GlStateManager.rotate(90.0F, 1.0F, 0.0F, 0.0F);
- break;
- case 0:
- GlStateManager.rotate(-90.0F, 1.0F, 0.0F, 0.0F);
- break;
- }
-
- Minecraft.getMinecraft().getTextureManager().bindTexture(RENDERS[i]);
- GlStateManager.color(1, 1, 1, 1);
- if (i != 0) GlStateManager.translate(0, 0, 3.49);
-
- if (i != 0) {
- GlStateManager.translate(-3.5f, -3.5f, 0);
- WorldRenderer worldRenderer = getSurfaceWorldRenderer();
- VertexFormat vertexformat = worldRenderer.getVertexFormat();
- int stride = vertexformat.getNextOffset();
- ByteBuffer bytebuffer = worldRenderer.getByteBuffer();
- List<VertexFormatElement> list = vertexformat.getElements();
-
- for (int index = 0; index < list.size(); index++) {
- VertexFormatElement vertexformatelement = list.get(index);
- vertexformatelement.getUsage().preDraw(vertexformat, index, stride, bytebuffer);
- }
-
- GL11.glDrawArrays(worldRenderer.getDrawMode(), 0, worldRenderer.getVertexCount());
-
- for (int index = 0; index < list.size(); index++) {
- VertexFormatElement vertexformatelement = list.get(index);
- vertexformatelement.getUsage().postDraw(vertexformat, index, stride, bytebuffer);
- }
- } else {
- Utils.drawTexturedRect(-3.5f, -3.5f, 7, 7, i == 0 ? GL11.GL_NEAREST : GL11.GL_LINEAR);
- }
-
- GlStateManager.enableDepth();
-
- GlStateManager.popMatrix();
- }
- }
-
- @SubscribeEvent
- public void onRenderLast(RenderWorldLastEvent event) {
- if (true) return;
- if (!Minecraft.getMinecraft().getFramebuffer().isStencilEnabled())
- Minecraft.getMinecraft().getFramebuffer().enableStencil();
-
- GL11.glEnable(GL11.GL_STENCIL_TEST);
- GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
- GL11.glStencilOp(GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_REPLACE);
- GL11.glStencilMask(0xFF);
- GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
- GlStateManager.enableDepth();
- GlStateManager.enableCull();
- GlStateManager.cullFace(GL11.GL_BACK);
-
- GL11.glColorMask(false, false, false, false);
-
- Entity viewer = Minecraft.getMinecraft().getRenderViewEntity();
- double viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * event.partialTicks;
- double viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * event.partialTicks;
- double viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * event.partialTicks;
- GlStateManager.pushMatrix();
-
- GlStateManager.translate(-viewerX + 12 + 5 / 16f, -viewerY + 100, -viewerZ + 39);
- GlStateManager.rotate(90, 0, 1, 0);
- Gui.drawRect(0, 5, 3, 0, 0xffffffff);
- GlStateManager.rotate(180, 0, 1, 0);
- GlStateManager.translate(-3, 0, -6 / 16f);
- Gui.drawRect(0, 5, 3, 0, 0xffffffff);
-
- GlStateManager.popMatrix();
-
- GL11.glColorMask(true, true, true, true);
-
- // Only pass stencil test if equal to 1
- GL11.glStencilMask(0x00);
- GL11.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
-
- GlStateManager.translate(-viewerX + 12, -viewerY + 100, -viewerZ + 37.5f);
-
- renderWorld();
-
- GL11.glDisable(GL11.GL_STENCIL_TEST);
- GlStateManager.enableCull();
- }
-}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FishingHelper.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FishingHelper.java
index 34e849d0..13a078a2 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FishingHelper.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/FishingHelper.java
@@ -1,8 +1,29 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.ChromaColour;
import io.github.moulberry.notenoughupdates.util.SpecialColour;
import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.ISound;
import net.minecraft.client.audio.PositionedSound;
@@ -11,8 +32,10 @@ import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.EntityFishHook;
+import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
+import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
@@ -21,7 +44,13 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.lwjgl.opengl.GL11;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class FishingHelper {
private static final FishingHelper INSTANCE = new FishingHelper();
@@ -63,46 +92,92 @@ public class FishingHelper {
private int pingDelayTicks = 0;
private final List<Integer> pingDelayList = new ArrayList<>();
private int buildupSoundDelay = 0;
+ private boolean playedSound = false;
private static final ResourceLocation FISHING_WARNING_EXCLAM = new ResourceLocation(
"notenoughupdates:fishing_warning_exclam.png");
+ public boolean renderWarning() {
+ if (warningState == PlayerWarningState.NOTHING) return false;
+
+ if (!NotEnoughUpdates.INSTANCE.config.fishing.incomingFishWarning &&
+ warningState == PlayerWarningState.FISH_INCOMING)
+ return false;
+ if (!NotEnoughUpdates.INSTANCE.config.fishing.incomingFishWarningR &&
+ warningState == PlayerWarningState.FISH_HOOKED)
+ return false;
+
+ float offset = warningState == PlayerWarningState.FISH_HOOKED ? 0.5f : 0f;
+
+ float centerOffset = 0.5f / 8f;
+ Minecraft.getMinecraft().getTextureManager().bindTexture(FISHING_WARNING_EXCLAM);
+ Utils.drawTexturedRect(
+ centerOffset - 4f / 8f,
+ -20 / 8f,
+ 1f,
+ 2f,
+ 0 + offset,
+ 0.5f + offset,
+ 0,
+ 1,
+ GL11.GL_NEAREST
+ );
+ return true;
+ }
+
public void onRenderBobber(EntityFishHook hook) {
- if (Minecraft.getMinecraft().thePlayer.fishEntity == hook && warningState != PlayerWarningState.NOTHING) {
-
- if (!NotEnoughUpdates.INSTANCE.config.fishing.incomingFishWarning &&
- warningState == PlayerWarningState.FISH_INCOMING)
- return;
- if (!NotEnoughUpdates.INSTANCE.config.fishing.incomingFishWarningR &&
- warningState == PlayerWarningState.FISH_HOOKED)
- return;
-
- GlStateManager.disableCull();
- GlStateManager.disableLighting();
- GL11.glDepthFunc(GL11.GL_ALWAYS);
- GlStateManager.scale(1, -1, 1);
-
- float offset = warningState == PlayerWarningState.FISH_HOOKED ? 0.5f : 0f;
-
- float centerOffset = 0.5f / 8f;
- Minecraft.getMinecraft().getTextureManager().bindTexture(FISHING_WARNING_EXCLAM);
- Utils.drawTexturedRect(
- centerOffset - 4f / 8f,
- -20 / 8f,
- 1f,
- 2f,
- 0 + offset,
- 0.5f + offset,
- 0,
- 1,
- GL11.GL_NEAREST
- );
-
- GlStateManager.scale(1, -1, 1);
- GL11.glDepthFunc(GL11.GL_LEQUAL);
- GlStateManager.enableLighting();
- GlStateManager.enableCull();
+ if (Minecraft.getMinecraft().thePlayer.fishEntity != hook) return;
+ GlStateManager.pushMatrix();
+ GlStateManager.disableCull();
+ GlStateManager.disableLighting();
+ GL11.glDepthFunc(GL11.GL_ALWAYS);
+ GlStateManager.scale(1, -1, 1);
+ boolean isExclamationMarkPresent = renderWarning();
+ GlStateManager.scale(0.1, 0.1, 1);
+ drawFishingTimer(hook, isExclamationMarkPresent);
+ GL11.glDepthFunc(GL11.GL_LEQUAL);
+ GlStateManager.enableLighting();
+ GlStateManager.enableCull();
+ GlStateManager.popMatrix();
+ }
+
+ private void drawFishingTimer(EntityFishHook hook, boolean isExclamationMarkPresent) {
+ if (!NotEnoughUpdates.INSTANCE.config.fishing.fishingTimer) return;
+ float baseHeight = isExclamationMarkPresent ? 20 : 0;
+ int ticksExisted = hook.ticksExisted;
+ float seconds = ticksExisted / 20F;
+ int color;
+ if (seconds >= 30) {
+ color = ChromaColour.specialToChromaRGB(NotEnoughUpdates.INSTANCE.config.fishing.fishingTimerColor30SecPlus);
+ if (NotEnoughUpdates.INSTANCE.config.fishing.fishingSound30Sec && !playedSound) {
+ ISound sound = new PositionedSound(new ResourceLocation("random.orb")) {{
+ volume = 50;
+ pitch = 2f;
+ repeat = false;
+ repeatDelay = 0;
+ attenuationType = ISound.AttenuationType.NONE;
+ }};
+
+ float oldLevel = Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS);
+ Minecraft.getMinecraft().gameSettings.setSoundLevel(SoundCategory.RECORDS, 1);
+ Minecraft.getMinecraft().getSoundHandler().playSound(sound);
+ Minecraft.getMinecraft().gameSettings.setSoundLevel(SoundCategory.RECORDS, oldLevel);
+ playedSound = true;
+ }
+ } else {
+ color = ChromaColour.specialToChromaRGB(NotEnoughUpdates.INSTANCE.config.fishing.fishingTimerColor);
+ playedSound = false;
}
+
+ Utils.drawStringCentered(
+ String.format("%.02fs", seconds),
+ Minecraft.getMinecraft().fontRendererObj,
+ 0,
+ -baseHeight - Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT,
+ false,
+ color
+ );
+
}
public void addEntity(int entityId, Entity entity) {
@@ -257,7 +332,17 @@ public class FishingHelper {
double angle2
) {
double dY = particleY - hook.posY;
- if (Math.abs(dY) > 0.5f) {
+ double tolerance = 0.5F;
+ if (hook.worldObj != null) {
+ for (int i = -2; i < 2; i++) {
+ IBlockState state = hook.worldObj.getBlockState(new BlockPos(particleX, particleY + i, particleZ));
+ if (state != null && (state.getBlock() == Blocks.flowing_lava
+ || state.getBlock() == Blocks.flowing_water
+ || state.getBlock() == Blocks.lava))
+ tolerance = 2.0F;
+ }
+ }
+ if (Math.abs(dY) > tolerance) {
return HookPossibleRet.NOT_POSSIBLE;
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java
index 96522d34..f2b13abc 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCustomizeManager.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCustomizeManager.java
index 655364ef..e1b9d567 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCustomizeManager.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCustomizeManager.java
@@ -1,3 +1,22 @@
+/*
+ * 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.Gson;
@@ -18,7 +37,13 @@ import org.lwjgl.opengl.GL14;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.function.Consumer;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemRarityHalo.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemRarityHalo.java
index 88c7261c..79dd18f1 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemRarityHalo.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemRarityHalo.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
@@ -5,7 +24,11 @@ import io.github.moulberry.notenoughupdates.util.NEUResourceManager;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
-import net.minecraft.client.renderer.*;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.OpenGlHelper;
+import net.minecraft.client.renderer.RenderHelper;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java
index b56737e7..12e0301b 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NPCRetexturing.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NPCRetexturing.java
index 05d75c26..ad0dd66d 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NPCRetexturing.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NPCRetexturing.java
@@ -1,3 +1,22 @@
+/*
+ * 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.Gson;
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
+ );
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NewApiKeyHelper.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NewApiKeyHelper.java
new file mode 100644
index 00000000..6036d93b
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NewApiKeyHelper.java
@@ -0,0 +1,43 @@
+/*
+ * 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 net.minecraft.client.Minecraft;
+import net.minecraft.network.play.client.C01PacketChatMessage;
+import net.minecraft.util.ChatComponentText;
+
+public class NewApiKeyHelper {
+ private static final NewApiKeyHelper INSTANCE = new NewApiKeyHelper();
+
+ public static NewApiKeyHelper getInstance() {
+ return INSTANCE;
+ }
+
+ public void hookPacketChatMessage(C01PacketChatMessage packet) {
+ String message = packet.getMessage().toLowerCase();
+ if (message.equals("/api new")) return;
+
+ if (message.replace(" ", "").startsWith("/apinew")) {
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(
+ "§e[NotEnoughUpdates] §7You just executed §c" + packet.getMessage() +
+ "§7. Did you mean to execute §e/api new§7?"));
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java
index 927111b3..6302343e 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.util.ReverseWorldRenderer;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java
index cb2b7031..776e3647 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java
@@ -1,12 +1,37 @@
+/*
+ * 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.common.collect.Lists;
-import com.google.gson.*;
-import io.github.moulberry.notenoughupdates.NEUEventListener;
-import io.github.moulberry.notenoughupdates.NEUOverlay;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.core.config.Position;
+import io.github.moulberry.notenoughupdates.core.util.StringUtils;
import io.github.moulberry.notenoughupdates.core.util.lerp.LerpUtils;
+import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
+import io.github.moulberry.notenoughupdates.listener.RenderListener;
import io.github.moulberry.notenoughupdates.options.NEUConfig;
import io.github.moulberry.notenoughupdates.overlays.TextOverlay;
import io.github.moulberry.notenoughupdates.overlays.TextOverlayStyle;
@@ -23,10 +48,10 @@ import net.minecraft.init.Items;
import net.minecraft.inventory.ContainerChest;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.client.event.ClientChatReceivedEvent;
-import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@@ -34,23 +59,34 @@ import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.apache.commons.lang3.text.WordUtils;
import org.lwjgl.util.vector.Vector2f;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
public class PetInfoOverlay extends TextOverlay {
private static final Pattern XP_BOOST_PATTERN = Pattern.compile(
"PET_ITEM_(COMBAT|FISHING|MINING|FORAGING|ALL|FARMING)_(SKILL|SKILLS)_BOOST_(COMMON|UNCOMMON|RARE|EPIC)");
private static final Pattern PET_CONTAINER_PAGE = Pattern.compile("\\((\\d)/(\\d)\\) Pets");
- private static final Pattern PET_NAME_PATTERN = Pattern.compile("\u00a77\\[Lvl \\d+] \u00a7(.+)");
- private static final Pattern XP_LINE_PATTERN = Pattern.compile(
- "-------------------- (\\d+(?:,\\d+)*(?:\\.\\d+)?)/(\\d+(?:\\.\\d+)?[B|M|k]?)");
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
@@ -70,10 +106,10 @@ public class PetInfoOverlay extends TextOverlay {
LEGENDARY(20, 4, 5, EnumChatFormatting.GOLD),
MYTHIC(20, 5, 5, EnumChatFormatting.LIGHT_PURPLE);
- public int petOffset;
- public EnumChatFormatting chatFormatting;
- public int petId;
- public int beastcreatMultiplyer;
+ public final int petOffset;
+ public final EnumChatFormatting chatFormatting;
+ public final int petId;
+ public final int beastcreatMultiplyer;
Rarity(int petOffset, int petId, int beastcreatMultiplyer, EnumChatFormatting chatFormatting) {
this.chatFormatting = chatFormatting;
@@ -97,9 +133,15 @@ public class PetInfoOverlay extends TextOverlay {
public GuiProfileViewer.PetLevel petLevel;
public String petXpType;
public String petItem;
- }
+ public String skin;
+ public int candyUsed;
- private static long lastXpGain = 0;
+ public String getPetId(boolean withoutBoost) {
+ boolean shouldDecreaseRarity = withoutBoost && "PET_ITEM_TIER_BOOST".equals(petItem);
+ return petType + ";" + (shouldDecreaseRarity ? rarity.petId - 1 : rarity.petId);
+ }
+
+ }
public static class PetConfig {
public HashMap<Integer, Pet> petMap = new HashMap<>();
@@ -129,7 +171,7 @@ public class PetInfoOverlay extends TextOverlay {
public static void loadConfig(File file) {
try (
BufferedReader reader = new BufferedReader(new InputStreamReader(
- new FileInputStream(file),
+ Files.newInputStream(file.toPath()),
StandardCharsets.UTF_8
))
) {
@@ -146,7 +188,7 @@ public class PetInfoOverlay extends TextOverlay {
file.createNewFile();
try (
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
- new FileOutputStream(file),
+ Files.newOutputStream(file.toPath()),
StandardCharsets.UTF_8
))
) {
@@ -208,15 +250,11 @@ public class PetInfoOverlay extends TextOverlay {
}
private static Pet getClosestPet(String petType, int petId, String petItem, float petLevel) {
- Set<Pet> pets = new HashSet<Pet>() {{
- for (Pet pet : config.petMap.values()) {
- if (pet.petType.equals(petType) && pet.rarity.petId == petId) {
- add(pet);
- }
- }
- }};
+ Set<Pet> pets = config.petMap.values().stream().filter(pet -> pet.petType.equals(petType) &&
+ pet.rarity.petId == petId).collect(
+ Collectors.toSet());
- if (pets == null || pets.isEmpty()) {
+ if (pets.isEmpty()) {
return null;
}
@@ -224,15 +262,10 @@ public class PetInfoOverlay extends TextOverlay {
return pets.iterator().next();
}
- String searchItem = petItem;
-
- Set<Pet> itemMatches = new HashSet<>();
- for (Pet pet : pets) {
- if ((searchItem == null && pet.petItem == null) ||
- (searchItem != null && searchItem.equals(pet.petItem))) {
- itemMatches.add(pet);
- }
- }
+ Set<Pet> itemMatches = pets
+ .stream()
+ .filter(pet -> Objects.equals(petItem, pet.petItem))
+ .collect(Collectors.toSet());
if (itemMatches.size() == 1) {
return itemMatches.iterator().next();
@@ -253,15 +286,11 @@ public class PetInfoOverlay extends TextOverlay {
}
}
- if (closestPet != null) {
- return closestPet;
- } else {
- return pets.iterator().next();
- }
+ return closestPet;
}
private static void getAndSetPet(ProfileViewer.Profile profile) {
- JsonObject skillInfo = profile.getSkillInfo(profile.getLatestProfile());
+ Map<String, ProfileViewer.Level> skyblockInfo = profile.getSkyblockInfo(profile.getLatestProfile());
JsonObject invInfo = profile.getInventoryInfo(profile.getLatestProfile());
JsonObject profileInfo = profile.getProfileInformation(profile.getLatestProfile());
if (invInfo != null && profileInfo != null) {
@@ -300,7 +329,8 @@ public class PetInfoOverlay extends TextOverlay {
}
}
}
- if (skillInfo != null) config.tamingLevel = skillInfo.get("level_skill_taming").getAsInt();
+ if (skyblockInfo != null) config.tamingLevel = (int) skyblockInfo.get("taming").level;
+
//JsonObject petObject = profile.getPetsInfo(profile.getLatestProfile());
/*JsonObject petsJson = Constants.PETS;
if(petsJson != null) {
@@ -421,7 +451,7 @@ public class PetInfoOverlay extends TextOverlay {
String etaStr = null;
String etaMaxStr = null;
- if (currentPet.petLevel.level < 100) {
+ if (currentPet.petLevel.level < currentPet.petLevel.maxLevel) {
float remaining = currentPet.petLevel.currentLevelRequirement - currentPet.petLevel.levelXp;
if (remaining > 0) {
if (xpGain < 1000) {
@@ -433,14 +463,15 @@ public class PetInfoOverlay extends TextOverlay {
}
}
- if (currentPet.petLevel.level < 99 || !NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText.contains(6)) {
+ if (currentPet.petLevel.level < (currentPet.petLevel.maxLevel - 1) ||
+ !NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText.contains(6)) {
float remainingMax = currentPet.petLevel.maxXP - currentPet.petLevel.totalXp;
if (remaining > 0) {
if (xpGain < 1000) {
- etaMaxStr = EnumChatFormatting.AQUA + "Until L100: " +
+ etaMaxStr = EnumChatFormatting.AQUA + "Until L" + (int) currentPet.petLevel.maxLevel + ": " +
EnumChatFormatting.YELLOW + "N/A";
} else {
- etaMaxStr = EnumChatFormatting.AQUA + "Until L100: " +
+ etaMaxStr = EnumChatFormatting.AQUA + "Until L" + (int) currentPet.petLevel.maxLevel + ": " +
EnumChatFormatting.YELLOW + Utils.prettyTime((long) (remainingMax) * 1000 * 60 * 60 / (long) xpGain);
}
}
@@ -525,86 +556,10 @@ public class PetInfoOverlay extends TextOverlay {
}
}
- private static GuiProfileViewer.PetLevel getMaxLevel(JsonArray levels, int offset) {
- float xpTotal = 0;
- float level = 1;
- float currentLevelRequirement = 0;
-
- for (int i = offset; i < offset + 99; i++) {
- currentLevelRequirement = levels.get(i).getAsFloat();
- xpTotal += currentLevelRequirement;
- level += 1;
- }
-
- if (level <= 0) {
- level = 1;
- } else if (level > 100) {
- level = 100;
- }
- GuiProfileViewer.PetLevel levelObj = new GuiProfileViewer.PetLevel();
- levelObj.level = level;
- levelObj.currentLevelRequirement = currentLevelRequirement;
- levelObj.maxXP = xpTotal;
- levelObj.levelPercentage = 1;
- levelObj.levelXp = currentLevelRequirement - 5;
- levelObj.totalXp = xpTotal - 5;
- return levelObj;
- }
-
- private static GuiProfileViewer.PetLevel getLevel(
- JsonArray levels,
- int offset,
- float xpThisLevel,
- int xpMaxThisLevel
- ) {
- float xpTotal = 0;
- float level = 1;
- float currentLevelRequirement = 0;
- float exp = xpThisLevel;
-
- boolean addLevel = true;
-
- for (int i = offset; i < offset + 99; i++) {
- if (addLevel) {
- currentLevelRequirement = levels.get(i).getAsFloat();
- xpTotal += currentLevelRequirement;
-
- if (currentLevelRequirement >= xpMaxThisLevel) {
- addLevel = false;
- } else {
- exp += currentLevelRequirement;
- level += 1;
- }
- } else {
- xpTotal += levels.get(i).getAsFloat();
- }
- }
-
- level += xpThisLevel / currentLevelRequirement;
- if (level <= 0) {
- level = 1;
- } else if (level > 100) {
- level = 100;
- }
- GuiProfileViewer.PetLevel levelObj = new GuiProfileViewer.PetLevel();
- levelObj.level = level;
- levelObj.currentLevelRequirement = currentLevelRequirement;
- levelObj.maxXP = xpTotal;
- levelObj.levelPercentage = xpThisLevel / currentLevelRequirement;
- levelObj.levelXp = xpThisLevel;
- levelObj.totalXp = exp;
- return levelObj;
- }
-
- public static Pet getPetFromStack(String name, String[] lore) {
+ public static Pet getPetFromStack(NBTTagCompound tag) {
if (Constants.PETS == null || Constants.PETS.get("pet_levels") == null ||
Constants.PETS.get("pet_levels") instanceof JsonNull) {
- Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(
- "\u00a7cInvalid PET constants. Please run " + EnumChatFormatting.BOLD + "/neuresetrepo" +
- EnumChatFormatting.RESET + EnumChatFormatting.RED + " and " + EnumChatFormatting.BOLD + "restart your game" +
- EnumChatFormatting.RESET + EnumChatFormatting.RED + " in order to fix. " + EnumChatFormatting.DARK_RED +
- EnumChatFormatting.BOLD + "If that doesn't fix it" + EnumChatFormatting.RESET + EnumChatFormatting.RED +
- ", please join discord.gg/moulberry and post in #neu-support"));
+ Utils.showOutdatedRepoNotification();
return null;
}
@@ -612,132 +567,50 @@ public class PetInfoOverlay extends TextOverlay {
Rarity rarity = null;
String heldItem = null;
GuiProfileViewer.PetLevel level = null;
-
- Matcher petNameMatcher = PET_NAME_PATTERN.matcher(name);
- if (petNameMatcher.matches()) {
- String petStringMatch = petNameMatcher.group(1);
-
- char colChar = petStringMatch.charAt(0);
- EnumChatFormatting col = EnumChatFormatting.RESET;
- for (EnumChatFormatting formatting : EnumChatFormatting.values()) {
- if (formatting.toString().equals("\u00a7" + colChar)) {
- col = formatting;
- break;
- }
- }
-
- rarity = Rarity.COMMON;
- if (col != EnumChatFormatting.RESET) {
- rarity = Rarity.getRarityFromColor(col);
- }
-
- petType = Utils.cleanColour(petStringMatch.substring(1))
- .replaceAll("[^\\w ]", "").trim()
- .replace(" ", "_").toUpperCase();
- }
- if (petType == null || rarity == null) {
- return null;
- }
-
- for (String line : lore) {
- Matcher xpLineMatcher = XP_LINE_PATTERN.matcher(Utils.cleanColour(line));
- if (line.startsWith("\u00a76Held Item: ")) {
- String after = line.substring("\u00a76Held Item: ".length());
-
- if (itemMap == null) {
- itemMap = new HashMap<>();
-
- for (Map.Entry<String, JsonObject> entry : NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .entrySet()) {
- boolean petItem = false;
-
- if (entry.getKey().startsWith("PET_ITEM_")) {
- petItem = true;
- } else {
- ItemStack stack = NotEnoughUpdates.INSTANCE.manager.jsonToStack(entry.getValue());
- if (stack.hasTagCompound()) {
- String[] itemLore = NotEnoughUpdates.INSTANCE.manager.getLoreFromNBT(stack.getTagCompound());
-
- for (String itemLoreLine : itemLore) {
- if (itemLoreLine.contains("PET ITEM")) {
- petItem = true;
- break;
- }
- }
- }
- }
-
- if (petItem) {
- ItemStack stack = NotEnoughUpdates.INSTANCE.manager.jsonToStack(entry.getValue());
- itemMap.put(stack.getDisplayName().replace("\u00a7f\u00a7f", ""), entry.getKey());
- }
- }
+ String skin = null;
+
+ if (tag != null && tag.hasKey("ExtraAttributes")) {
+ NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
+ if (ea.hasKey("petInfo")) {
+ JsonObject petInfo = new JsonParser().parse(ea.getString("petInfo")).getAsJsonObject();
+ petType = petInfo.get("type").getAsString();
+ rarity = Rarity.valueOf(petInfo.get("tier").getAsString());
+ level = GuiProfileViewer.getPetLevel(
+ petType,
+ rarity.name(),
+ Utils.getElementAsFloat(petInfo.get("exp"), 0) // Should only default if from item list and repo missing exp:0
+ );
+ if (petInfo.has("heldItem")) {
+ heldItem = petInfo.get("heldItem").getAsString();
}
-
- if (itemMap.containsKey(after)) {
- heldItem = itemMap.get(after);
- }
- } else if (xpLineMatcher.matches()) {
- String xpThisLevelS = xpLineMatcher.group(1);
- String xpMaxThisLevelS = xpLineMatcher.group(2).toLowerCase();
-
- try {
- float xpThisLevel = Float.parseFloat(xpThisLevelS.replace(",", ""));
-
- int mutiplier = 1;
- char end = xpMaxThisLevelS.charAt(xpMaxThisLevelS.length() - 1);
- if (end < '0' || end > '9') {
- xpMaxThisLevelS = xpMaxThisLevelS.substring(0, xpMaxThisLevelS.length() - 1);
-
- switch (end) {
- case 'k':
- mutiplier = 1000;
- break;
- case 'm':
- mutiplier = 1000000;
- break;
- case 'b':
- mutiplier = 1000000000;
- break;
- }
- }
- int xpMaxThisLevel = (int) (Float.parseFloat(xpMaxThisLevelS) * mutiplier);
-
- level = getLevel(
- Constants.PETS.get("pet_levels").getAsJsonArray(),
- rarity.petOffset,
- xpThisLevel,
- xpMaxThisLevel
- );
- } catch (NumberFormatException ignored) {
+ if (petInfo.has("skin")) {
+ skin = "PET_SKIN_" + petInfo.get("skin").getAsString();
}
- } else if (line.equals("\u00a7b\u00a7lMAX LEVEL")) {
- level = getMaxLevel(Constants.PETS.get("pet_levels").getAsJsonArray(), rarity.petOffset);
}
}
- if (level != null) {
- Pet pet = new Pet();
- pet.petItem = heldItem;
- pet.petLevel = level;
- pet.rarity = rarity;
- pet.petType = petType;
- JsonObject petTypes = Constants.PETS.get("pet_types").getAsJsonObject();
- pet.petXpType =
- petTypes.has(pet.petType) ? petTypes.get(pet.petType.toUpperCase()).getAsString().toLowerCase() : "unknown";
-
- return pet;
+ if (petType == null) {
+ return null;
}
- return null;
+ Pet pet = new Pet();
+ pet.petItem = heldItem;
+ pet.petLevel = level;
+ pet.rarity = rarity;
+ pet.petType = petType;
+ JsonObject petTypes = Constants.PETS.get("pet_types").getAsJsonObject();
+ pet.petXpType =
+ petTypes.has(pet.petType) ? petTypes.get(pet.petType.toUpperCase()).getAsString().toLowerCase() : "unknown";
+ pet.skin = skin;
+
+ return pet;
}
private static final HashMap<Integer, Integer> removeMap = new HashMap<>();
@SubscribeEvent
public void onTick(TickEvent.ClientTickEvent event) {
- if (Minecraft.getMinecraft().currentScreen instanceof GuiChest && NEUEventListener.inventoryLoaded) {
+ if (Minecraft.getMinecraft().currentScreen instanceof GuiChest && RenderListener.inventoryLoaded) {
GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
ContainerChest container = (ContainerChest) chest.inventorySlots;
IInventory lower = container.getLowerChestInventory();
@@ -761,7 +634,6 @@ public class PetInfoOverlay extends TextOverlay {
}
}
}
-
if (isPets) {
boolean hasItem = false;
for (int i = 0; i < lower.getSizeInventory(); i++) {
@@ -805,14 +677,14 @@ public class PetInfoOverlay extends TextOverlay {
}
} else {
String[] lore = NotEnoughUpdates.INSTANCE.manager.getLoreFromNBT(stack.getTagCompound());
- Pet pet = getPetFromStack(stack.getDisplayName(), lore);
+ Pet pet = getPetFromStack(stack.getTagCompound());
if (pet != null) {
config.petMap.put(petIndex, pet);
if (currentTime - lastPetSelect > 500) {
boolean foundDespawn = false;
for (String line : lore) {
- if (line.equals("\u00a77\u00a7cClick to despawn.")) {
+ if (line.startsWith("\u00a77\u00a7cClick to despawn")) {
config.selectedPet = petIndex;
foundDespawn = true;
break;
@@ -829,6 +701,40 @@ public class PetInfoOverlay extends TextOverlay {
}
}
removeMap.keySet().retainAll(removeSet);
+ } else if (containerName.equals("Your Equipment")) {
+ ItemStack petStack = lower.getStackInSlot(47);
+ if (petStack != null && petStack.getItem() == Items.skull) {
+ NBTTagCompound tag = petStack.getTagCompound();
+
+ if (tag.hasKey("ExtraAttributes", 10)) {
+ NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
+ if (ea.hasKey("petInfo")) {
+ JsonParser jsonParser = new JsonParser();
+
+ JsonObject petInfoObject = jsonParser.parse(ea.getString("petInfo")).getAsJsonObject();
+
+ JsonObject jsonStack = NotEnoughUpdates.INSTANCE.manager.getJsonForItem(petStack);
+ if (jsonStack == null || !jsonStack.has("lore") || !petInfoObject.has("exp")) {
+ return;
+ }
+
+ int rarity = Utils.getRarityFromLore(jsonStack.get("lore").getAsJsonArray());
+ String rarityString = Utils.getRarityFromInt(rarity);
+
+ String name = StringUtils.cleanColour(petStack.getDisplayName());
+ name = name.substring(name.indexOf(']') + 1).trim().replace(' ', '_').toUpperCase();
+
+ float petXp = petInfoObject.get("exp").getAsFloat();
+
+ double petLevel = GuiProfileViewer.getPetLevel(name, rarityString, petXp).level;
+ int index = getClosestPetIndex(name, rarity, "", (float) petLevel);
+ if (index != config.selectedPet) {
+ clearPet();
+ setCurrentPet(index);
+ }
+ }
+ }
+ }
}
}
}
@@ -885,11 +791,14 @@ public class PetInfoOverlay extends TextOverlay {
if (!NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayIcon) return;
int mythicRarity = currentPet.rarity.petId;
- if (currentPet.rarity.petId == 5) {
- mythicRarity = 4;
- }
JsonObject petItem = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(
- currentPet.petType + ";" + mythicRarity);
+ currentPet.skin != null ? currentPet.skin : (currentPet.petType + ";" + mythicRarity));
+
+ if (petItem == null && currentPet.rarity.petId == 5) {
+ petItem = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(
+ currentPet.skin != null ? currentPet.skin : (currentPet.petType + ";" + 4));
+ }
+
if (petItem != null) {
Vector2f position = getPosition(overlayWidth, overlayHeight);
int x = (int) position.x;
@@ -907,7 +816,7 @@ public class PetInfoOverlay extends TextOverlay {
Pet currentPet2 = getCurrentPet2();
if (currentPet2 != null) {
JsonObject petItem2 = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(
- currentPet2.petType + ";" + currentPet2.rarity.petId);
+ currentPet2.skin != null ? currentPet2.skin : (currentPet2.petType + ";" + currentPet2.rarity.petId));
if (petItem2 != null) {
Vector2f position = getPosition(overlayWidth, overlayHeight);
int x = (int) position.x;
@@ -954,19 +863,19 @@ public class PetInfoOverlay extends TextOverlay {
"alchemy"
);
- public static void onStackClick(ItemStack stack, int windowId, int slotId, int mouseButtonClicked, int mode) {
- if (mode != 0) return;
- if (mouseButtonClicked != 0 && mouseButtonClicked != 1) return;
+ @SubscribeEvent
+ public void onStackClick(SlotClickEvent event) {
+ if (event.clickedButton != 0 && event.clickedButton != 1 && event.clickedButton != 2) return;
- int slotIdMod = (slotId - 10) % 9;
- if (slotId >= 10 && slotId <= 43 && slotIdMod >= 0 && slotIdMod <= 6 &&
+ int slotIdMod = (event.slotId - 10) % 9;
+ if (event.slotId >= 10 && event.slotId <= 43 && slotIdMod >= 0 && slotIdMod <= 6 &&
Minecraft.getMinecraft().currentScreen instanceof GuiChest) {
GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
ContainerChest container = (ContainerChest) chest.inventorySlots;
IInventory lower = container.getLowerChestInventory();
String containerName = lower.getDisplayName().getUnformattedText();
- if (lower.getSizeInventory() >= 54 && windowId == container.windowId) {
+ if (lower.getSizeInventory() >= 54 && event.guiContainer.inventorySlots.windowId == container.windowId) {
int page = 0;
boolean isPets = false;
@@ -988,7 +897,7 @@ public class PetInfoOverlay extends TextOverlay {
boolean isRemoving =
removingStack != null && removingStack.getItem() == Items.dye && removingStack.getItemDamage() == 10;
- int newSelected = (slotId - 10) - (slotId - 10) / 9 * 2 + page * 28;
+ int newSelected = (event.slotId - 10) - (event.slotId - 10) / 9 * 2 + page * 28;
lastPetSelect = System.currentTimeMillis();
@@ -1001,10 +910,11 @@ public class PetInfoOverlay extends TextOverlay {
} else {
setCurrentPet(newSelected);
- String[] lore = NotEnoughUpdates.INSTANCE.manager.getLoreFromNBT(stack.getTagCompound());
- Pet pet = getPetFromStack(stack.getDisplayName(), lore);
- if (pet != null) {
- config.petMap.put(config.selectedPet, pet);
+ if (event.slot.getStack() != null && event.slot.getStack().getTagCompound() != null) {
+ Pet pet = getPetFromStack(event.slot.getStack().getTagCompound());
+ if (pet != null) {
+ config.petMap.put(config.selectedPet, pet);
+ }
}
}
}
@@ -1013,7 +923,7 @@ public class PetInfoOverlay extends TextOverlay {
}
public static float getXpGain(Pet pet, float xp, String xpType) {
- if (pet.petLevel.level >= 100) return 0;
+ if (pet.petLevel.level >= pet.petLevel.maxLevel) return 0;
if (validXpTypes == null)
validXpTypes = Lists.newArrayList("mining", "foraging", "enchanting", "farming", "combat", "fishing", "alchemy");
@@ -1047,8 +957,6 @@ public class PetInfoOverlay extends TextOverlay {
public void updatePetLevels() {
HashMap<String, XPInformation.SkillInfo> skillInfoMap = XPInformation.getInstance().getSkillInfoMap();
- long currentTime = System.currentTimeMillis();
-
float totalGain = 0;
Pet currentPet = getCurrentPet();
@@ -1062,7 +970,6 @@ public class PetInfoOverlay extends TextOverlay {
if (skillXpLast <= 0) {
skillInfoMapLast.put(entry.getKey(), skillXp);
} else if (skillXp > skillXpLast) {
- lastXpGain = currentTime;
float deltaXp = skillXp - skillXpLast;
@@ -1106,7 +1013,7 @@ public class PetInfoOverlay extends TextOverlay {
JsonObject petsJson = Constants.PETS;
if (currentPet != null && petsJson != null) {
currentPet.petLevel = GuiProfileViewer.getPetLevel(
- currentPet.petItem,
+ currentPet.petType,
currentPet.rarity.name(),
currentPet.petLevel.totalXp
);
@@ -1136,48 +1043,6 @@ public class PetInfoOverlay extends TextOverlay {
}
private int lastLevelHovered = 0;
- private String lastItemHovered = null;
-
- private static HashMap<String, String> itemMap = null;
-
- @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true)
- public void onTooltip(ItemTooltipEvent event) {
- for (String line : event.toolTip) {
- if (line.startsWith("\u00a7o\u00a77[Lvl ")) {
- lastItemHovered = null;
-
- String after = line.substring("\u00a7o\u00a77[Lvl ".length());
- if (after.contains("]")) {
- String levelStr = after.split("]")[0];
-
- try {
- lastLevelHovered = Integer.parseInt(levelStr.trim());
- } catch (Exception ignored) {
- }
- }
- } else if (line.startsWith("\u00a75\u00a7o\u00a76Held Item: ")) {
- String after = line.substring("\u00a75\u00a7o\u00a76Held Item: ".length());
-
- if (itemMap == null) {
- itemMap = new HashMap<>();
-
- for (Map.Entry<String, JsonObject> entry : NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .entrySet()) {
- if (entry.getKey().equals("ALL_SKILLS_SUPER_BOOST") ||
- XP_BOOST_PATTERN.matcher(entry.getKey()).matches()) {
- ItemStack stack = NotEnoughUpdates.INSTANCE.manager.jsonToStack(entry.getValue());
- itemMap.put(stack.getDisplayName(), entry.getKey());
- }
- }
- }
-
- if (itemMap.containsKey(after)) {
- lastItemHovered = itemMap.get(after);
- }
- }
- }
- }
private static final Pattern AUTOPET_EQUIP = Pattern.compile(
"\u00a7cAutopet \u00a7eequipped your \u00a77\\[Lvl (\\d+)] \u00a7(.{2,})\u00a7e! \u00a7a\u00a7lVIEW RULE\u00a7r");
@@ -1185,18 +1050,12 @@ public class PetInfoOverlay extends TextOverlay {
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onChatReceived(ClientChatReceivedEvent event) {
NEUConfig config = NotEnoughUpdates.INSTANCE.config;
- if (NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() &&
- (config.petOverlay.enablePetInfo || config.itemOverlays.enableMonkeyCheck || config.petOverlay.petInvDisplay)) {
+ if (config.petOverlay.enablePetInfo || config.itemOverlays.enableMonkeyCheck || config.petOverlay.petInvDisplay) {
if (event.type == 0) {
String chatMessage = Utils.cleanColour(event.message.getUnformattedText());
Matcher autopetMatcher = AUTOPET_EQUIP.matcher(event.message.getFormattedText());
- if (event.message.getUnformattedText().startsWith("You summoned your") ||
- System.currentTimeMillis() - NEUOverlay.cachedPetTimer < 500) {
- NEUOverlay.cachedPetTimer = System.currentTimeMillis();
- NEUOverlay.shouldUseCachedPet = false;
- } else if (autopetMatcher.matches()) {
- NEUOverlay.shouldUseCachedPet = false;
+ if (autopetMatcher.matches()) {
try {
lastLevelHovered = Integer.parseInt(autopetMatcher.group(1));
} catch (NumberFormatException ignored) {
@@ -1223,9 +1082,13 @@ public class PetInfoOverlay extends TextOverlay {
setCurrentPet(getClosestPetIndex(pet, rarity.petId, "", lastLevelHovered));
if (PetInfoOverlay.config.selectedPet == -1) {
- Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(
- EnumChatFormatting.RED + "[NEU] Can't find pet \u00a7" + petStringMatch +
- EnumChatFormatting.RED + " try revisiting all pages of /pets."));
+ setCurrentPet(getClosestPetIndex(pet, rarity.petId - 1, "", lastLevelHovered));
+ if (!"PET_ITEM_TIER_BOOST".equals(getCurrentPet().petItem)) {
+ PetInfoOverlay.config.selectedPet = -1;
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.RED + "[NEU] Can't find pet \u00a7" + petStringMatch +
+ EnumChatFormatting.RED + " try revisiting all pages of /pets."));
+ }
}
} else if ((chatMessage.toLowerCase().startsWith("you despawned your")) || (chatMessage.toLowerCase().contains(
"switching to profile"))
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java
new file mode 100644
index 00000000..975bbe6e
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java
@@ -0,0 +1,173 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.util.StringUtils;
+import io.github.moulberry.notenoughupdates.options.NEUConfig;
+import io.github.moulberry.notenoughupdates.util.ItemUtils;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.inventory.Container;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.client.event.GuiOpenEvent;
+import net.minecraftforge.event.entity.player.ItemTooltipEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+import java.text.NumberFormat;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+public class PowerStoneStatsDisplay {
+ private static PowerStoneStatsDisplay instance = null;
+ private final NumberFormat format = NumberFormat.getInstance(Locale.US);
+ private boolean dirty = true;
+
+ public static PowerStoneStatsDisplay getInstance() {
+ if (instance == null) {
+ instance = new PowerStoneStatsDisplay();
+ }
+ return instance;
+ }
+
+ @SubscribeEvent
+ public void onTick(TickEvent event) {
+ if (!dirty) return;
+
+ if (!Utils.getOpenChestName().equals("SkyBlock Menu")) {
+ dirty = false;
+ return;
+ }
+
+ EntityPlayerSP p = Minecraft.getMinecraft().thePlayer;
+ Container openContainer = p.openContainer;
+ for (Slot slot : openContainer.inventorySlots) {
+ ItemStack stack = slot.getStack();
+ if (stack == null) continue;
+
+ String displayName = stack.getDisplayName();
+ if (!"§aAccessory Bag".equals(displayName)) continue;
+
+ for (String line : ItemUtils.getLore(stack)) {
+ if (line.startsWith("§7Magical Power: ")) {
+ String rawNumber = line.split("§6")[1].replace(",", "");
+ NEUConfig.HiddenProfileSpecific configProfileSpecific = NotEnoughUpdates.INSTANCE.config.getProfileSpecific();
+ if (configProfileSpecific == null) return;
+ configProfileSpecific.magicalPower = Integer.parseInt(rawNumber);
+ dirty = false;
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void onGuiOpen(GuiOpenEvent event) {
+ if (event.gui != null) {
+ dirty = true;
+ }
+ }
+
+ @SubscribeEvent
+ public void onItemTooltipLow(ItemTooltipEvent event) {
+ if (!NotEnoughUpdates.INSTANCE.config.tooltipTweaks.powerStoneStats) return;
+
+ ItemStack itemStack = event.itemStack;
+ if (itemStack == null) return;
+ List<String> lore = ItemUtils.getLore(itemStack);
+
+ boolean isPowerStone = false;
+ for (String line : lore) {
+ if (line.equals("§8Power Stone")) {
+ isPowerStone = true;
+ break;
+ }
+ }
+
+ if (!isPowerStone) return;
+
+ NEUConfig.HiddenProfileSpecific configProfileSpecific = NotEnoughUpdates.INSTANCE.config.getProfileSpecific();
+ if (configProfileSpecific == null) return;
+
+ int magicalPower = configProfileSpecific.magicalPower;
+ if (magicalPower < 1) return;
+
+ double scaledMagicalPower = scalePower(magicalPower);
+ double scaledCurrentPower = 0.0;
+
+ int index = 0;
+ boolean foundMagicalPower = false;
+ for (String line : new LinkedList<>(lore)) {
+ index++;
+ line = line.replace("§k", "");
+
+ if (line.startsWith("§7At ")) {
+
+ String rawNumber = StringUtils.substringBetween(StringUtils.cleanColour(line), "At ", " Magical");
+ if (rawNumber == null) return;
+
+ //This ignores old repo entries in the item browser from neu
+ if (rawNumber.equals("mmm")) return;
+
+ try {
+ scaledCurrentPower = scalePower(StringUtils.cleanAndParseInt(rawNumber));
+ } catch (NumberFormatException ignored) {
+ return;
+ }
+
+ event.toolTip.set(index, "§7At §6" + format.format((double) magicalPower) + " Magical Power§7:");
+ foundMagicalPower = true;
+ continue;
+ }
+
+ if (!foundMagicalPower) continue;
+
+ String cleanLine = StringUtils.cleanColour(line);
+ if (cleanLine.equals("")) {
+ break;
+ }
+
+ for (String operator : new String[]{"+", "-"}) {
+ if (!cleanLine.startsWith(operator)) continue;
+ String rawStat = StringUtils.cleanColour(StringUtils.substringBetween(line, operator, " "));
+
+ double currentStat;
+ try {
+ currentStat = 0.0 + StringUtils.cleanAndParseInt(rawStat.substring(0, rawStat.length() - 1));
+ } catch (NumberFormatException ignored) {
+ continue;
+ }
+ double realStat = (currentStat / scaledCurrentPower) * scaledMagicalPower;
+
+ String format = this.format.format((double) Math.round(realStat));
+ format += rawStat.substring(rawStat.length() - 1);
+
+ event.toolTip.set(index, line.replace(rawStat, format));
+ }
+ }
+ }
+
+ private double scalePower(int magicalPower) {
+ return Math.pow(29.97 * (Math.log(0.0019 * magicalPower + 1)), 1.2);
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SlotLocking.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SlotLocking.java
index 743625f5..8a487739 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SlotLocking.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SlotLocking.java
@@ -1,3 +1,22 @@
+/*
+ * 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.Gson;
@@ -5,6 +24,8 @@ import com.google.gson.GsonBuilder;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.core.config.KeybindHelper;
import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils;
+import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
+import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer;
import io.github.moulberry.notenoughupdates.util.SBInfo;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.ISound;
@@ -23,16 +44,20 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
-import org.apache.commons.lang3.tuple.Triple;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Vector2f;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
-import java.util.function.Consumer;
public class SlotLocking {
private static final SlotLocking INSTANCE = new SlotLocking();
@@ -200,7 +225,7 @@ public class SlotLocking {
int mouseX = Mouse.getX() * scaledWidth / Minecraft.getMinecraft().displayWidth;
int mouseY = scaledHeight - Mouse.getY() * scaledHeight / Minecraft.getMinecraft().displayHeight - 1;
- Slot slot = container.getSlotAtPosition(mouseX, mouseY);
+ Slot slot = ((AccessorGuiContainer) container).doGetSlotAtPosition(mouseX, mouseY);
if (slot != null && slot.getSlotIndex() != 8 && slot.inventory == Minecraft.getMinecraft().thePlayer.inventory) {
int slotNum = slot.getSlotIndex();
if (slotNum >= 0 && slotNum <= 39) {
@@ -280,7 +305,7 @@ public class SlotLocking {
int mouseX = Mouse.getX() * scaledWidth / Minecraft.getMinecraft().displayWidth;
int mouseY = scaledHeight - Mouse.getY() * scaledHeight / Minecraft.getMinecraft().displayHeight - 1;
- Slot slot = container.getSlotAtPosition(mouseX, mouseY);
+ Slot slot = ((AccessorGuiContainer) container).doGetSlotAtPosition(mouseX, mouseY);
if (slot != null && slot.getSlotIndex() != 8 && slot.inventory == Minecraft.getMinecraft().thePlayer.inventory) {
int slotNum = slot.getSlotIndex();
if (slotNum >= 0 && slotNum <= 39) {
@@ -378,10 +403,10 @@ public class SlotLocking {
if (!(Minecraft.getMinecraft().currentScreen instanceof GuiContainer)) {
return;
}
- GuiContainer container = (GuiContainer) Minecraft.getMinecraft().currentScreen;
+ AccessorGuiContainer container = (AccessorGuiContainer) Minecraft.getMinecraft().currentScreen;
- int x1 = container.guiLeft + pairingSlot.xDisplayPosition + 8;
- int y1 = container.guiTop + pairingSlot.yDisplayPosition + 8;
+ int x1 = container.getGuiLeft() + pairingSlot.xDisplayPosition + 8;
+ int y1 = container.getGuiTop() + pairingSlot.yDisplayPosition + 8;
int x2 = event.mouseX;
int y2 = event.mouseY;
@@ -429,22 +454,21 @@ public class SlotLocking {
}
}
- public void onWindowClick(
- Slot slotIn,
- int slotId,
- int clickedButton,
- int clickType,
- Consumer<Triple<Integer, Integer, Integer>> consumer
- ) {
- LockedSlot locked = getLockedSlot(slotIn);
+ @SubscribeEvent
+ public void onWindowClick(SlotClickEvent slotClickEvent) {
+ LockedSlot locked = getLockedSlot(slotClickEvent.slot);
if (locked == null) {
return;
- } else if (locked.locked || (clickType == 2 && SlotLocking.getInstance().isSlotIndexLocked(clickedButton))) {
- consumer.accept(null);
- } else if (NotEnoughUpdates.INSTANCE.config.slotLocking.enableSlotBinding && clickType == 1 &&
+ }
+ if (locked.locked ||
+ (slotClickEvent.clickType == 2 && SlotLocking.getInstance().isSlotIndexLocked(slotClickEvent.clickedButton))) {
+ slotClickEvent.setCanceled(true);
+ return;
+ }
+ if (NotEnoughUpdates.INSTANCE.config.slotLocking.enableSlotBinding
+ && slotClickEvent.clickType == 1 &&
locked.boundTo != -1) {
- GuiContainer container = (GuiContainer) Minecraft.getMinecraft().currentScreen;
- Slot boundSlot = container.inventorySlots.getSlotFromInventory(
+ Slot boundSlot = slotClickEvent.guiContainer.inventorySlots.getSlotFromInventory(
Minecraft.getMinecraft().thePlayer.inventory,
locked.boundTo
);
@@ -455,29 +479,41 @@ public class SlotLocking {
LockedSlot boundLocked = getLockedSlot(boundSlot);
- int id = slotIn.getSlotIndex();
- if (id >= 9 && locked.boundTo >= 0 && locked.boundTo < 8) {
- if (!boundLocked.locked) {
- consumer.accept(Triple.of(slotId, locked.boundTo, 2));
- if (boundLocked == DEFAULT_LOCKED_SLOT) {
- LockedSlot[] lockedSlots = getDataForProfile();
- lockedSlots[locked.boundTo] = new LockedSlot();
- lockedSlots[locked.boundTo].boundTo = id;
- } else {
- boundLocked.boundTo = id;
- }
+ int from, to;
+ int id = slotClickEvent.slot.getSlotIndex();
+ if (id >= 9 && 0 <= locked.boundTo && locked.boundTo < 8 && !boundLocked.locked) {
+ from = id;
+ to = locked.boundTo;
+ if (boundLocked == DEFAULT_LOCKED_SLOT) {
+ LockedSlot[] lockedSlots = getDataForProfile();
+ lockedSlots[locked.boundTo] = new LockedSlot();
+ lockedSlots[locked.boundTo].boundTo = id;
+ } else {
+ boundLocked.boundTo = id;
}
- } else if (id >= 0 && id < 8 && locked.boundTo >= 9 && locked.boundTo <= 39) {
+ } else if (0 <= id && id < 8 && locked.boundTo >= 9 && locked.boundTo <= 39) {
if (boundLocked.locked || boundLocked.boundTo != id) {
locked.boundTo = -1;
+ return;
} else {
- int boundTo = boundSlot.slotNumber;
- consumer.accept(Triple.of(boundTo, id, 2));
+ from = boundSlot.slotNumber;
+ to = id;
}
+ } else {
+ return;
}
+ if (from == 39) from = 5;
+ if (from == 38) from = 6;
+ if (from == 37) from = 7;
+ if (from == 36) from = 8;
+ Minecraft.getMinecraft().playerController.windowClick(
+ slotClickEvent.guiContainer.inventorySlots.windowId,
+ from, to, 2, Minecraft.getMinecraft().thePlayer
+ );
+ slotClickEvent.setCanceled(true);
} else if (NotEnoughUpdates.INSTANCE.config.slotLocking.enableSlotBinding && locked.boundTo != -1 &&
NotEnoughUpdates.INSTANCE.config.slotLocking.bindingAlsoLocks) {
- consumer.accept(null);
+ slotClickEvent.setCanceled(true);
}
}
@@ -514,7 +550,7 @@ public class SlotLocking {
return;
}
- boolean hoverOverSlot = container.isMouseOverSlot(slot, mouseX, mouseY);
+ boolean hoverOverSlot = ((AccessorGuiContainer) container).doIsMouseOverSlot(slot, mouseX, mouseY);
if (hoverOverSlot || slot.getSlotIndex() >= 9) {
Minecraft.getMinecraft().getTextureManager().bindTexture(BOUND);
@@ -545,8 +581,8 @@ public class SlotLocking {
);
}
} else if (pairingSlot != null && lockKeyHeld && slot.getSlotIndex() < 8) {
- int x1 = container.guiLeft + pairingSlot.xDisplayPosition;
- int y1 = container.guiTop + pairingSlot.yDisplayPosition;
+ int x1 = ((AccessorGuiContainer) container).getGuiLeft() + pairingSlot.xDisplayPosition;
+ int y1 = ((AccessorGuiContainer) container).getGuiTop() + pairingSlot.yDisplayPosition;
if (mouseX <= x1 || mouseX >= x1 + 16 ||
mouseY <= y1 || mouseY >= y1 + 16) {
@@ -634,8 +670,8 @@ public class SlotLocking {
int mouseX = Mouse.getX() * scaledWidth / Minecraft.getMinecraft().displayWidth;
int mouseY = scaledHeight - Mouse.getY() * scaledHeight / Minecraft.getMinecraft().displayHeight - 1;
- int x1 = container.guiLeft + pairingSlot.xDisplayPosition;
- int y1 = container.guiTop + pairingSlot.yDisplayPosition;
+ int x1 = ((AccessorGuiContainer) container).getGuiLeft() + pairingSlot.xDisplayPosition;
+ int y1 = ((AccessorGuiContainer) container).getGuiTop() + pairingSlot.yDisplayPosition;
if (mouseX <= x1 || mouseX >= x1 + 16 ||
mouseY <= y1 || mouseY >= y1 + 16) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StorageManager.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StorageManager.java
index 94aee789..bef07399 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StorageManager.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StorageManager.java
@@ -1,6 +1,35 @@
+/*
+ * 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.*;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.miscgui.StorageOverlay;
import io.github.moulberry.notenoughupdates.util.SBInfo;
@@ -9,18 +38,38 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.inventory.GuiChest;
import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
import net.minecraft.inventory.ContainerChest;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.*;
+import net.minecraft.nbt.JsonToNBT;
+import net.minecraft.nbt.NBTBase;
+import net.minecraft.nbt.NBTException;
+import net.minecraft.nbt.NBTTagByte;
+import net.minecraft.nbt.NBTTagByteArray;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagDouble;
+import net.minecraft.nbt.NBTTagFloat;
+import net.minecraft.nbt.NBTTagInt;
+import net.minecraft.nbt.NBTTagIntArray;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.nbt.NBTTagLong;
+import net.minecraft.nbt.NBTTagShort;
+import net.minecraft.nbt.NBTTagString;
import net.minecraft.network.play.client.C0EPacketClickWindow;
import net.minecraft.network.play.server.S2DPacketOpenWindow;
import net.minecraft.network.play.server.S2EPacketCloseWindow;
import net.minecraft.network.play.server.S2FPacketSetSlot;
import net.minecraft.network.play.server.S30PacketWindowItems;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
@@ -35,11 +84,12 @@ public class StorageManager {
private static final StorageManager INSTANCE = new StorageManager();
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(ItemStack.class, new ItemStackSerializer())
- .registerTypeAdapter(ItemStack.class, new ItemStackDeserilizer()).create();
+ .registerTypeAdapter(ItemStack.class, new ItemStackDeserializer()).create();
public static class ItemStackSerializer implements JsonSerializer<ItemStack> {
@Override
public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) {
+ fixPetInfo(src);
NBTTagCompound tag = src.serializeNBT();
return nbtToJson(tag);
}
@@ -47,7 +97,7 @@ public class StorageManager {
private static final Pattern JSON_FIX_REGEX = Pattern.compile("\"([^,:]+)\":");
- public static class ItemStackDeserilizer implements JsonDeserializer<ItemStack> {
+ public static class ItemStackDeserializer implements JsonDeserializer<ItemStack> {
@Override
public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
@@ -87,6 +137,63 @@ public class StorageManager {
return (JsonObject) loadJson(NBTTagCompound);
}
+ private static class PetInfo {
+ String type;
+ Boolean active;
+ Double exp;
+ String tier;
+ Boolean hideInfo;
+ Integer candyUsed;
+ String uuid;
+ Boolean hideRightClick;
+ String heldItem;
+ String skin;
+
+ private <T> void appendIfNotNull(StringBuilder builder, String key, T value) {
+ if (value != null) {
+ if (builder.indexOf("{") != builder.length()-1) {
+ builder.append(",");
+ }
+ builder.append(key).append(":");
+ if (value instanceof String) {
+ builder.append("\"").append(value).append("\"");
+ } else {
+ builder.append(value);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder object = new StringBuilder();
+ object.append("{");
+ appendIfNotNull(object, "type", type);
+ appendIfNotNull(object, "active", active);
+ appendIfNotNull(object, "exp", exp);
+ appendIfNotNull(object, "tier", tier);
+ appendIfNotNull(object, "hideInfo", hideInfo);
+ appendIfNotNull(object, "candyUsed", candyUsed);
+ appendIfNotNull(object, "uuid", uuid);
+ appendIfNotNull(object, "hideRightClick", hideRightClick);
+ appendIfNotNull(object, "heldItem", heldItem);
+ appendIfNotNull(object, "skin", skin);
+ object.append("}");
+ return object.toString();
+ }
+ }
+
+ private static void fixPetInfo(ItemStack src) {
+ if (src.getTagCompound() == null || !src.getTagCompound().hasKey("ExtraAttributes") || !src.getTagCompound().getCompoundTag("ExtraAttributes").hasKey("petInfo")) return;
+ PetInfo oldPetInfo = GSON.fromJson(src.getTagCompound().getCompoundTag("ExtraAttributes").getString("petInfo"), PetInfo.class);
+ src.getTagCompound().getCompoundTag("ExtraAttributes").removeTag("petInfo");
+ try {
+ src.getTagCompound().getCompoundTag("ExtraAttributes").setTag(
+ "petInfo",
+ JsonToNBT.getTagFromJson(oldPetInfo.toString())
+ );
+ } catch (NBTException | NullPointerException ignored) {}
+ }
+
private static JsonElement loadJson(NBTBase tag) {
if (tag instanceof NBTTagCompound) {
NBTTagCompound compoundTag = (NBTTagCompound) tag;
@@ -187,7 +294,7 @@ public class StorageManager {
private boolean shouldRenderStorageOverlayCached = false;
- private static final Pattern WINDOW_REGEX = Pattern.compile(".+ Backpack (?:\u2726 )?\\((\\d+)/(\\d+)\\)");
+ private static final Pattern WINDOW_REGEX = Pattern.compile(".+ Backpack (?:✦ )?\\(Slot #(\\d+)\\)");
private static final Pattern ECHEST_WINDOW_REGEX = Pattern.compile("Ender Chest \\((\\d+)/(\\d+)\\)");
public void loadConfig(File file) {
@@ -508,8 +615,8 @@ public class StorageManager {
int index = slot - 9;
boolean changed = false;
- if (stack.getItem() == Item.getItemFromBlock(Blocks.stained_glass_pane) &&
- stack.getMetadata() == 14) {
+ if ((stack.getItem() == Item.getItemFromBlock(Blocks.stained_glass_pane) &&
+ stack.getMetadata() == 14) || (stack.getItem() == Items.dye && stack.getMetadata() == 8)) {
if (storagePresent[index]) changed = true;
storagePresent[index] = false;
removePage(index);
@@ -549,7 +656,8 @@ public class StorageManager {
boolean changed = false;
- if (stack.getItem() == Item.getItemFromBlock(Blocks.stained_glass_pane)) {
+ if (stack.getItem() == Item.getItemFromBlock(Blocks.stained_glass_pane)
+ || (stack.getItem() == Items.dye && stack.getMetadata() == 8)) {
if (storagePresent[index]) changed = true;
storagePresent[index] = false;
removePage(index);
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StreamerMode.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StreamerMode.java
index 064f41ca..a2951e56 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StreamerMode.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/StreamerMode.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.util.Utils;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SunTzu.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SunTzu.java
index 27cf42ac..7fc8beba 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SunTzu.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/SunTzu.java
@@ -1,3 +1,22 @@
+/*
+ * 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 io.github.moulberry.notenoughupdates.util.Utils;
@@ -37,7 +56,8 @@ public class SunTzu {
"The wise warrior avoids the battle.",
"Great results, can be achieved with small forces.",
"Attack is the secret of defense; defense is the planning of an attack.",
- "Subscribe to Moulberry on YouTube."
+ "Subscribe to Moulberry on YouTube.",
+ "Technoblade never dies!"
};
public static void setEnabled(boolean enabled) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CrystalHollowsTextures.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CrystalHollowsTextures.java
index 36858276..7d23d922 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CrystalHollowsTextures.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CrystalHollowsTextures.java
@@ -1,3 +1,22 @@
+/*
+ * 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.customblockzones;
import net.minecraft.util.BlockPos;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBiomes.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBiomes.java
index 9a6f9cac..48a01187 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBiomes.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBiomes.java
@@ -1,3 +1,22 @@
+/*
+ * 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.customblockzones;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBlockSounds.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBlockSounds.java
index dec011d3..2a3e9ec4 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBlockSounds.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/CustomBlockSounds.java
@@ -1,8 +1,26 @@
+/*
+ * 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.customblockzones;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
-import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.ISound;
import net.minecraft.client.audio.PositionedSoundRecord;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/DwarvenMinesTextures.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/DwarvenMinesTextures.java
index faf7eabe..1c6acc1d 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/DwarvenMinesTextures.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/DwarvenMinesTextures.java
@@ -1,3 +1,22 @@
+/*
+ * 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.customblockzones;
import com.google.gson.JsonArray;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/IslandZoneSubdivider.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/IslandZoneSubdivider.java
index 8b00bc85..75ff566f 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/IslandZoneSubdivider.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/IslandZoneSubdivider.java
@@ -1,3 +1,22 @@
+/*
+ * 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.customblockzones;
import net.minecraft.util.BlockPos;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/LocationChangeEvent.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/LocationChangeEvent.java
index c6f5e9c0..5a87af11 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/LocationChangeEvent.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/LocationChangeEvent.java
@@ -1,3 +1,22 @@
+/*
+ * 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.customblockzones;
import net.minecraftforge.fml.common.eventhandler.Event;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/SpecialBlockZone.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/SpecialBlockZone.java
index f4e8f1fc..e88e2c25 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/SpecialBlockZone.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/customblockzones/SpecialBlockZone.java
@@ -1,3 +1,22 @@
+/*
+ * 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.customblockzones;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/AgeModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/AgeModifier.java
new file mode 100644
index 00000000..963fa793
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/AgeModifier.java
@@ -0,0 +1,49 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.mixins.AccessorEntityAgeable;
+import io.github.moulberry.notenoughupdates.mixins.AccessorEntityArmorStand;
+import net.minecraft.entity.EntityAgeable;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.item.EntityArmorStand;
+import net.minecraft.entity.monster.EntityZombie;
+
+public class AgeModifier extends EntityViewerModifier {
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ boolean baby = info.has("baby") && info.get("baby").getAsBoolean();
+ if (base instanceof EntityAgeable) {
+ ((AccessorEntityAgeable) base).setGrowingAgeDirect(baby ? -1 : 1);
+ return base;
+ }
+ if (base instanceof EntityZombie) {
+ ((EntityZombie) base).setChild(baby);
+ return base;
+ }
+ if (base instanceof EntityArmorStand) {
+ ((AccessorEntityArmorStand) base).setSmallDirect(baby);
+ return base;
+ }
+ System.out.println("Cannot apply age to a non ageable entity: " + base);
+ return null;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/ChargedModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/ChargedModifier.java
new file mode 100644
index 00000000..5f452132
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/ChargedModifier.java
@@ -0,0 +1,36 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.monster.EntityCreeper;
+
+public class ChargedModifier extends EntityViewerModifier {
+
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ if (base instanceof EntityCreeper) {
+ base.getDataWatcher().updateObject(17, (byte) 1);
+ return base;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewer.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewer.java
new file mode 100644
index 00000000..367d62d3
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewer.java
@@ -0,0 +1,245 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.inventory.GuiInventory;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.boss.EntityDragon;
+import net.minecraft.entity.boss.EntityWither;
+import net.minecraft.entity.item.EntityArmorStand;
+import net.minecraft.entity.monster.EntityBlaze;
+import net.minecraft.entity.monster.EntityCaveSpider;
+import net.minecraft.entity.monster.EntityCreeper;
+import net.minecraft.entity.monster.EntityEnderman;
+import net.minecraft.entity.monster.EntityEndermite;
+import net.minecraft.entity.monster.EntityGhast;
+import net.minecraft.entity.monster.EntityGuardian;
+import net.minecraft.entity.monster.EntityIronGolem;
+import net.minecraft.entity.monster.EntityMagmaCube;
+import net.minecraft.entity.monster.EntityPigZombie;
+import net.minecraft.entity.monster.EntitySilverfish;
+import net.minecraft.entity.monster.EntitySkeleton;
+import net.minecraft.entity.monster.EntitySlime;
+import net.minecraft.entity.monster.EntitySnowman;
+import net.minecraft.entity.monster.EntitySpider;
+import net.minecraft.entity.monster.EntityWitch;
+import net.minecraft.entity.monster.EntityZombie;
+import net.minecraft.entity.passive.EntityBat;
+import net.minecraft.entity.passive.EntityChicken;
+import net.minecraft.entity.passive.EntityCow;
+import net.minecraft.entity.passive.EntityHorse;
+import net.minecraft.entity.passive.EntityMooshroom;
+import net.minecraft.entity.passive.EntityOcelot;
+import net.minecraft.entity.passive.EntityPig;
+import net.minecraft.entity.passive.EntityRabbit;
+import net.minecraft.entity.passive.EntitySheep;
+import net.minecraft.entity.passive.EntitySquid;
+import net.minecraft.entity.passive.EntityVillager;
+import net.minecraft.entity.passive.EntityWolf;
+import net.minecraft.util.ResourceLocation;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class EntityViewer extends GuiScreen {
+
+ public static Map<String, Supplier<? extends EntityLivingBase>> validEntities =
+ new HashMap<String, Supplier<? extends EntityLivingBase>>() {{
+ put("Zombie", () -> new EntityZombie(null));
+ put("Chicken", () -> new EntityChicken(null));
+ put("Slime", () -> new EntitySlime(null));
+ put("Wolf", () -> new EntityWolf(null));
+ put("Skeleton", () -> new EntitySkeleton(null));
+ put("Creeper", () -> new EntityCreeper(null));
+ put("Ocelot", () -> new EntityOcelot(null));
+ put("Blaze", () -> new EntityBlaze(null));
+ put("Rabbit", () -> new EntityRabbit(null));
+ put("Sheep", () -> new EntitySheep(null));
+ put("Horse", () -> new EntityHorse(null));
+ put("Eisengolem", () -> new EntityIronGolem(null));
+ put("Silverfish", () -> new EntitySilverfish(null));
+ put("Witch", () -> new EntityWitch(null));
+ put("Endermite", () -> new EntityEndermite(null));
+ put("Snowman", () -> new EntitySnowman(null));
+ put("Villager", () -> new EntityVillager(null));
+ put("Guardian", () -> new EntityGuardian(null));
+ put("ArmorStand", () -> new EntityArmorStand(null));
+ put("Squid", () -> new EntitySquid(null));
+ put("Bat", () -> new EntityBat(null));
+ put("Spider", () -> new EntitySpider(null));
+ put("CaveSpider", () -> new EntityCaveSpider(null));
+ put("Pigman", () -> new EntityPigZombie(null));
+ put("Ghast", () -> new EntityGhast(null));
+ put("MagmaCube", () -> new EntityMagmaCube(null));
+ put("Wither", () -> new EntityWither(null));
+ put("Enderman", () -> new EntityEnderman(null));
+ put("Mooshroom", () -> new EntityMooshroom(null));
+ put("WitherSkeleton", () -> {
+ EntitySkeleton skeleton = new EntitySkeleton(null);
+ skeleton.setSkeletonType(1);
+ return skeleton;
+ });
+ put("Cow", () -> new EntityCow(null));
+ put("Dragon", () -> new EntityDragon(null));
+ put("Player", () -> new GUIClientPlayer());
+ put("Pig", () -> new EntityPig(null));
+ }};
+
+ public static Map<String, EntityViewerModifier> validModifiers = new HashMap<String, EntityViewerModifier>() {{
+ put("playerdata", new SkinModifier());
+ put("equipment", new EquipmentModifier());
+ put("riding", new RidingModifier());
+ put("charged", new ChargedModifier());
+ put("witherdata", new WitherModifier());
+ put("invisible", new InvisibleModifier());
+ put("age", new AgeModifier());
+ put("horse", new HorseModifier());
+ put("name", new NameModifier());
+ }};
+
+ public int guiLeft = 0;
+ public int guiTop = 0;
+ public int xSize = 176;
+ public int ySize = 166;
+
+ private final String label;
+ private final EntityLivingBase entity;
+ private static final ResourceLocation BACKGROUND = new ResourceLocation(
+ "notenoughupdates",
+ "textures/gui/entity_viewer.png"
+ );
+
+ public EntityViewer(String label, EntityLivingBase entity) {
+ this.label = label;
+ this.entity = entity;
+ }
+
+ public static EntityLivingBase constructEntity(ResourceLocation resourceLocation) {
+ Gson gson = NotEnoughUpdates.INSTANCE.manager.gson;
+ try (
+ Reader is = new InputStreamReader(
+ Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation).getInputStream(),
+ StandardCharsets.UTF_8
+ )
+ ) {
+ return constructEntity(gson.fromJson(is, JsonObject.class));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static EntityLivingBase constructEntity(JsonObject info) {
+ List<JsonObject> modifiers = info.has("modifiers") ?
+ StreamSupport.stream(info.get("modifiers").getAsJsonArray().spliterator(), false)
+ .map(JsonElement::getAsJsonObject).collect(Collectors.toList())
+ : Collections.emptyList();
+ return EntityViewer.constructEntity(info.get("entity").getAsString(), modifiers);
+ }
+
+ public static EntityLivingBase constructEntity(String string, String[] modifiers) {
+ Gson gson = NotEnoughUpdates.INSTANCE.manager.gson;
+ return constructEntity(
+ string,
+ Arrays.stream(modifiers).map(it -> gson.fromJson(it, JsonObject.class)).collect(Collectors.toList())
+ );
+ }
+
+ public static EntityLivingBase constructEntity(String string, List<JsonObject> modifiers) {
+ Supplier<? extends EntityLivingBase> aClass = validEntities.get(string);
+ if (aClass == null) {
+ System.err.println("Could not find entity of type: " + string);
+ return null;
+ }
+ try {
+ EntityLivingBase entity = aClass.get();
+ for (JsonObject modifier : modifiers) {
+ String type = modifier.get("type").getAsString();
+ EntityViewerModifier entityViewerModifier = validModifiers.get(type);
+ entity = entityViewerModifier.applyModifier(entity, modifier);
+ if (entity == null) break;
+ }
+ return entity;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+ drawDefaultBackground();
+ FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj;
+
+ this.guiLeft = (width - this.xSize) / 2;
+ this.guiTop = (height - this.ySize) / 2;
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(BACKGROUND);
+ drawTexturedModalRect(guiLeft, guiTop, 0, 0, this.xSize, this.ySize);
+
+ Utils.drawStringScaledMaxWidth(label, fontRenderer, guiLeft + 10, guiTop + 10, false, 100, 0xFF00FF);
+ renderEntity(entity, guiLeft + 90, guiTop + 75, mouseX, mouseY);
+ }
+
+ public static void renderEntity(EntityLivingBase entity, int posX, int posY, int mouseX, int mouseY) {
+ GlStateManager.color(1F, 1F, 1F, 1F);
+
+ int scale = 30;
+ float bottomOffset = 0F;
+ EntityLivingBase stack = entity;
+ while (true) {
+
+ stack.ticksExisted = Minecraft.getMinecraft().thePlayer.ticksExisted;
+ GuiInventory.drawEntityOnScreen(
+ posX,
+ (int) (posY - bottomOffset * scale),
+ scale,
+ posX - mouseX,
+ (int) (posY - stack.getEyeHeight() * scale - mouseY),
+ stack
+ );
+ bottomOffset += stack.getMountedYOffset();
+ if (!(stack.riddenByEntity instanceof EntityLivingBase)) {
+ break;
+ }
+ stack = (EntityLivingBase) stack.riddenByEntity;
+ }
+
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewerModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewerModifier.java
new file mode 100644
index 00000000..aa6d22c9
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EntityViewerModifier.java
@@ -0,0 +1,27 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+
+public abstract class EntityViewerModifier {
+ public abstract EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info);
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EquipmentModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EquipmentModifier.java
new file mode 100644
index 00000000..685e161a
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/EquipmentModifier.java
@@ -0,0 +1,91 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemArmor;
+import net.minecraft.item.ItemStack;
+
+public class EquipmentModifier extends EntityViewerModifier {
+
+ private ItemStack createItem(String item) {
+ NEUManager manager = NotEnoughUpdates.INSTANCE.manager;
+ String[] split = item.split("#");
+ if (split.length == 2) {
+ switch (split[0].intern()) {
+ case "LEATHER_LEGGINGS":
+ return coloredLeatherArmor(Items.leather_leggings, split[1]);
+ case "LEATHER_HELMET":
+ return coloredLeatherArmor(Items.leather_helmet, split[1]);
+ case "LEATHER_CHESTPLATE":
+ return coloredLeatherArmor(Items.leather_chestplate, split[1]);
+ case "LEATHER_BOOTS":
+ return coloredLeatherArmor(Items.leather_boots, split[1]);
+ default:
+ throw new RuntimeException("Unknown leather piece: " + item);
+ }
+ }
+ return manager.createItem(item);
+ }
+
+ private ItemStack coloredLeatherArmor(ItemArmor item, String colorHex) {
+ ItemStack is = new ItemStack(item);
+ item.setColor(is, Integer.parseInt(colorHex, 16));
+ return is;
+ }
+
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ if (info.has("hand"))
+ setCurrentItemOrArmor(base, 0, createItem(info.get("hand").getAsString()));
+ if (info.has("helmet"))
+ setCurrentItemOrArmor(base, 4, createItem(info.get("helmet").getAsString()));
+ if (info.has("chestplate"))
+ setCurrentItemOrArmor(base, 3, createItem(info.get("chestplate").getAsString()));
+ if (info.has("leggings"))
+ setCurrentItemOrArmor(base, 2, createItem(info.get("leggings").getAsString()));
+ if (info.has("feet"))
+ setCurrentItemOrArmor(base, 1, createItem(info.get("feet").getAsString()));
+ return base;
+ }
+
+ public void setCurrentItemOrArmor(EntityLivingBase entity, int slot, ItemStack itemStack) {
+ if (entity instanceof EntityPlayer) {
+ setPlayerCurrentItemOrArmor((EntityPlayer) entity, slot, itemStack);
+ } else {
+ entity.setCurrentItemOrArmor(slot, itemStack);
+ }
+ }
+
+ // Biscuit person needs to learn how to code and not fuck up valid vanilla behaviour
+ public static void setPlayerCurrentItemOrArmor(EntityPlayer player, int slot, ItemStack itemStack) {
+ if (slot == 0) {
+ player.inventory.mainInventory[player.inventory.currentItem] = itemStack;
+ } else {
+ player.inventory.armorInventory[slot - 1] = itemStack;
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/GUIClientPlayer.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/GUIClientPlayer.java
new file mode 100644
index 00000000..8cdf2ef0
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/GUIClientPlayer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.entityviewer;
+
+import com.mojang.authlib.GameProfile;
+import net.minecraft.client.entity.AbstractClientPlayer;
+import net.minecraft.client.network.NetworkPlayerInfo;
+import net.minecraft.client.resources.DefaultPlayerSkin;
+import net.minecraft.util.ResourceLocation;
+
+import java.util.UUID;
+
+public class GUIClientPlayer extends AbstractClientPlayer {
+ public GUIClientPlayer() {
+ super(null, new GameProfile(UUID.randomUUID(), "GuiPlayer"));
+ }
+
+ ResourceLocation overrideSkin = DefaultPlayerSkin.getDefaultSkinLegacy();
+ ResourceLocation overrideCape = null;
+ boolean overrideIsSlim = false;
+ NetworkPlayerInfo playerInfo = new NetworkPlayerInfo(this.getGameProfile()) {
+ @Override
+ public String getSkinType() {
+ return overrideIsSlim ? "slim" : "default";
+ }
+
+ @Override
+ public ResourceLocation getLocationSkin() {
+ return overrideSkin;
+ }
+
+ @Override
+ public ResourceLocation getLocationCape() {
+ return overrideCape;
+ }
+ };
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public String name;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ protected NetworkPlayerInfo getPlayerInfo() {
+ return playerInfo;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/HorseModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/HorseModifier.java
new file mode 100644
index 00000000..b29152bf
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/HorseModifier.java
@@ -0,0 +1,85 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.passive.EntityHorse;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+public class HorseModifier extends EntityViewerModifier {
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ if (!(base instanceof EntityHorse))
+ return null;
+ EntityHorse horse = (EntityHorse) base;
+ if (info.has("kind")) {
+ String type = info.get("kind").getAsString().intern();
+ switch (type) {
+ case "skeleton":
+ horse.setHorseType(4);
+ break;
+ case "zombie":
+ horse.setHorseType(3);
+ break;
+ case "mule":
+ horse.setHorseType(2);
+ break;
+ case "donkey":
+ horse.setHorseType(1);
+ break;
+ case "horse":
+ horse.setHorseType(0);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown horse type: " + type);
+ }
+ }
+ if (info.has("armor")) {
+ JsonElement el = info.get("armor");
+ if (el.isJsonNull()) {
+ horse.setHorseArmorStack(null);
+ } else {
+ Item item;
+ switch (el.getAsString().intern()) {
+ case "iron":
+ item = Items.iron_horse_armor;
+ break;
+ case "golden":
+ item = Items.golden_horse_armor;
+ break;
+ case "diamond":
+ item = Items.diamond_horse_armor;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown horse armor: " + el.getAsString());
+ }
+ horse.setHorseArmorStack(new ItemStack(item));
+ }
+ }
+ if (info.has("saddled")) {
+ horse.setHorseSaddled(info.get("saddled").getAsBoolean());
+ }
+ return horse;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/InvisibleModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/InvisibleModifier.java
new file mode 100644
index 00000000..74535ce9
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/InvisibleModifier.java
@@ -0,0 +1,31 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+
+public class InvisibleModifier extends EntityViewerModifier {
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ base.setInvisible(!info.has("invisible") || info.get("invisible").getAsBoolean());
+ return base;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/NameModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/NameModifier.java
new file mode 100644
index 00000000..c0fd23bd
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/NameModifier.java
@@ -0,0 +1,34 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+
+public class NameModifier extends EntityViewerModifier {
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ if (base instanceof GUIClientPlayer) {
+ ((GUIClientPlayer) base).setName(info.get("name").getAsString());
+ }
+ base.setCustomNameTag(info.get("name").getAsString());
+ return base;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/RidingModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/RidingModifier.java
new file mode 100644
index 00000000..72dd8b73
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/RidingModifier.java
@@ -0,0 +1,33 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+
+public class RidingModifier extends EntityViewerModifier {
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ EntityLivingBase newEntity = EntityViewer.constructEntity(info);
+ if (newEntity == null) return null;
+ newEntity.mountEntity(base);
+ return base;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/SkinModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/SkinModifier.java
new file mode 100644
index 00000000..076e8417
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/SkinModifier.java
@@ -0,0 +1,65 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EnumPlayerModelParts;
+import net.minecraft.util.ResourceLocation;
+
+import java.util.Map;
+
+public class SkinModifier extends EntityViewerModifier {
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ if (base instanceof GUIClientPlayer) {
+ GUIClientPlayer player = (GUIClientPlayer) base;
+ if (info.has("cape")) {
+ player.overrideCape = new ResourceLocation(info.get("cape").getAsString());
+ }
+ if (info.has("skin")) {
+ player.overrideSkin = new ResourceLocation(info.get("skin").getAsString());
+ }
+ if (info.has("slim")) {
+ player.overrideIsSlim = info.get("slim").getAsBoolean();
+ }
+ if (info.has("parts")) {
+ JsonElement parts = info.get("parts");
+ byte partBitField = player.getDataWatcher().getWatchableObjectByte(10);
+ if (parts.isJsonPrimitive() && parts.getAsJsonPrimitive().isBoolean()) {
+ partBitField = parts.getAsBoolean() ? (byte) -1 : 0;
+ } else {
+ JsonObject obj = parts.getAsJsonObject();
+ for (Map.Entry<String, JsonElement> part : obj.entrySet()) {
+ EnumPlayerModelParts modelPart = EnumPlayerModelParts.valueOf(part.getKey());
+ if (part.getValue().getAsBoolean()) {
+ partBitField |= modelPart.getPartMask();
+ } else {
+ partBitField &= ~modelPart.getPartMask();
+ }
+ }
+ }
+ player.getDataWatcher().updateObject(10, partBitField);
+ }
+ }
+ return base;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/WitherModifier.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/WitherModifier.java
new file mode 100644
index 00000000..3b73c26f
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/entityviewer/WitherModifier.java
@@ -0,0 +1,48 @@
+/*
+ * 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.entityviewer;
+
+import com.google.gson.JsonObject;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.boss.EntityWither;
+
+public class WitherModifier extends EntityViewerModifier {
+ @Override
+ public EntityLivingBase applyModifier(EntityLivingBase base, JsonObject info) {
+ if (!(base instanceof EntityWither))
+ return null;
+ EntityWither wither = (EntityWither) base;
+ if (info.has("tiny")) {
+ if (info.get("tiny").getAsBoolean()) {
+ wither.setInvulTime(800);
+ } else {
+ wither.setInvulTime(0);
+ }
+ }
+ if (info.has("armored")) {
+ if (info.get("armored").getAsBoolean()) {
+ wither.setHealth(1);
+ } else {
+ wither.setHealth(wither.getMaxHealth());
+ }
+ }
+ return base;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java
new file mode 100644
index 00000000..54fcc204
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java
@@ -0,0 +1,256 @@
+/*
+ * 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.updater;
+
+import com.google.common.collect.Iterables;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.util.MoulSigner;
+import io.github.moulberry.notenoughupdates.util.NotificationHandler;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.event.ClickEvent;
+import net.minecraft.event.HoverEvent;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.ChatStyle;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.IChatComponent;
+import net.minecraftforge.fml.common.Loader;
+import org.apache.commons.lang3.SystemUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class AutoUpdater {
+
+ NotEnoughUpdates neu;
+
+ public AutoUpdater(NotEnoughUpdates neu) {
+ this.neu = neu;
+ }
+
+ public void logProgress(String str) {
+ logProgress(new ChatComponentText(str));
+ }
+
+ public void logProgress(IChatComponent chatComponent) {
+ Minecraft.getMinecraft().addScheduledTask(() -> {
+ IChatComponent chatComponent1 = new ChatComponentText("");
+ chatComponent1.setChatStyle(new ChatStyle().setColor(EnumChatFormatting.AQUA));
+ chatComponent1.appendSibling(chatComponent);
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("§e[NEU-AutoUpdater] ").appendSibling(
+ chatComponent1));
+ });
+ }
+
+ public UpdateLoader getUpdateLoader(URL url) {
+ if (SystemUtils.IS_OS_UNIX) {
+ return new LinuxBasedUpdater(this, url);
+ }
+ if (Loader.isModLoaded("skyblockclientupdater") && SCUCompatUpdater.IS_ENABLED) {
+ return SCUCompatUpdater.tryCreate(this, url);
+ }
+ return null;
+ }
+
+ UpdateLoader updateLoader;
+
+ public void updateFromURL(URL url) {
+ if (updateLoader != null) {
+ logProgress(
+ "There is already an update in progress, so the auto updater cannot process this update (as it might already be installed or is currently being downloaded). Please restart your client to install another update");
+ return;
+ }
+ if (!"https".equals(url.getProtocol())) {
+ logProgress("§cInvalid protocol in url: " + url + ". Only https is a valid protocol.");
+ return;
+ }
+ updateLoader = getUpdateLoader(url);
+ if (updateLoader == null) {
+ return;
+ }
+ updateLoader.greet();
+ logProgress(new ChatComponentText("[Start download now]")
+ .setChatStyle(new ChatStyle()
+ .setColor(EnumChatFormatting.GREEN)
+ .setChatHoverEvent(new HoverEvent(
+ HoverEvent.Action.SHOW_TEXT,
+ new ChatComponentText("Click to start download.")
+ ))
+ .setChatClickEvent(new ClickEvent(
+ ClickEvent.Action.RUN_COMMAND,
+ "/neuupdate scheduleDownload"
+ ))));
+ }
+
+ public void scheduleDownload() {
+ if (updateLoader == null) {
+ logProgress("§cNo update found. Try running /neuupdate check first");
+ return;
+ }
+ if (updateLoader.getState() != UpdateLoader.State.NOTHING) {
+ logProgress("§cUpdate download already started. No need to start the download again.");
+ return;
+ }
+ logProgress("Download started.");
+ updateLoader.scheduleDownload();
+ }
+
+ private void displayUpdateMessage(
+ JsonObject updateJson,
+ String updateMessage,
+ String downloadLink,
+ String directDownload
+ ) {
+ int firstWidth = -1;
+
+ for (String line : Iterables.concat(Arrays.asList(updateMessage.split("\n")), Arrays.asList("Download here"))) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ boolean isDownloadLink = line.equals("Download here");
+ int width = fr.getStringWidth(line);
+ if (firstWidth == -1) {
+ firstWidth = width;
+ }
+ int missingLen = firstWidth - width;
+ if (missingLen > 0) {
+ StringBuilder sb = new StringBuilder(missingLen / 4 / 2 + line.length());
+ for (int i = 0; i < missingLen / 4 / 2; i++) { /* fr.getCharWidth(' ') == 4 */
+ sb.append(" ");
+ }
+ sb.append(line);
+ line = sb.toString();
+ }
+ ChatComponentText cp = new ChatComponentText(line);
+ if (isDownloadLink)
+ cp.setChatStyle(Utils.createClickStyle(ClickEvent.Action.OPEN_URL, downloadLink));
+ Minecraft.getMinecraft().thePlayer.addChatMessage(cp);
+ }
+ neu.displayLinks(updateJson, firstWidth);
+ NotificationHandler.displayNotification(Arrays.asList(
+ "",
+ "§eThere is a new version of NotEnoughUpdates available.",
+ "§eCheck the chat for more information"
+ ), true);
+ try {
+ if (directDownload != null)
+ updateFromURL(new URL(directDownload));
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void displayUpdateMessageIfOutOfDate() {
+ File repo = neu.manager.repoLocation;
+ File updateJson = new File(repo, "update.json");
+ if (updateJson.exists()) {
+ if (!MoulSigner.verifySignature(updateJson)) {
+ NotEnoughUpdates.LOGGER.error("update.json found without signature");
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(
+ "§e[NEU] §cThere has been an error checking for updates. Check the log or join the discord for more information.").setChatStyle(
+ Utils.createClickStyle(
+ ClickEvent.Action.OPEN_URL, "https://discord.gg/moulberry")));
+ return;
+ }
+ try {
+ JsonObject o = neu.manager.getJsonFromFile(updateJson);
+
+ int fullReleaseVersion =
+ o.has("version_id") && o.get("version_id").isJsonPrimitive() ? o.get("version_id").getAsInt() : -1;
+ int preReleaseVersion =
+ o.has("version_id") && o.get("version_id").isJsonPrimitive() ? o.get("version_id").getAsInt() : -1;
+ int hotfixVersion =
+ o.has("hotfix_id") && o.get("hotfix_id").isJsonPrimitive() ? o.get("hotfix_id").getAsInt() : -1;
+
+ boolean hasFullReleaseAvailableForUpgrade = fullReleaseVersion > NotEnoughUpdates.VERSION_ID;
+ boolean hasHotfixAvailableForUpgrade =
+ fullReleaseVersion == NotEnoughUpdates.VERSION_ID && hotfixVersion > NotEnoughUpdates.HOTFIX_VERSION_ID;
+ boolean hasPreReleaseAvailableForUpdate =
+ fullReleaseVersion == NotEnoughUpdates.VERSION_ID && preReleaseVersion > NotEnoughUpdates.PRE_VERSION_ID;
+
+ int updateChannel = NotEnoughUpdates.INSTANCE.config.notifications.updateChannel; /* 1 = Full, 2 = Pre */
+ if (hasFullReleaseAvailableForUpgrade || (hasHotfixAvailableForUpgrade && updateChannel == 1)) {
+ displayUpdateMessage(
+ o,
+ o.get("update_msg").getAsString(),
+ o.get("update_link").getAsString(),
+ o.has("update_direct") ? o.get("update_direct").getAsString() : null
+ );
+ } else if (hasPreReleaseAvailableForUpdate && updateChannel == 2) {
+ displayUpdateMessage(
+ o,
+ o.get("pre_update_msg").getAsString(),
+ o.get("pre_update_link").getAsString(),
+ o.has("pre_update_direct") ? o.get("pre_update_direct").getAsString() : null
+ );
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(
+ "§e[NEU] §cThere has been an error checking for updates. Check the log or join the discord for more information.").setChatStyle(
+ Utils.createClickStyle(
+ ClickEvent.Action.OPEN_URL, "https://discord.gg/moulberry")));
+ }
+ }
+ }
+
+ private boolean validateMcModInfo(JsonArray array) {
+ if (array.size() != 1) return false;
+ JsonElement jsonElement = array.get(0);
+ if (!jsonElement.isJsonObject()) return false;
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ if (!jsonObject.has("modid")) return false;
+ JsonElement modid = jsonObject.get("modid");
+ if (!modid.isJsonPrimitive()) return false;
+ JsonPrimitive primitive = modid.getAsJsonPrimitive();
+ if (!primitive.isString()) return false;
+ return "notenoughupdates".equals(primitive.getAsString());
+ }
+
+ public boolean isNeuJar(File sus) {
+ try (ZipFile zipFile = new ZipFile(sus)) {
+ ZipEntry entry = zipFile.getEntry("mcmod.info");
+ if (entry == null) {
+ return false;
+ }
+ try (InputStream inputStream = zipFile.getInputStream(entry)) {
+ JsonArray jsonArray = neu.manager.gson.fromJson(
+ new InputStreamReader(inputStream),
+ JsonArray.class
+ );
+ return validateMcModInfo(jsonArray);
+ }
+ } catch (IOException | JsonSyntaxException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/LinuxBasedUpdater.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/LinuxBasedUpdater.java
new file mode 100644
index 00000000..3118b135
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/LinuxBasedUpdater.java
@@ -0,0 +1,47 @@
+/*
+ * 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.updater;
+
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+class LinuxBasedUpdater /* Based on what? */ extends UpdateLoader {
+
+ LinuxBasedUpdater(AutoUpdater updater, URL url) {
+ super(updater, url);
+ }
+
+ @Override
+ public void greet() {
+ updater.logProgress(
+ "Welcome Aristocrat! Your superior linux system configuration is supported for NEU auto updates.");
+ }
+
+ @Override
+ public void deleteFiles(List<File> toDelete) {
+ for (File toDel : toDelete) {
+ if (!toDel.delete()) {
+ updater.logProgress("§cCould not delete old version of NEU: " + toDel + ". Please manually delete file.");
+ state = State.FAILED;
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/SCUCompatUpdater.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/SCUCompatUpdater.java
new file mode 100644
index 00000000..3cb600cb
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/SCUCompatUpdater.java
@@ -0,0 +1,100 @@
+/*
+ * 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.updater;
+
+import java.io.File;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.List;
+
+/*
+ * Legal considerations: Skyblock Client Updater is licensed under the GNU AGPL v3.0 or later (with modifications).
+ * https://github.com/My-Name-Is-Jeff/SkyblockClient-Updater/blob/main/LICENSE
+ *
+ * However, even tho the AGPL License does not allow conveying covered work in combination with LGPL licensed code
+ * (such as our own), we do not perceive ourselves as conveying neither an unmodified version of Skyblock Client Updater
+ * nor a work based on Skyblock Client Updater (modified work) since our work is usable and functional in its entirety
+ * without presence of Skyblock Client Updater and is not to be distributed along a copy of Skyblock Client Updater
+ * unless that combined work is licensed with respect of both the LGPL and the AGPL, therefore is not adapting any part
+ * of Skyblock Client Updater unless already part of a whole distribution.
+ *
+ * In case the Copyright owner (Lily aka My-Name-Is-Jeff on Github) disagrees, we are willing to take down this module
+ * (or only convey this component of our work under a pure GPL license) with or without them providing legal grounds
+ * for this request. However, due to them not being able to be reached for comment, we will include this
+ * component for the time being.
+ * */
+public class SCUCompatUpdater extends UpdateLoader {
+
+ public static final boolean IS_ENABLED = false;
+
+ private SCUCompatUpdater(AutoUpdater updater, URL url) {
+ super(updater, url);
+ }
+
+ @Override
+ public void greet() {
+ updater.logProgress("Skyblock Client Updater compatibility layer loaded.");
+ }
+
+ @Override
+ public void deleteFiles(List<File> toDelete) {
+ try {
+ for (File f : toDelete)
+ ReflectionHolder.deleteFileOnShutdownHandle.invoke(ReflectionHolder.updateCheckerInstance, f, "");
+ } catch (Throwable e) {
+ e.printStackTrace();
+ updater.logProgress("Invoking SCU failed. Check the log for more info.");
+ state = State.FAILED;
+ }
+ }
+
+ static class ReflectionHolder {
+ static boolean isSCUFullyPresent = false;
+ static Class<?> updateChecker;
+ static Object updateCheckerInstance;
+ static Method deleteFileOnShutdown;
+ static MethodHandle deleteFileOnShutdownHandle;
+
+ static {
+ try {
+ updateChecker = Class.forName("mynameisjeff.skyblockclientupdater.utils.UpdateChecker");
+ Field instanceField = updateChecker.getDeclaredField("INSTANCE");
+ instanceField.setAccessible(true);
+ updateCheckerInstance = instanceField.get(null);
+ deleteFileOnShutdown = updateChecker.getDeclaredMethod("deleteFileOnShutdown", File.class, String.class);
+ deleteFileOnShutdownHandle = MethodHandles.publicLookup().unreflect(deleteFileOnShutdown);
+ isSCUFullyPresent = true;
+ } catch (NoSuchFieldException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static UpdateLoader tryCreate(AutoUpdater updater, URL url) {
+ if (!ReflectionHolder.isSCUFullyPresent) {
+ updater.logProgress("§cFound Skyclient Updater Mod, however our hooks did not function properly.");
+ return null;
+ }
+ return new SCUCompatUpdater(updater, url);
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/UpdateLoader.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/UpdateLoader.java
new file mode 100644
index 00000000..1a1a8504
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/UpdateLoader.java
@@ -0,0 +1,141 @@
+/*
+ * 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.updater;
+
+import io.github.moulberry.notenoughupdates.util.NetUtils;
+import net.minecraft.client.Minecraft;
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class UpdateLoader {
+
+ enum State {
+ NOTHING, DOWNLOAD_STARTED, DOWNLOAD_FINISHED, INSTALLED, FAILED
+ }
+
+ URL url;
+ AutoUpdater updater;
+
+ State state = State.NOTHING;
+
+ UpdateLoader(AutoUpdater updater, URL url) {
+ this.url = url;
+ this.updater = updater;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ }
+
+ public URL getUrl() {
+ return url;
+ }
+
+ public void scheduleDownload() {
+ state = State.DOWNLOAD_STARTED;
+ try {
+ NetUtils.downloadAsync(url, File.createTempFile("NotEnoughUpdates-update", ".jar"))
+ .handle(
+ (f, exc) -> {
+ if (exc != null) {
+ state = State.FAILED;
+ updater.logProgress("§cError while downloading. Check your logs for more info.");
+ exc.printStackTrace();
+ return null;
+ }
+ state = State.DOWNLOAD_FINISHED;
+
+ updater.logProgress("Download completed. Trying to install");
+ launchUpdate(f);
+ return null;
+ });
+ } catch (IOException e) {
+ state = State.FAILED;
+ updater.logProgress("§cError while creating download. Check your logs for more info.");
+ e.printStackTrace();
+ }
+ }
+
+ public abstract void greet();
+
+ public void launchUpdate(File file) {
+
+ if (state != State.DOWNLOAD_FINISHED) {
+ updater.logProgress("§cUpdate is invalid state " + state + " to start update.");
+ state = State.FAILED;
+ return;
+ }
+ File mcDataDir = new File(Minecraft.getMinecraft().mcDataDir, "mods");
+ if (!mcDataDir.exists() || !mcDataDir.isDirectory() || !mcDataDir.canRead()) {
+ updater.logProgress("§cCould not find mods folder. Searched: " + mcDataDir);
+ state = State.FAILED;
+ return;
+ }
+ ArrayList<File> toDelete = new ArrayList<>();
+ File[] modFiles = mcDataDir.listFiles();
+ if (modFiles == null) {
+ updater.logProgress("§cCould not list minecraft mod folder (" + mcDataDir + ")");
+ state = State.FAILED;
+ return;
+ }
+ for (File sus : modFiles) {
+ if (sus.getName().endsWith(".jar")) {
+ if (updater.isNeuJar(sus)) {
+ updater.logProgress("Found old NEU file: " + sus + ". Deleting later.");
+ toDelete.add(sus);
+ }
+ }
+ }
+ File dest = new File(mcDataDir, file.getName());
+ try (
+ InputStream i = Files.newInputStream(file.toPath());
+ OutputStream o = Files.newOutputStream(dest.toPath());
+ ) {
+ IOUtils.copyLarge(i, o);
+ } catch (IOException e) {
+ e.printStackTrace();
+ updater.logProgress(
+ "§cFailed to copy release JAR. Not making any changes to your mod folder. Consult your logs for more info.");
+ state = State.FAILED;
+ }
+ deleteFiles(toDelete);
+ if (state != State.FAILED) {
+ state = State.INSTALLED;
+ updater.logProgress("Update successful. Thank you for your time.");
+ return;
+ }
+ updater.logProgress("§cFailure to delete some files. Please delte the old NEU version manually from your mods folder.");
+ }
+
+ public abstract void deleteFiles(List<File> toDelete);
+
+}