Skip to content

Commit

Permalink
Merge pull request #389 from AzureAaron/museum-cache
Browse files Browse the repository at this point in the history
Museum Item Cache
  • Loading branch information
kevinthegreat1 authored Oct 28, 2023
2 parents 158782d + 1cbf455 commit 9edc484
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 26 deletions.
5 changes: 0 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ dependencies {
modCompileOnly "dev.emi:emi-fabric:${project.emi_version}:api"
modLocalRuntime "dev.emi:emi-fabric:${project.emi_version}"

// Renderer (https://github.com/0x3C50/Renderer)
include modImplementation("com.github.0x3C50:Renderer:${project.renderer_version}") {
exclude group: "io.github.ladysnake" exclude module: "satin"
}

include modImplementation("meteordevelopment:discord-ipc:1.1")

// Mixin Extras (https://github.com/LlamaLad7/MixinExtras)
Expand Down
2 changes: 0 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ mod_menu_version = 8.0.0
rei_version = 13.0.666
## EMI (https://modrinth.com/mod/emi/versions)
emi_version = 1.0.22+1.20.2
## Renderer (https://github.com/0x3C50/Renderer)
renderer_version = master-SNAPSHOT

# Minecraft and Related Libraries
## Mixin Extras (https://github.com/LlamaLad7/MixinExtras)
Expand Down
1 change: 1 addition & 0 deletions src/main/java/de/hysky/skyblocker/SkyblockerMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public void onInitializeClient() {
ItemProtection.init();
CreeperBeams.init();
ItemRarityBackgrounds.init();
MuseumItemCache.init();
containerSolverManager.init();
statusBarTracker.init();
Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.hysky.skyblocker.skyblock;

import com.mojang.blaze3d.systems.RenderSystem;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.item.PriceInfoTooltip;
import de.hysky.skyblocker.utils.ItemUtils;
Expand Down Expand Up @@ -103,13 +102,7 @@ private static void render(WorldRenderContext wrc, BlockHitResult blockHitResult
@SuppressWarnings("DataFlowIssue")
BlockState state = client.world.getBlockState(pos);
if (!state.isAir() && client.world.getBlockState(pos.up()).isAir() && client.world.getBlockState(pos.up(2)).isAir()) {
RenderSystem.polygonOffset(-1f, -10f);
RenderSystem.enablePolygonOffset();

RenderHelper.renderFilledIfVisible(wrc, pos, COLOR_COMPONENTS, 0.5f);

RenderSystem.polygonOffset(0f, 0f);
RenderSystem.disablePolygonOffset();
}
}
}
146 changes: 146 additions & 0 deletions src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package de.hysky.skyblocker.skyblock.item;

import java.io.ByteArrayInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.mojang.util.UndashedUuid;

import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.utils.Http;
import de.hysky.skyblocker.utils.Http.ApiResponse;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtList;
import net.minecraft.util.Util;

public class MuseumItemCache {
private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class);
private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json");
private static final Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>();
private static final Type MAP_TYPE = new TypeToken<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>>>() {}.getType();

private static CompletableFuture<Void> loaded;

public static void init() {
ClientLifecycleEvents.CLIENT_STARTED.register(MuseumItemCache::load);
}

private static void load(MinecraftClient client) {
loaded = CompletableFuture.runAsync(() -> {
try (BufferedReader reader = Files.newBufferedReader(CACHE_FILE)) {
Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> cachedData = SkyblockerMod.GSON.fromJson(reader, MAP_TYPE);

MUSEUM_ITEM_CACHE.putAll(cachedData);
LOGGER.info("[Skyblocker] Loaded museum items cache");
} catch (NoSuchFileException ignored) {
} catch (IOException e) {
LOGGER.error("[Skyblocker] Failed to load cached museum items", e);
}
});
}

private static void save() {
CompletableFuture.runAsync(() -> {
try (BufferedWriter writer = Files.newBufferedWriter(CACHE_FILE)) {
SkyblockerMod.GSON.toJson(MUSEUM_ITEM_CACHE, writer);
} catch (IOException e) {
LOGGER.error("[Skyblocker] Failed to save cached museum items!", e);
}
});
}

private static void updateData4ProfileMember(String uuid, String profileId) {
CompletableFuture.runAsync(() -> {
try {
ApiResponse response = Http.sendHypixelRequest("skyblock/museum", "?profile=" + profileId);

//The request was successful
if (response.ok()) {
JsonObject profileData = JsonParser.parseString(response.content()).getAsJsonObject();
JsonObject memberData = profileData.get("members").getAsJsonObject().get(uuid).getAsJsonObject();

//We call them sets because it could either be a singular item or an entire armour set
Map<String, JsonElement> donatedSets = memberData.get("items").getAsJsonObject().asMap();

//Set of all found item ids on profile
ObjectOpenHashSet<String> itemIds = new ObjectOpenHashSet<>();

for (Map.Entry<String, JsonElement> donatedSet : donatedSets.entrySet()) {
//Item is plural here because the nbt is a list
String itemsData = donatedSet.getValue().getAsJsonObject().get("items").getAsJsonObject().get("data").getAsString();
NbtList items = NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(itemsData))).getList("i", NbtElement.COMPOUND_TYPE);

for (int i = 0; i < items.size(); i++) {
NbtCompound tag = items.getCompound(i).getCompound("tag");

if (tag.contains("ExtraAttributes")) {
NbtCompound extraAttributes = tag.getCompound("ExtraAttributes");

if (extraAttributes.contains("id")) itemIds.add(extraAttributes.getString("id"));
}
}
}

MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds));
save();

LOGGER.info("[Skyblocker] Successfully updated museum item cache for profile {}", profileId);
}
} catch (Exception e) {
LOGGER.error("[Skyblocker] Failed to refresh museum item data for profile {}", profileId, e);
}
});
}

/**
* The cache is ticked upon switching skyblock servers
*/
public static void tick(String profileId) {
if (loaded.isDone()) {
String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
Object2ObjectOpenHashMap<String, ProfileMuseumData> playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, uuid1 -> Util.make(new Object2ObjectOpenHashMap<>(), map -> {
map.put(profileId, ProfileMuseumData.EMPTY);
}));

if (playerData.get(profileId).stale()) updateData4ProfileMember(uuid, profileId);
}
}

public static boolean hasItemInMuseum(String id) {
String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
ObjectOpenHashSet<String> collectedItemIds = MUSEUM_ITEM_CACHE.get(uuid).get(Utils.getProfileId()).collectedItemIds();

return collectedItemIds != null && collectedItemIds.contains(id);
}

private record ProfileMuseumData(long lastUpdated, ObjectOpenHashSet<String> collectedItemIds) {
private static final ProfileMuseumData EMPTY = new ProfileMuseumData(0L, null);
private static final long MAX_AGE = 86_400_000;

private boolean stale() {
return System.currentTimeMillis() > lastUpdated + MAX_AGE;
}
}
}
8 changes: 6 additions & 2 deletions src/main/java/de/hysky/skyblocker/utils/Http.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private static ApiResponse sendCacheableGetRequest(String url) throws IOExceptio
InputStream decodedInputStream = getDecodedInputStream(response);
String body = new String(decodedInputStream.readAllBytes());

return new ApiResponse(body, getCacheStatus(response.headers()));
return new ApiResponse(body, response.statusCode(), getCacheStatus(response.headers()));
}

public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException {
Expand Down Expand Up @@ -120,7 +120,11 @@ public static String getCacheStatus(HttpHeaders headers) {
}

//TODO If ever needed, we could just replace cache status with the response headers and go from there
public record ApiResponse(String content, String cacheStatus) {
public record ApiResponse(String content, int statusCode, String cacheStatus) {

public boolean ok() {
return statusCode == 200;
}

public boolean cached() {
return cacheStatus.equals("HIT");
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/de/hysky/skyblocker/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
import de.hysky.skyblocker.skyblock.item.PriceInfoTooltip;
import de.hysky.skyblocker.skyblock.rift.TheRift;
import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
Expand Down Expand Up @@ -365,6 +366,8 @@ public static boolean onChatMessage(Text text, boolean overlay) {

if (isOnSkyblock && message.startsWith("Profile ID: ")) {
profileId = message.replace("Profile ID: ", "");

MuseumItemCache.tick(profileId);
}

return true;
Expand Down
43 changes: 33 additions & 10 deletions src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import de.hysky.skyblocker.utils.render.culling.OcclusionCulling;
import de.hysky.skyblocker.utils.render.title.Title;
import de.hysky.skyblocker.utils.render.title.TitleContainer;
import me.x150.renderer.render.Renderer3d;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
Expand All @@ -22,8 +21,6 @@
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;

import java.awt.*;

public class RenderHelper {
private static final Vec3d ONE = new Vec3d(1, 1, 1);
private static final int MAX_OVERWORLD_BUILD_HEIGHT = 319;
Expand All @@ -36,20 +33,46 @@ public static void renderFilledThroughWallsWithBeaconBeam(WorldRenderContext con

public static void renderFilledThroughWalls(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha) {
if (FrustumUtils.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1)) {
Renderer3d.renderThroughWalls();
renderFilled(context, pos, colorComponents, alpha);
Renderer3d.stopRenderThroughWalls();
renderFilled(context, Vec3d.of(pos), ONE, colorComponents, alpha, true);
}
}

public static void renderFilledIfVisible(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha) {
if (OcclusionCulling.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1)) {
renderFilled(context, pos, colorComponents, alpha);
renderFilled(context, Vec3d.of(pos), ONE, colorComponents, alpha, false);
}
}

private static void renderFilled(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha) {
Renderer3d.renderFilled(context.matrixStack(), new Color(colorComponents[0], colorComponents[1], colorComponents[2], alpha), Vec3d.of(pos), ONE);

private static void renderFilled(WorldRenderContext context, Vec3d pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) {
MatrixStack matrices = context.matrixStack();
Vec3d camera = context.camera().getPos();
Tessellator tessellator = RenderSystem.renderThreadTesselator();
BufferBuilder buffer = tessellator.getBuffer();

matrices.push();
matrices.translate(-camera.x, -camera.y, -camera.z);

RenderSystem.setShader(GameRenderer::getPositionColorProgram);
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
RenderSystem.polygonOffset(-1f, -10f);
RenderSystem.enablePolygonOffset();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableDepthTest();
RenderSystem.depthFunc(throughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL);
RenderSystem.disableCull();

buffer.begin(DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR);
WorldRenderer.renderFilledBox(matrices, buffer, pos.x, pos.y, pos.z, pos.x + dimensions.x, pos.y + dimensions.y, pos.z + dimensions.z, colorComponents[0], colorComponents[1], colorComponents[2], alpha);
tessellator.draw();

matrices.pop();
RenderSystem.polygonOffset(0f, 0f);
RenderSystem.disablePolygonOffset();
RenderSystem.disableBlend();
RenderSystem.disableDepthTest();
RenderSystem.depthFunc(GL11.GL_LEQUAL);
RenderSystem.enableCull();
}

private static void renderBeaconBeam(WorldRenderContext context, BlockPos pos, float[] colorComponents) {
Expand Down

0 comments on commit 9edc484

Please sign in to comment.