/*
* Copyright (C) 2022-2023 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 .
*/
package io.github.moulberry.notenoughupdates;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import io.github.moulberry.moulconfig.observer.PropertyTypeAdapterFactory;
import io.github.moulberry.notenoughupdates.autosubscribe.AutoLoad;
import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe;
import io.github.moulberry.notenoughupdates.core.BackgroundBlur;
import io.github.moulberry.notenoughupdates.core.config.ConfigUtil;
import io.github.moulberry.notenoughupdates.cosmetics.ShaderManager;
import io.github.moulberry.notenoughupdates.listener.ChatListener;
import io.github.moulberry.notenoughupdates.listener.ItemTooltipEssenceShopListener;
import io.github.moulberry.notenoughupdates.listener.ItemTooltipListener;
import io.github.moulberry.notenoughupdates.listener.ItemTooltipRngListener;
import io.github.moulberry.notenoughupdates.listener.NEUEventListener;
import io.github.moulberry.notenoughupdates.listener.RenderListener;
import io.github.moulberry.notenoughupdates.listener.WorldListener;
import io.github.moulberry.notenoughupdates.miscfeatures.CustomSkulls;
import io.github.moulberry.notenoughupdates.miscfeatures.FairySouls;
import io.github.moulberry.notenoughupdates.miscfeatures.NPCRetexturing;
import io.github.moulberry.notenoughupdates.miscfeatures.Navigation;
import io.github.moulberry.notenoughupdates.miscfeatures.PetInfoOverlay;
import io.github.moulberry.notenoughupdates.miscfeatures.SlotLocking;
import io.github.moulberry.notenoughupdates.miscfeatures.StorageManager;
import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.CustomBlockSounds;
import io.github.moulberry.notenoughupdates.miscfeatures.inventory.MuseumCheapestItemOverlay;
import io.github.moulberry.notenoughupdates.miscfeatures.inventory.MuseumItemHighlighter;
import io.github.moulberry.notenoughupdates.miscgui.itemcustomization.ItemCustomizeManager;
import io.github.moulberry.notenoughupdates.mixins.AccessorMinecraft;
import io.github.moulberry.notenoughupdates.oneconfig.IOneConfigCompat;
import io.github.moulberry.notenoughupdates.options.NEUConfig;
import io.github.moulberry.notenoughupdates.overlays.OverlayManager;
import io.github.moulberry.notenoughupdates.profileviewer.ProfileViewer;
import io.github.moulberry.notenoughupdates.recipes.RecipeGenerator;
import io.github.moulberry.notenoughupdates.util.Utils;
import io.github.moulberry.notenoughupdates.util.brigadier.BrigadierRoot;
import io.github.moulberry.notenoughupdates.util.hypixelapi.HypixelItemAPI;
import io.github.moulberry.notenoughupdates.util.kotlin.KotlinTypeAdapterFactory;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.event.ClickEvent;
import net.minecraft.scoreboard.ScoreObjective;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.biome.BiomeGenHell;
import net.minecraft.world.biome.BiomeGenJungle;
import net.minecraft.world.biome.BiomeGenMesa;
import net.minecraft.world.biome.BiomeGenSnow;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.io.File;
import java.util.HashMap;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@NEUAutoSubscribe
@Mod(
modid = NotEnoughUpdates.MODID, version = NotEnoughUpdates.VERSION, clientSideOnly = true, useMetadata = true,
guiFactory = "io.github.moulberry.notenoughupdates.core.config.MoulConfigGuiForgeInterop")
public class NotEnoughUpdates {
public static final String MODID = "notenoughupdates";
public static final String VERSION = VersionConst.VERSION;
private static final Pattern versionPattern = Pattern.compile("([0-9]+)\\.([0-9]+)\\.([0-9]+)");
public static final int VERSION_ID = parseVersion(VERSION);
private static int parseVersion(String versionName) {
Matcher matcher = versionPattern.matcher(versionName);
if (!matcher.matches()) {
return 0;
}
int major = Integer.parseInt(matcher.group(1));
if (major < 0 || major > 99) {
return 0;
}
int minor = Integer.parseInt(matcher.group(2));
if (minor < 0 || minor > 99) {
return 0;
}
int patch = Integer.parseInt(matcher.group(3));
if (patch < 0 || patch > 99) {
return 0;
}
return major * 10000 + minor * 100 + patch;
}
public static final Logger LOGGER = LogManager.getLogger("NotEnoughUpdates");
/**
* Registers the biomes for the crystal hollows here so optifine knows they exists
*/
public static final BiomeGenBase crystalHollowsJungle =
(new BiomeGenJungle(101, true))
.setColor(5470985)
.setBiomeName("NeuCrystalHollowsJungle")
.setFillerBlockMetadata(5470985)
.setTemperatureRainfall(0.95F, 0.9F);
public static final BiomeGenBase crystalHollowsMagmaFields =
(new BiomeGenHell(102))
.setColor(16711680)
.setBiomeName("NeuCrystalHollowsMagmaFields")
.setDisableRain()
.setTemperatureRainfall(2.0F, 0.0F);
public static final BiomeGenBase crystalHollowsGoblinHoldout =
(new BiomeGenMesa(103, false, false))
.setColor(13274213)
.setBiomeName("NeuCrystalHollowsGoblinHoldout");
public static final BiomeGenBase crystalHollowsPrecursorRemnants =
(new BiomeGenMesa(104, false, true))
.setColor(11573093)
.setBiomeName("NeuCrystalHollowsPrecursorRemnants");
public static final BiomeGenBase crystalHollowsMithrilDeposit =
(new BiomeGenSnow(105, false))
.setColor(16777215)
.setBiomeName("NeuCrystalHollowsMithrilDeposits");
public static final BiomeGenBase crystalHollowsCrystalNucleus =
(new BiomeGenJungle(106, true))
.setColor(5470985)
.setBiomeName("NeuCrystalHollowsCrystalNucleus")
.setFillerBlockMetadata(5470985)
.setTemperatureRainfall(0.95F, 0.9F);
public static final BiomeGenBase smolderingTomb =
(new BiomeGenHell(107))
.setColor(16777215)
.setBiomeName("NeuSmolderingTomb");
public static final BiomeGenBase glaciteMineshaft =
(new BiomeGenSnow(108, false))
.setColor(16777215)
.setBiomeName("NeuGlaciteMineshaft");
public static final BiomeGenBase glaciteTunnels =
(new BiomeGenSnow(109, false))
.setColor(16777215)
.setBiomeName("NeuGlaciteTunnels");
private static final long CHAT_MSG_COOLDOWN = 200;
//Stolen from Biscut and used for detecting whether in skyblock
private static final Set SKYBLOCK_IN_ALL_LANGUAGES =
Sets.newHashSet("SKYBLOCK", "\u7A7A\u5C9B\u751F\u5B58", "\u7A7A\u5CF6\u751F\u5B58",
"SKIBLOCK"
); // april fools language
public static NotEnoughUpdates INSTANCE = null;
public static HashMap petRarityToColourMap = new HashMap() {{
put("UNKNOWN", EnumChatFormatting.RED.toString());
put("COMMON", EnumChatFormatting.WHITE.toString());
put("UNCOMMON", EnumChatFormatting.GREEN.toString());
put("RARE", EnumChatFormatting.BLUE.toString());
put("EPIC", EnumChatFormatting.DARK_PURPLE.toString());
put("LEGENDARY", EnumChatFormatting.GOLD.toString());
put("MYTHIC", EnumChatFormatting.LIGHT_PURPLE.toString());
}};
public static ProfileViewer profileViewer;
private final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation()
.registerTypeAdapterFactory(new PropertyTypeAdapterFactory())
.registerTypeAdapterFactory(KotlinTypeAdapterFactory.INSTANCE).create();
public NEUManager manager;
public NEUOverlay overlay;
public NEUConfig config;
public Navigation navigation = new Navigation(this);
public GuiScreen openGui = null;
public long lastOpenedGui = 0;
public boolean packDevEnabled = false;
public Color[][] colourMap = null;
private File configFile;
private long lastChatMessage = 0;
private long secondLastChatMessage = 0;
private String currChatMessage = null;
private File neuDir;
private boolean hasSkyblockScoreboard;
public NotEnoughUpdates() {
// Budget Construction Event
((AccessorMinecraft) FMLClientHandler.instance().getClient())
.onGetDefaultResourcePacks()
.add(new NEURepoResourcePack(null, "neurepo"));
}
public File getConfigFile() {
return this.configFile;
}
public void newConfigFile() {
this.configFile = new File(NotEnoughUpdates.INSTANCE.getNeuDir(), "configNew.json");
}
public File getNeuDir() {
return this.neuDir;
}
/**
* Instantiates NEUIo, NEUManager and NEUOverlay instances. Registers keybinds and adds a shutdown hook to clear tmp folder.
*/
@EventHandler
public void preinit(FMLPreInitializationEvent event) {
INSTANCE = this;
neuDir = new File(event.getModConfigurationDirectory(), "notenoughupdates");
neuDir.mkdirs();
configFile = new File(neuDir, "configNew.json");
if (configFile.exists()) {
config = ConfigUtil.loadConfig(NEUConfig.class, configFile, gson);
}
ItemCustomizeManager.loadCustomization(new File(neuDir, "itemCustomization.json"));
StorageManager.getInstance().loadConfig(new File(neuDir, "storageItems.json"));
FairySouls.getInstance().loadFoundSoulsForAllProfiles(new File(neuDir, "collected_fairy_souls.json"), gson);
PetInfoOverlay.loadConfig(new File(neuDir, "petCache.json"));
SlotLocking.getInstance().loadConfig(new File(neuDir, "slotLocking.json"));
ItemPriceInformation.init(new File(neuDir, "auctionable_items.json"), gson);
if (config == null) {
config = new NEUConfig();
saveConfig();
} else {
//add the trophy fishing tab to the config
if (config.profileViewer.pageLayout.size() == 8) {
config.profileViewer.pageLayout.add(8);
}
if (config.profileViewer.pageLayout.size() == 9) {
config.profileViewer.pageLayout.add(9);
}
if (config.profileViewer.pageLayout.size() == 10) {
config.profileViewer.pageLayout.add(10);
}
if (config.profileViewer.pageLayout.size() == 11) {
config.profileViewer.pageLayout.add(11);
}
if (config.profileViewer.pageLayout.size() == 12) {
config.profileViewer.pageLayout.add(12);
}
if (config.profileViewer.pageLayout.size() == 13) {
config.profileViewer.pageLayout.add(13);
}
if ((config.apiData.repoUser.isEmpty() || config.apiData.repoName.isEmpty() || config.apiData.repoBranch.isEmpty()) && config.apiData.autoupdate_new) {
config.apiData.repoUser = "NotEnoughUpdates";
config.apiData.repoName = "NotEnoughUpdates-REPO";
config.apiData.repoBranch = "master";
}
// When this is changed next, also change it in the build gradle
if ("prerelease".equals(config.apiData.repoBranch)) {
config.apiData.repoBranch = "master";
}
if (config.apiData.moulberryCodesApi.isEmpty()) {
config.apiData.moulberryCodesApi = "moulberry.codes";
}
if (config.ahGraph.serverUrl.trim().isEmpty()) {
config.ahGraph.serverUrl = "pricehistory.notenoughupdates.org";
}
saveConfig();
}
if (config != null)
if (config.mining.powderGrindingTrackerResetMode == 2)
OverlayManager.powderGrindingOverlay.load();
IOneConfigCompat.getInstance().ifPresent(it -> it.initConfig(config));
MinecraftForge.EVENT_BUS.register(new NEUEventListener(this));
MinecraftForge.EVENT_BUS.register(new RecipeGenerator(this));
MinecraftForge.EVENT_BUS.register(OverlayManager.petInfoOverlay);
MinecraftForge.EVENT_BUS.register(OverlayManager.timersOverlay);
MinecraftForge.EVENT_BUS.register(new ChatListener(this));
MinecraftForge.EVENT_BUS.register(new ItemTooltipListener(this));
MinecraftForge.EVENT_BUS.register(new ItemTooltipRngListener(this));
MinecraftForge.EVENT_BUS.register(new ItemTooltipEssenceShopListener(this));
MinecraftForge.EVENT_BUS.register(new RenderListener(this));
MinecraftForge.EVENT_BUS.register(navigation);
MinecraftForge.EVENT_BUS.register(new WorldListener(this));
AutoLoad.INSTANCE.provide(supplier -> MinecraftForge.EVENT_BUS.register(supplier.get()));
MinecraftForge.EVENT_BUS.register(MuseumItemHighlighter.INSTANCE);
MinecraftForge.EVENT_BUS.register(MuseumCheapestItemOverlay.INSTANCE);
if (Minecraft.getMinecraft().getResourceManager() instanceof IReloadableResourceManager) {
IReloadableResourceManager manager = (IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager();
manager.registerReloadListener(CustomSkulls.getInstance());
manager.registerReloadListener(NPCRetexturing.getInstance());
manager.registerReloadListener(ShaderManager.getInstance());
manager.registerReloadListener(new ItemCustomizeManager.ReloadListener());
manager.registerReloadListener(new CustomBlockSounds.ReloaderListener());
}
BrigadierRoot.INSTANCE.updateHooks();
BackgroundBlur.registerListener();
manager = new NEUManager(this, neuDir);
manager.loadItemInformation();
overlay = new NEUOverlay(manager);
profileViewer = new ProfileViewer(manager);
HypixelItemAPI.INSTANCE.loadItemData();
for (KeyBinding kb : manager.keybinds) {
ClientRegistry.registerKeyBinding(kb);
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
File tmp = new File(neuDir, "tmp");
if (tmp.exists()) {
for (File tmpFile : tmp.listFiles()) {
tmpFile.delete();
}
tmp.delete();
}
}));
}
public void saveConfig() {
try {
OverlayManager.powderGrindingOverlay.save();
} catch (Exception ignored) {
}
ConfigUtil.saveConfig(config, configFile, gson);
ItemCustomizeManager.saveCustomization(new File(neuDir, "itemCustomization.json"));
StorageManager.getInstance().saveConfig(new File(neuDir, "storageItems.json"));
FairySouls.getInstance().saveFoundSoulsForAllProfiles(new File(neuDir, "collected_fairy_souls.json"), gson);
PetInfoOverlay.saveConfig(new File(neuDir, "petCache.json"));
SlotLocking.getInstance().saveConfig(new File(neuDir, "slotLocking.json"));
}
/**
* If the last chat messages was sent >200ms ago, sends the message.
* If the last chat message was sent <200 ago, will cache the message for #onTick to handle.
*/
public void sendChatMessage(String message) {
if (System.currentTimeMillis() - lastChatMessage > CHAT_MSG_COOLDOWN) {
secondLastChatMessage = lastChatMessage;
lastChatMessage = System.currentTimeMillis();
Minecraft.getMinecraft().thePlayer.sendChatMessage(message);
currChatMessage = null;
} else {
currChatMessage = message;
}
}
public void trySendCommand(String message) {
if (ClientCommandHandler.instance.executeCommand(Minecraft.getMinecraft().thePlayer, message) == 0) {
sendChatMessage(message);
}
}
public void displayLinks(JsonObject update, int totalWidth) {
String discord_link = update.get("discord_link").getAsString();
String youtube_link = update.get("youtube_link").getAsString();
String twitch_link = update.get("twitch_link").getAsString();
String github_link = update.get("github_link").getAsString();
String other_text = update.get("other_text").getAsString();
String other_link = update.get("other_link").getAsString();
ChatComponentText other = null;
if (other_text.length() > 0) {
other = new ChatComponentText(
EnumChatFormatting.GRAY + "[" + EnumChatFormatting.BLUE + other_text + EnumChatFormatting.GRAY + "]");
other.setChatStyle(Utils.createClickStyle(ClickEvent.Action.OPEN_URL, other_link));
}
ChatComponentText links = new ChatComponentText("");
ChatComponentText separator = new ChatComponentText(
EnumChatFormatting.GRAY + EnumChatFormatting.BOLD.toString() + EnumChatFormatting.STRIKETHROUGH + "-");
ChatComponentText discord = new ChatComponentText(
EnumChatFormatting.GRAY + "[" + EnumChatFormatting.BLUE + "Discord" + EnumChatFormatting.GRAY + "]");
discord.setChatStyle(Utils.createClickStyle(ClickEvent.Action.OPEN_URL, discord_link));
ChatComponentText youtube = new ChatComponentText(
EnumChatFormatting.GRAY + "[" + EnumChatFormatting.RED + "YouTube" + EnumChatFormatting.GRAY + "]");
youtube.setChatStyle(Utils.createClickStyle(ClickEvent.Action.OPEN_URL, youtube_link));
ChatComponentText twitch = new ChatComponentText(
EnumChatFormatting.GRAY + "[" + EnumChatFormatting.DARK_PURPLE + "Twitch" + EnumChatFormatting.GRAY + "]");
twitch.setChatStyle(Utils.createClickStyle(ClickEvent.Action.OPEN_URL, twitch_link));
ChatComponentText github = new ChatComponentText(
EnumChatFormatting.GRAY + "[" + EnumChatFormatting.DARK_PURPLE + "GitHub" + EnumChatFormatting.GRAY + "]");
github.setChatStyle(Utils.createClickStyle(ClickEvent.Action.OPEN_URL, github_link));
links.appendSibling(separator);
links.appendSibling(discord);
links.appendSibling(separator);
links.appendSibling(youtube);
links.appendSibling(separator);
links.appendSibling(twitch);
links.appendSibling(separator);
links.appendSibling(github);
links.appendSibling(separator);
if (other != null) {
links.appendSibling(other);
links.appendSibling(separator);
}
FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
int missingWidth = Math.max(0, totalWidth - fr.getStringWidth(links.getFormattedText()));
int missingCharsOnEitherSide = missingWidth / fr.getStringWidth(EnumChatFormatting.BOLD + "-") / 2;
StringBuilder sb = new StringBuilder(missingCharsOnEitherSide + 6);
sb.append(EnumChatFormatting.GRAY);
sb.append(EnumChatFormatting.BOLD);
sb.append(EnumChatFormatting.STRIKETHROUGH);
for (int i = 0; i < missingCharsOnEitherSide; i++) {
sb.append("-");
}
String padding = sb.toString();
ChatComponentText cp = new ChatComponentText("");
cp.appendText(padding);
cp.appendSibling(links);
cp.appendText(padding);
Minecraft.getMinecraft().thePlayer.addChatMessage(cp);
}
@SubscribeEvent
public void onTick(TickEvent.ClientTickEvent event) {
if (event.phase != TickEvent.Phase.START) return;
if (Minecraft.getMinecraft().thePlayer == null) {
openGui = null;
currChatMessage = null;
return;
}
long currentTime = System.currentTimeMillis();
if (openGui != null) {
if (Minecraft.getMinecraft().thePlayer.openContainer != null) {
Minecraft.getMinecraft().thePlayer.closeScreen();
}
Minecraft.getMinecraft().displayGuiScreen(openGui);
openGui = null;
lastOpenedGui = System.currentTimeMillis();
}
if (currChatMessage != null && currentTime - lastChatMessage > CHAT_MSG_COOLDOWN) {
lastChatMessage = currentTime;
Minecraft.getMinecraft().thePlayer.sendChatMessage(currChatMessage);
currChatMessage = null;
}
}
public boolean isOnSkyblock() {
if (!config.misc.onlyShowOnSkyblock) return true;
return hasSkyblockScoreboard();
}
public boolean hasSkyblockScoreboard() {
return hasSkyblockScoreboard;
}
public void updateSkyblockScoreboard() {
Minecraft mc = Minecraft.getMinecraft();
if (mc != null && mc.theWorld != null && mc.thePlayer != null) {
if (mc.isSingleplayer() || mc.thePlayer.getClientBrand() == null ||
!mc.thePlayer.getClientBrand().toLowerCase(Locale.ROOT).contains("hypixel")) {
hasSkyblockScoreboard = false;
return;
}
Scoreboard scoreboard = mc.theWorld.getScoreboard();
ScoreObjective sidebarObjective = scoreboard.getObjectiveInDisplaySlot(1);
if (sidebarObjective != null) {
String objectiveName = sidebarObjective.getDisplayName().replaceAll("(?i)\\u00A7.", "");
for (String skyblock : SKYBLOCK_IN_ALL_LANGUAGES) {
if (objectiveName.startsWith(skyblock)) {
hasSkyblockScoreboard = true;
return;
}
}
}
hasSkyblockScoreboard = false;
}
}
}