/*
* 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 .
*/
package io.github.moulberry.notenoughupdates.auction;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.github.moulberry.notenoughupdates.ItemPriceInformation;
import io.github.moulberry.notenoughupdates.NEUManager;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.miscgui.GuiPriceGraph;
import io.github.moulberry.notenoughupdates.recipes.Ingredient;
import io.github.moulberry.notenoughupdates.recipes.NeuRecipe;
import io.github.moulberry.notenoughupdates.util.Constants;
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.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatStyle;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.ResourceLocation;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public class APIManager {
private final NEUManager manager;
public final CustomAH customAH;
private final TreeMap auctionMap = new TreeMap<>();
public HashMap> internalnameToAucIdMap = new HashMap<>();
private final HashSet playerBids = new HashSet<>();
private final HashSet playerBidsNotified = new HashSet<>();
private final HashSet playerBidsFinishedNotified = new HashSet<>();
private final int LOWEST_BIN_UPDATE_INTERVAL = 2 * 60 * 1000; // 2 minutes
private final int AUCTION_AVG_UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes
private final int BAZAAR_UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes
private JsonObject lowestBins = null;
private JsonObject auctionPricesAvgLowestBinJson = null;
private LinkedList pagesToDownload = null;
private JsonObject bazaarJson = null;
private JsonObject auctionPricesJson = null;
private final HashMap craftCost = new HashMap<>();
public TreeMap>> extrasToAucIdMap = new TreeMap<>();
private boolean didFirstUpdate = false;
private long lastAuctionUpdate = 0;
private long lastShortAuctionUpdate = 0;
private long lastCustomAHSearch = 0;
private long lastCleanup = 0;
private long lastAuctionAvgUpdate = 0;
private long lastBazaarUpdate = 0;
private long lastLowestBinUpdate = 0;
private long lastApiUpdate = 0;
private long firstHypixelApiUpdate = 0;
public int activeAuctions = 0;
public int uniqueItems = 0;
public int totalTags = 0;
public int internalnameTaggedAuctions = 0;
public int taggedAuctions = 0;
public int processMillis = 0;
public APIManager(NEUManager manager) {
this.manager = manager;
customAH = new CustomAH(manager);
}
public TreeMap getAuctionItems() {
return auctionMap;
}
public HashSet getPlayerBids() {
return playerBids;
}
public HashSet getAuctionsForInternalname(String internalname) {
return internalnameToAucIdMap.computeIfAbsent(internalname, k -> new HashSet<>());
}
public class Auction {
public String auctioneerUuid;
public long end;
public int starting_bid;
public int highest_bid_amount;
public int bid_count;
public boolean bin;
public String category;
public String rarity;
public int dungeonTier;
public String item_tag_str;
public NBTTagCompound item_tag = null;
private ItemStack stack;
public int enchLevel = 0; //0 = clean, 1 = ench, 2 = ench/hpb
public Auction(
String auctioneerUuid, long end, int starting_bid, int highest_bid_amount, int bid_count,
boolean bin, String category, String rarity, int dungeonTier, String item_tag_str
) {
this.auctioneerUuid = auctioneerUuid;
this.end = end;
this.starting_bid = starting_bid;
this.highest_bid_amount = highest_bid_amount;
this.bid_count = bid_count;
this.bin = bin;
this.category = category;
this.dungeonTier = dungeonTier;
this.rarity = rarity;
this.item_tag_str = item_tag_str;
}
public ItemStack getStack() {
if (item_tag == null && item_tag_str != null) {
try {
item_tag = CompressedStreamTools.readCompressed(
new ByteArrayInputStream(Base64.getDecoder().decode(item_tag_str)));
item_tag_str = null;
} catch (IOException e) {
return null;
}
}
if (stack != null) {
return stack;
} else {
JsonObject item = manager.getJsonFromNBT(item_tag);
ItemStack stack = manager.jsonToStack(item, false);
JsonObject itemDefault = manager.getItemInformation().get(item.get("internalname").getAsString());
if (stack != null && itemDefault != null) {
ItemStack stackDefault = manager.jsonToStack(itemDefault, true);
if (stack.isItemEqual(stackDefault)) {
//Item types are the same, compare lore
String[] stackLore = manager.getLoreFromNBT(stack.getTagCompound());
String[] defaultLore = manager.getLoreFromNBT(stackDefault.getTagCompound());
boolean loreMatches = stackLore != null && defaultLore != null && stackLore.length == defaultLore.length;
if (loreMatches) {
for (int i = 0; i < stackLore.length; i++) {
if (!stackLore[i].equals(defaultLore[i])) {
loreMatches = false;
break;
}
}
}
if (loreMatches) {
stack = stackDefault;
}
}
}
this.stack = stack;
return stack;
}
}
}
public void markNeedsUpdate() {
firstHypixelApiUpdate = 0;
pagesToDownload = null;
auctionMap.clear();
internalnameToAucIdMap.clear();
extrasToAucIdMap.clear();
}
public void tick() {
customAH.tick();
long currentTime = System.currentTimeMillis();
if (NotEnoughUpdates.INSTANCE.config.neuAuctionHouse.enableNeuAuctionHouse &&
NotEnoughUpdates.INSTANCE.config.apiData.apiKey != null &&
!NotEnoughUpdates.INSTANCE.config.apiData.apiKey.isEmpty()) {
if (currentTime - lastAuctionUpdate > 60 * 1000) {
lastAuctionUpdate = currentTime;
updatePageTick();
}
if (currentTime - lastShortAuctionUpdate > 10 * 1000) {
lastShortAuctionUpdate = currentTime;
updatePageTickShort();
ahNotification();
}
if (currentTime - lastCleanup > 60 * 1000) {
lastCleanup = currentTime;
cleanup();
}
if (currentTime - lastCustomAHSearch > 60 * 1000) {
lastCustomAHSearch = currentTime;
if (Minecraft.getMinecraft().currentScreen instanceof CustomAHGui || customAH.isRenderOverAuctionView()) {
customAH.updateSearch();
calculateStats();
}
}
}
if (currentTime - lastAuctionAvgUpdate > AUCTION_AVG_UPDATE_INTERVAL) {
lastAuctionAvgUpdate = currentTime - AUCTION_AVG_UPDATE_INTERVAL + 60 * 1000; // Try again in 1 minute on failure
updateAvgPrices();
}
if (currentTime - lastBazaarUpdate > BAZAAR_UPDATE_INTERVAL) {
lastBazaarUpdate = currentTime - BAZAAR_UPDATE_INTERVAL + 60 * 1000; // Try again in 1 minute on failure
updateBazaar();
}
if (currentTime - lastLowestBinUpdate > LOWEST_BIN_UPDATE_INTERVAL) {
lastLowestBinUpdate = currentTime - LOWEST_BIN_UPDATE_INTERVAL + 30 * 1000; // Try again in 30 seconds on failure
updateLowestBin();
}
}
private String niceAucId(String aucId) {
if (aucId.length() != 32) return aucId;
StringBuilder niceAucId = new StringBuilder();
niceAucId.append(aucId, 0, 8);
niceAucId.append("-");
niceAucId.append(aucId, 8, 12);
niceAucId.append("-");
niceAucId.append(aucId, 12, 16);
niceAucId.append("-");
niceAucId.append(aucId, 16, 20);
niceAucId.append("-");
niceAucId.append(aucId, 20, 32);
return niceAucId.toString();
}
public Set getLowestBinKeySet() {
if (lowestBins == null) return new HashSet<>();
HashSet keys = new HashSet<>();
for (Map.Entry entry : lowestBins.entrySet()) {
keys.add(entry.getKey());
}
return keys;
}
public long getLowestBin(String internalName) {
if (lowestBins != null && lowestBins.has(internalName)) {
JsonElement e = lowestBins.get(internalName);
if (e.isJsonPrimitive() && e.getAsJsonPrimitive().isNumber()) {
return e.getAsBigDecimal().longValue();
}
}
return -1;
}
public void updateLowestBin() {
manager.hypixelApi.getMyApiGZIPAsync("lowestbin.json.gz", (jsonObject) -> {
if (lowestBins == null) {
lowestBins = new JsonObject();
}
if (!jsonObject.entrySet().isEmpty()) {
lastLowestBinUpdate = System.currentTimeMillis();
}
for (Map.Entry entry : jsonObject.entrySet()) {
lowestBins.add(entry.getKey(), entry.getValue());
}
if (!didFirstUpdate) {
ItemPriceInformation.updateAuctionableItemsList();
didFirstUpdate = true;
}
GuiPriceGraph.addToCache(lowestBins, false);
}, () -> {
});
}
private void ahNotification() {
playerBidsNotified.retainAll(playerBids);
playerBidsFinishedNotified.retainAll(playerBids);
if (NotEnoughUpdates.INSTANCE.config.neuAuctionHouse.ahNotification <= 0) {
return;
}
for (String aucid : playerBids) {
Auction auc = auctionMap.get(aucid);
if (!playerBidsNotified.contains(aucid)) {
if (auc != null &&
auc.end - System.currentTimeMillis() <
1000 * 60 * NotEnoughUpdates.INSTANCE.config.neuAuctionHouse.ahNotification) {
ChatComponentText message = new ChatComponentText(
EnumChatFormatting.YELLOW +
"The " +
auc.getStack().getDisplayName() +
EnumChatFormatting.YELLOW +
" you have bid on is ending soon! Click here to view.");
ChatStyle clickEvent = new ChatStyle().setChatClickEvent(
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/viewauction " + niceAucId(aucid)));
clickEvent.setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(
EnumChatFormatting.YELLOW +
"View auction")));
message.setChatStyle(clickEvent);
Minecraft.getMinecraft().thePlayer.addChatMessage(message);
playerBidsNotified.add(aucid);
}
}
if (!playerBidsFinishedNotified.contains(aucid)) {
if (auc != null && auc.end < System.currentTimeMillis()) {
ChatComponentText message = new ChatComponentText(
EnumChatFormatting.YELLOW +
"The " +
auc.getStack().getDisplayName() +
EnumChatFormatting.YELLOW +
" you have bid on (might) have ended! Click here to view.");
ChatStyle clickEvent = new ChatStyle().setChatClickEvent(
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/viewauction " + niceAucId(aucid)));
clickEvent.setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(
EnumChatFormatting.YELLOW +
"View auction")));
message.setChatStyle(clickEvent);
Minecraft.getMinecraft().thePlayer.addChatMessage(message);
playerBidsFinishedNotified.add(aucid);
}
}
}
}
public long getLastLowestBinUpdateTime() {
return lastLowestBinUpdate;
}
private final ExecutorService es = Executors.newSingleThreadExecutor();
private void cleanup() {
es.submit(() -> {
try {
long currTime = System.currentTimeMillis();
Set toRemove = new HashSet<>();
for (Map.Entry entry : auctionMap.entrySet()) {
long timeToEnd = entry.getValue().end - currTime;
if (timeToEnd < -120 * 1000) { //2 minutes
toRemove.add(entry.getKey());
}
}
toRemove.removeAll(playerBids);
remove(toRemove);
} catch (ConcurrentModificationException e) {
lastCleanup = System.currentTimeMillis() - 110 * 1000;
}
});
}
private void remove(Set toRemove) {
for (String aucid : toRemove) {
auctionMap.remove(aucid);
}
for (HashMap> extrasMap : extrasToAucIdMap.values()) {
for (HashSet aucids : extrasMap.values()) {
for (String aucid : toRemove) {
aucids.remove(aucid);
}
}
}
for (HashSet aucids : internalnameToAucIdMap.values()) {
aucids.removeAll(toRemove);
}
}
private void updatePageTickShort() {
if (pagesToDownload == null || pagesToDownload.isEmpty()) return;
if (firstHypixelApiUpdate == 0 || (System.currentTimeMillis() - firstHypixelApiUpdate) % (60 * 1000) > 15 * 1000)
return;
JsonObject disable = Constants.DISABLE;
if (disable != null && disable.has("auctions_new") && disable.get("auctions_new").getAsBoolean()) return;
while (!pagesToDownload.isEmpty()) {
try {
int page = pagesToDownload.pop();
getPageFromAPI(page);
} catch (NoSuchElementException ignored) {
} //Weird race condition?
}
}
private void updatePageTick() {
JsonObject disable = Constants.DISABLE;
if (disable != null && disable.has("auctions_new") && disable.get("auctions_new").getAsBoolean()) return;
if (pagesToDownload == null) {
getPageFromAPI(0);
}
Consumer process = jsonObject -> {
if (jsonObject.get("success").getAsBoolean()) {
JsonArray new_auctions = jsonObject.get("new_auctions").getAsJsonArray();
for (JsonElement auctionElement : new_auctions) {
JsonObject auction = auctionElement.getAsJsonObject();
//System.out.println("New auction " + auction);
processAuction(auction);
}
JsonArray new_bids = jsonObject.get("new_bids").getAsJsonArray();
for (JsonElement newBidElement : new_bids) {
JsonObject newBid = newBidElement.getAsJsonObject();
String newBidUUID = newBid.get("uuid").getAsString();
//System.out.println("new bid" + newBidUUID);
int newBidAmount = newBid.get("highest_bid_amount").getAsInt();
int end = newBid.get("end").getAsInt();
int bid_count = newBid.get("bid_count").getAsInt();
Auction auc = auctionMap.get(newBidUUID);
if (auc != null) {
//System.out.println("Setting auction " + newBidUUID + " price to " + newBidAmount);
auc.highest_bid_amount = newBidAmount;
auc.end = end;
auc.bid_count = bid_count;
}
}
Set toRemove = new HashSet<>();
JsonArray removed_auctions = jsonObject.get("removed_auctions").getAsJsonArray();
for (JsonElement removedAuctionsElement : removed_auctions) {
String removed = removedAuctionsElement.getAsString();
toRemove.add(removed);
}
remove(toRemove);
}
};
manager.hypixelApi.getMyApiGZIPAsync("auctionLast.json.gz", process, () ->
System.out.println("Error downloading auction from Moulberry's jank API. :("));
manager.hypixelApi.getMyApiGZIPAsync("auction.json.gz", jsonObject -> {
if (jsonObject.get("success").getAsBoolean()) {
long apiUpdate = (long) jsonObject.get("time").getAsFloat();
if (lastApiUpdate == apiUpdate) {
lastAuctionUpdate -= 30 * 1000;
}
lastApiUpdate = apiUpdate;
process.accept(jsonObject);
}
}, () -> System.out.println("Error downloading auction from Moulberry's jank API. :("));
}
public void calculateStats() {
try {
uniqueItems = internalnameToAucIdMap.size();
Set aucs = new HashSet<>();
for (HashSet aucids : internalnameToAucIdMap.values()) {
aucs.addAll(aucids);
}
internalnameTaggedAuctions = aucs.size();
totalTags = extrasToAucIdMap.size();
aucs = new HashSet<>();
for (HashMap> extrasMap : extrasToAucIdMap.values()) {
for (HashSet aucids : extrasMap.values()) {
aucs.addAll(aucids);
}
}
taggedAuctions = aucs.size();
} catch (Exception ignored) {
}
}
//String[] rarityArr = new String[] {
// "COMMON", "UNCOMMON", "RARE", "EPIC", "LEGENDARY", "MYTHIC", "SPECIAL", "VERY SPECIAL", "SUPREME",
//};
public int checkItemType(String lore, boolean contains, String... typeMatches) {
String[] split = lore.split("\n");
for (int i = split.length - 1; i >= 0; i--) {
String line = split[i];
for (String rarity : Utils.rarityArr) {
for (int j = 0; j < typeMatches.length; j++) {
if (contains) {
if (line.trim().contains(rarity + " " + typeMatches[j])) {
return j;
} else if (line.trim().contains(rarity + " DUNGEON " + typeMatches[j])) {
return j;
}
} else {
if (line.trim().endsWith(rarity + " " + typeMatches[j])) {
return j;
} else if (line.trim().endsWith(rarity + " DUNGEON " + typeMatches[j])) {
return j;
}
}
}
}
}
return -1;
}
private final String[] romans = new String[]{
"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI",
"XII", "XIII", "XIV", "XV", "XVI", "XVII", "XIX", "XX"
};
String[] categoryItemType = new String[]{
"sword", "fishingrod", "pickaxe", "axe",
"shovel", "petitem", "travelscroll", "reforgestone", "bow"
};
String playerUUID = null;
private void processAuction(JsonObject auction) {
if (playerUUID == null)
playerUUID = Minecraft.getMinecraft().thePlayer.getUniqueID().toString().replaceAll("-", "");
String auctionUuid = auction.get("uuid").getAsString();
String auctioneerUuid = auction.get("auctioneer").getAsString();
long end = auction.get("end").getAsLong();
int starting_bid = auction.get("starting_bid").getAsInt();
int highest_bid_amount = auction.get("highest_bid_amount").getAsInt();
int bid_count = auction.get("bids").getAsJsonArray().size();
boolean bin = false;
if (auction.has("bin")) {
bin = auction.get("bin").getAsBoolean();
}
String sbCategory = auction.get("category").getAsString();
String extras = auction.get("extra").getAsString().toLowerCase();
String item_name = auction.get("item_name").getAsString();
String item_lore = Utils.fixBrokenAPIColour(auction.get("item_lore").getAsString());
String item_bytes = auction.get("item_bytes").getAsString();
String rarity = auction.get("tier").getAsString();
JsonArray bids = auction.get("bids").getAsJsonArray();
try {
NBTTagCompound item_tag;
try {
item_tag = CompressedStreamTools.readCompressed(
new ByteArrayInputStream(Base64.getDecoder().decode(item_bytes)));
} catch (IOException e) {
return;
}
NBTTagCompound tag = item_tag.getTagList("i", 10).getCompoundTagAt(0).getCompoundTag("tag");
String internalname = manager.getInternalnameFromNBT(tag);
NBTTagCompound display = tag.getCompoundTag("display");
if (display.hasKey("Lore", 9)) {
NBTTagList loreList = new NBTTagList();
for (String line : item_lore.split("\n")) {
loreList.appendTag(new NBTTagString(line));
}
display.setTag("Lore", loreList);
}
tag.setTag("display", display);
item_tag.getTagList("i", 10).getCompoundTagAt(0).setTag("tag", tag);
if (tag.hasKey("ExtraAttributes", 10)) {
NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
if (ea.hasKey("enchantments", 10)) {
NBTTagCompound enchantments = ea.getCompoundTag("enchantments");
for (String key : enchantments.getKeySet()) {
String enchantname = key.toLowerCase().replace("ultimate_", "").replace("_", " ");
int enchantlevel = enchantments.getInteger(key);
String enchantLevelStr;
if (enchantlevel >= 1 && enchantlevel <= 20) {
enchantLevelStr = romans[enchantlevel - 1];
} else {
enchantLevelStr = String.valueOf(enchantlevel);
}
extras = extras.replace(enchantname, enchantname + " " + enchantLevelStr);
}
}
}
int index = 0;
for (String str : extras.split(" ")) {
str = Utils.cleanColour(str).toLowerCase();
if (str.length() > 0) {
HashMap> extrasMap = extrasToAucIdMap.computeIfAbsent(str, k -> new HashMap<>());
HashSet aucids = extrasMap.computeIfAbsent(index, k -> new HashSet<>());
aucids.add(auctionUuid);
}
index++;
}
for (int j = 0; j < bids.size(); j++) {
JsonObject bid = bids.get(j).getAsJsonObject();
if (bid.get("bidder").getAsString().equalsIgnoreCase(playerUUID)) {
playerBids.add(auctionUuid);
}
}
int dungeonTier = -1;
if (checkItemType(item_lore, true, "DUNGEON") >= 0) {
dungeonTier = 0;
for (int i = 0; i < item_name.length(); i++) {
char c = item_name.charAt(i);
if (c == 0x272A) {
dungeonTier++;
}
}
}
//Categories
String category = sbCategory;
int itemType = checkItemType(item_lore, true, "SWORD", "FISHING ROD", "PICKAXE",
"AXE", "SHOVEL", "PET ITEM", "TRAVEL SCROLL", "REFORGE STONE", "BOW"
);
if (itemType >= 0 && itemType < categoryItemType.length) {
category = categoryItemType[itemType];
}
if (category.equals("consumables") && extras.contains("enchanted book")) category = "ebook";
if (category.equals("consumables") && extras.endsWith("potion")) category = "potion";
if (category.equals("misc") && extras.contains("rune")) category = "rune";
if (category.equals("misc") && item_lore.split("\n")[0].endsWith("Furniture")) category = "furniture";
if (item_lore.split("\n")[0].endsWith("Pet") ||
item_lore.split("\n")[0].endsWith("Mount")) category = "pet";
Auction auction1 = new Auction(auctioneerUuid, end, starting_bid, highest_bid_amount,
bid_count, bin, category, rarity, dungeonTier, item_bytes
);
if (tag.hasKey("ench")) {
auction1.enchLevel = 1;
if (tag.hasKey("ExtraAttributes", 10)) {
NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
int hotpotatocount = ea.getInteger("hot_potato_count");
if (hotpotatocount == 10) {
auction1.enchLevel = 2;
}
}
}
auctionMap.put(auctionUuid, auction1);
internalnameToAucIdMap.computeIfAbsent(internalname, k -> new HashSet<>()).add(auctionUuid);
} catch (Exception e) {
e.printStackTrace();
}
}
private void getPageFromAPI(int page) {
//System.out.println("downloading page:"+page);
//System.out.println("Trying to update page: " + page);
HashMap args = new HashMap<>();
args.put("page", "" + page);
manager.hypixelApi.getHypixelApiAsync(null, "skyblock/auctions",
args, jsonObject -> {
if (jsonObject == null) return;
if (jsonObject.get("success").getAsBoolean()) {
if (pagesToDownload == null) {
int totalPages = jsonObject.get("totalPages").getAsInt();
pagesToDownload = new LinkedList<>();
for (int i = 0; i < totalPages + 2; i++) {
pagesToDownload.add(i);
}
}
if (firstHypixelApiUpdate == 0) {
firstHypixelApiUpdate = jsonObject.get("lastUpdated").getAsLong();
}
activeAuctions = jsonObject.get("totalAuctions").getAsInt();
long startProcess = System.currentTimeMillis();
JsonArray auctions = jsonObject.get("auctions").getAsJsonArray();
for (int i = 0; i < auctions.size(); i++) {
JsonObject auction = auctions.get(i).getAsJsonObject();
processAuction(auction);
}
processMillis = (int) (System.currentTimeMillis() - startProcess);
} else {
pagesToDownload.addLast(page);
}
}, () -> pagesToDownload.addLast(page)
);
}
public void updateBazaar() {
manager.hypixelApi.getHypixelApiAsync(
NotEnoughUpdates.INSTANCE.config.apiData.apiKey,
"skyblock/bazaar",
new HashMap<>(),
(jsonObject) -> {
if (!jsonObject.get("success").getAsBoolean()) return;
craftCost.clear();
bazaarJson = new JsonObject();
JsonObject products = jsonObject.get("products").getAsJsonObject();
for (Map.Entry entry : products.entrySet()) {
if (entry.getValue().isJsonObject()) {
JsonObject productInfo = new JsonObject();
JsonObject product = entry.getValue().getAsJsonObject();
JsonObject quickStatus = product.get("quick_status").getAsJsonObject();
productInfo.addProperty("avg_buy", quickStatus.get("buyPrice").getAsFloat());
productInfo.addProperty("avg_sell", quickStatus.get("sellPrice").getAsFloat());
for (JsonElement element : product.get("sell_summary").getAsJsonArray()) {
if (element.isJsonObject()) {
JsonObject sellSummaryFirst = element.getAsJsonObject();
productInfo.addProperty("curr_sell", sellSummaryFirst.get("pricePerUnit").getAsFloat());
break;
}
}
for (JsonElement element : product.get("buy_summary").getAsJsonArray()) {
if (element.isJsonObject()) {
JsonObject sellSummaryFirst = element.getAsJsonObject();
productInfo.addProperty("curr_buy", sellSummaryFirst.get("pricePerUnit").getAsFloat());
break;
}
}
bazaarJson.add(entry.getKey().replace(":", "-"), productInfo);
}
}
GuiPriceGraph.addToCache(bazaarJson, true);
}
);
}
public void updateAvgPrices() {
manager.hypixelApi.getMyApiGZIPAsync("auction_averages/3day.json.gz", (jsonObject) -> {
craftCost.clear();
auctionPricesJson = jsonObject;
lastAuctionAvgUpdate = System.currentTimeMillis();
}, () -> {
});
manager.hypixelApi.getMyApiGZIPAsync("auction_averages_lbin/1day.json.gz", (jsonObject) ->
auctionPricesAvgLowestBinJson = jsonObject, () -> {
});
}
public Set getItemAuctionInfoKeySet() {
if (auctionPricesJson == null) return new HashSet<>();
HashSet keys = new HashSet<>();
for (Map.Entry entry : auctionPricesJson.entrySet()) {
keys.add(entry.getKey());
}
return keys;
}
public JsonObject getItemAuctionInfo(String internalname) {
if (auctionPricesJson == null) return null;
JsonElement e = auctionPricesJson.get(internalname);
if (e == null) {
return null;
}
return e.getAsJsonObject();
}
public double getItemAvgBin(String internalName) {
if (auctionPricesAvgLowestBinJson == null) return -1;
JsonElement e = auctionPricesAvgLowestBinJson.get(internalName);
if (e == null) {
return -1;
}
return Math.round(e.getAsDouble());
}
public Set getBazaarKeySet() {
if (bazaarJson == null) return new HashSet<>();
HashSet keys = new HashSet<>();
for (Map.Entry entry : bazaarJson.entrySet()) {
keys.add(entry.getKey());
}
return keys;
}
public JsonObject getBazaarInfo(String internalname) {
if (bazaarJson == null) return null;
JsonElement e = bazaarJson.get(internalname);
if (e == null) {
return null;
}
return e.getAsJsonObject();
}
private static final List hardcodedVanillaItems = Utils.createList(
"WOOD_AXE", "WOOD_HOE", "WOOD_PICKAXE", "WOOD_SPADE", "WOOD_SWORD",
"GOLD_AXE", "GOLD_HOE", "GOLD_PICKAXE", "GOLD_SPADE", "GOLD_SWORD",
"ROOKIE_HOE"
);
public boolean isVanillaItem(String internalname) {
if (hardcodedVanillaItems.contains(internalname)) return true;
//Removes trailing numbers and underscores, eg. LEAVES_2-3 -> LEAVES
String vanillaName = internalname.split("-")[0];
if (manager.getItemInformation().containsKey(vanillaName)) {
JsonObject json = manager.getItemInformation().get(vanillaName);
if (json != null && json.has("vanilla") && json.get("vanilla").getAsBoolean()) return true;
}
return Item.itemRegistry.getObject(new ResourceLocation(vanillaName)) != null;
}
public static class CraftInfo {
public boolean fromRecipe = false;
public boolean vanillaItem = false;
public double craftCost = -1;
}
public CraftInfo getCraftCost(String internalname) {
return getCraftCost(internalname, new HashSet<>());
}
/**
* Recursively calculates the cost of crafting an item from raw materials.
*/
private CraftInfo getCraftCost(String internalname, Set visited) {
if (craftCost.containsKey(internalname)) return craftCost.get(internalname);
if (visited.contains(internalname)) return null;
visited.add(internalname);
boolean vanillaItem = isVanillaItem(internalname);
double craftCost = Double.POSITIVE_INFINITY;
JsonObject auctionInfo = getItemAuctionInfo(internalname);
double lowestBin = getLowestBin(internalname);
JsonObject bazaarInfo = getBazaarInfo(internalname);
if (bazaarInfo != null && bazaarInfo.get("curr_buy") != null) {
craftCost = bazaarInfo.get("curr_buy").getAsFloat();
}
//Don't use auction prices for vanilla items cuz people like to transfer money, messing up the cost of vanilla items.
if (!vanillaItem) {
if (lowestBin > 0) {
craftCost = Math.min(lowestBin, craftCost);
} else if (auctionInfo != null) {
float auctionPrice = auctionInfo.get("price").getAsFloat() / auctionInfo.get("count").getAsInt();
craftCost = Math.min(auctionPrice, craftCost);
}
}
Set recipes = manager.getRecipesFor(internalname);
boolean fromRecipe = false;
if (recipes != null)
RECIPE_ITER:
for (NeuRecipe recipe : recipes) {
if (recipe.hasVariableCost() || !recipe.shouldUseForCraftCost()) continue;
float craftPrice = 0;
for (Ingredient i : recipe.getIngredients()) {
if (i.isCoins()) {
craftPrice += i.getCount();
continue;
}
CraftInfo ingredientCraftCost = getCraftCost(i.getInternalItemId(), visited);
if (ingredientCraftCost == null)
continue RECIPE_ITER; // Skip recipes with items further up the chain
craftPrice += ingredientCraftCost.craftCost * i.getCount();
}
int resultCount = 0;
for (Ingredient item : recipe.getOutputs())
if (item.getInternalItemId().equals(internalname))
resultCount += item.getCount();
if (resultCount == 0)
continue;
float craftPricePer = craftPrice / resultCount;
if (craftPricePer < craftCost) {
fromRecipe = true;
craftCost = craftPricePer;
}
}
visited.remove(internalname);
if (Double.isInfinite(craftCost)) {
return null;
}
CraftInfo craftInfo = new CraftInfo();
craftInfo.vanillaItem = vanillaItem;
craftInfo.craftCost = craftCost;
craftInfo.fromRecipe = fromRecipe;
this.craftCost.put(internalname, craftInfo);
return craftInfo;
}
/**
* Calculates the cost of enchants + other price modifiers such as pet xp, midas price, etc.
*/
public float getCostOfEnchants(String internalname, NBTTagCompound tag) {
float costOfEnchants = 0;
if (true) return 0;
JsonObject info = getItemAuctionInfo(internalname);
if (info == null || !info.has("price")) {
return 0;
}
if (auctionPricesJson == null || !auctionPricesJson.has("ench_prices") || !auctionPricesJson.has("ench_maximums")) {
return 0;
}
JsonObject ench_prices = auctionPricesJson.getAsJsonObject("ench_prices");
JsonObject ench_maximums = auctionPricesJson.getAsJsonObject("ench_maximums");
if (!ench_prices.has(internalname) || !ench_maximums.has(internalname)) {
return 0;
}
JsonObject iid_variables = ench_prices.getAsJsonObject(internalname);
float ench_maximum = ench_maximums.get(internalname).getAsFloat();
int enchants = 0;
float price = getItemAuctionInfo(internalname).get("price").getAsFloat();
if (tag.hasKey("ExtraAttributes")) {
NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
if (ea.hasKey("enchantments")) {
NBTTagCompound enchs = ea.getCompoundTag("enchantments");
for (String ench : enchs.getKeySet()) {
enchants++;
int level = enchs.getInteger(ench);
for (Map.Entry entry : iid_variables.entrySet()) {
if (matchEnch(ench, level, entry.getKey())) {
costOfEnchants += entry.getValue().getAsJsonObject().get("A").getAsFloat() * price +
entry.getValue().getAsJsonObject().get("B").getAsFloat();
break;
}
}
}
}
}
return costOfEnchants;
}
/**
* Checks whether a certain enchant (ench name + lvl) matches an enchant id
* eg. PROTECTION_GE6 will match -> ench_name = PROTECTION, lvl >= 6
*/
private boolean matchEnch(String ench, int level, String id) {
if (!id.contains(":")) {
return false;
}
String idEnch = id.split(":")[0];
String idLevel = id.split(":")[1];
if (!ench.equalsIgnoreCase(idEnch)) {
return false;
}
if (String.valueOf(level).equalsIgnoreCase(idLevel)) {
return true;
}
if (idLevel.startsWith("LE")) {
int idLevelI = Integer.parseInt(idLevel.substring(2));
return level <= idLevelI;
} else if (idLevel.startsWith("GE")) {
int idLevelI = Integer.parseInt(idLevel.substring(2));
return level >= idLevelI;
}
return false;
}
/*ScheduledExecutorService auctionUpdateSES = Executors.newSingleThreadScheduledExecutor();
private AtomicInteger auctionUpdateId = new AtomicInteger(0);
public void updateAuctions() {
HashMap pages = new HashMap<>();
HashMap args = new HashMap<>();
args.put("page", "0");
AtomicInteger totalPages = new AtomicInteger(1);
AtomicInteger currentPages = new AtomicInteger(0);
manager.hypixelApi.getHypixelApiAsync(NotEnoughUpdates.INSTANCE.config.apiKey.apiKey, "skyblock/auctions",
args, jsonObject -> {
if (jsonObject.get("success").getAsBoolean()) {
pages.put(0, jsonObject);
totalPages.set(jsonObject.get("totalPages").getAsInt());
currentPages.incrementAndGet();
for (int i = 1; i < totalPages.get(); i++) {
int j = i;
HashMap args2 = new HashMap<>();
args2.put("page", "" + i);
manager.hypixelApi.getHypixelApiAsync(NotEnoughUpdates.INSTANCE.config.apiKey.apiKey, "skyblock/auctions",
args2, jsonObject2 -> {
if (jsonObject2.get("success").getAsBoolean()) {
pages.put(j, jsonObject2);
currentPages.incrementAndGet();
} else {
currentPages.incrementAndGet();
}
}
);
}
}
}
);
long startTime = System.currentTimeMillis();
int currentAuctionUpdateId = auctionUpdateId.incrementAndGet();
auctionUpdateSES.schedule(new Runnable() {
public void run() {
if(auctionUpdateId.get() != currentAuctionUpdateId) return;
System.out.println(currentPages.get() + "/" + totalPages.get());
if (System.currentTimeMillis() - startTime > 20000) return;
if (currentPages.get() == totalPages.get()) {
TreeMap auctionItemsTemp = new TreeMap<>();
for (int pageNum : pages.keySet()) {
System.out.println(pageNum + "/" + pages.size());
JsonObject page = pages.get(pageNum);
JsonArray auctions = page.get("auctions").getAsJsonArray();
for (int i = 0; i < auctions.size(); i++) {
JsonObject auction = auctions.get(i).getAsJsonObject();
String auctionUuid = auction.get("uuid").getAsString();
String auctioneerUuid = auction.get("auctioneer").getAsString();
long end = auction.get("end").getAsLong();
int starting_bid = auction.get("starting_bid").getAsInt();
int highest_bid_amount = auction.get("highest_bid_amount").getAsInt();
int bid_count = auction.get("bids").getAsJsonArray().size();
boolean bin = false;
if(auction.has("bin")) {
bin = auction.get("bin").getAsBoolean();
}
String category = auction.get("category").getAsString();
String extras = auction.get("item_lore").getAsString().replaceAll("\n"," ") + " " +
auction.get("extra").getAsString();
String item_bytes = auction.get("item_bytes").getAsString();
auctionItemsTemp.put(auctionUuid, new Auction(auctioneerUuid, end, starting_bid, highest_bid_amount,
bid_count, bin, category, extras, item_bytes));
}
}
auctionItems = auctionItemsTemp;
customAH.updateSearch();
return;
}
auctionUpdateSES.schedule(this, 1000L, TimeUnit.MILLISECONDS);
}
}, 3000L, TimeUnit.MILLISECONDS);
}*/
}