Skip to content

Commit

Permalink
Profiled data api (#1102)
Browse files Browse the repository at this point in the history
* Add ProfiledData

* Refactor end stats

* Use UUID

* Refactor MuseumItemCache

* null safety + item cooldown bug fix

* Migrate SlayerTimer and fix compressed

* Fix accessories crash

* Migrate PowderMiningTracker

* Create directories

* Add async options

* Migrate async saves
  • Loading branch information
kevinthegreat1 authored Jan 5, 2025
1 parent f23a43b commit e4ea0c5
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 381 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,7 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
.option(ButtonOption.createBuilder()
.name(Text.translatable("skyblocker.config.otherLocations.end.resetName"))
.text(Text.translatable("skyblocker.config.otherLocations.end.resetText"))
.action((screen, opt) -> {
TheEnd.zealotsKilled = 0;
TheEnd.zealotsSinceLastEye = 0;
TheEnd.eyes = 0;
})
.action((screen, opt) -> TheEnd.PROFILES_STATS.put(TheEnd.EndStats.EMPTY))
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.otherLocations.end.muteEndermanSounds"))
Expand Down
56 changes: 8 additions & 48 deletions src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
Original file line number Diff line number Diff line change
@@ -1,41 +1,27 @@
package de.hysky.skyblocker.skyblock;

import com.google.gson.JsonParser;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.skyblock.item.PetInfo;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import de.hysky.skyblocker.utils.profile.ProfiledData;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.slot.Slot;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;

/**
* Doesn't work with auto pet right now because thats complicated.
* Doesn't work with auto pet right now because that's complicated.
* <p>
* Want support? Ask the Admins for a Mod API event or open your pets menu.
*/
public class PetCache {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("pet_cache.json");
private static final Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>> CACHED_PETS = new Object2ObjectOpenHashMap<>();
public static final Codec<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING,
Codec.unboundedMap(Codec.STRING, PetInfo.CODEC).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)
).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new);
private static final ProfiledData<PetInfo> CACHED_PETS = new ProfiledData<>(FILE, PetInfo.CODEC, true, true);

/**
* Used in case the server lags to prevent the screen tick check from overwriting the clicked pet logic
Expand All @@ -44,7 +30,7 @@ public class PetCache {

@Init
public static void init() {
load();
CACHED_PETS.load();

ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> {
if (Utils.isOnSkyblock() && screen instanceof GenericContainerScreen genericContainerScreen) {
Expand All @@ -70,27 +56,6 @@ public static void init() {
});
}

private static void load() {
CompletableFuture.runAsync(() -> {
try (BufferedReader reader = Files.newBufferedReader(FILE)) {
CACHED_PETS.putAll(SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow());
} catch (NoSuchFileException ignored) {
} catch (Exception e) {
LOGGER.error("[Skyblocker Pet Cache] Failed to load saved pet!", e);
}
});
}

private static void save() {
CompletableFuture.runAsync(() -> {
try (BufferedWriter writer = Files.newBufferedWriter(FILE)) {
SkyblockerMod.GSON.toJson(SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, CACHED_PETS).getOrThrow(), writer);
} catch (Exception e) {
LOGGER.error("[Skyblocker Pet Cache] Failed to save pet data to the cache!", e);
}
});
}

public static void handlePetEquip(Slot slot, int slotId) {
//Ignore inventory clicks
if (slotId >= 0 && slotId <= 53) {
Expand All @@ -112,24 +77,19 @@ private static void parsePet(ItemStack stack, boolean clicked) {

shouldLook4Pets = false;

Object2ObjectOpenHashMap<String, PetInfo> playerData = CACHED_PETS.computeIfAbsent(Utils.getUndashedUuid(), _uuid -> new Object2ObjectOpenHashMap<>());

//Handle deselecting pets
if (clicked && getCurrentPet() != null && getCurrentPet().uuid().orElse("").equals(petInfo.uuid().orElse(""))) {
playerData.remove(profileId);
CACHED_PETS.remove();
} else {
playerData.put(profileId, petInfo);
CACHED_PETS.put(petInfo);
}

save();
CACHED_PETS.save();
}
}

@Nullable
public static PetInfo getCurrentPet() {
String uuid = Utils.getUndashedUuid();
String profileId = Utils.getProfileId();

return CACHED_PETS.containsKey(uuid) && CACHED_PETS.get(uuid).containsKey(profileId) ? CACHED_PETS.get(uuid).get(profileId) : null;
return CACHED_PETS.get();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package de.hysky.skyblocker.skyblock.dwarven;

import com.google.gson.JsonElement;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.UnboundedMapCodec;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
Expand All @@ -16,10 +13,10 @@
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Location;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.profile.ProfiledData;
import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair;
import it.unimi.dsi.fastutil.objects.*;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.hud.ChatHud;
Expand All @@ -33,9 +30,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Files;
import java.nio.file.Path;
import java.text.NumberFormat;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -47,27 +44,16 @@ public class PowderMiningTracker {
private static final Pattern GEMSTONE_SYMBOLS = Pattern.compile("[α☘☠✎✧❁❂❈❤⸕] ");
private static final Pattern REWARD_PATTERN = Pattern.compile(" {4}(.*?) ?x?([\\d,]*)");
private static final Codec<Object2IntMap<String>> REWARDS_CODEC = CodecUtils.object2IntMapCodec(Codec.STRING);
// Doesn't matter if the codec outputs a java map instead of a fastutils map, it's only used in #putAll anyway so the contents are copied over
private static final UnboundedMapCodec<String, Object2IntMap<String>> ALL_REWARDS_CODEC = Codec.unboundedMap(Codec.STRING, REWARDS_CODEC);
private static final Object2ObjectArrayMap<String, String> NAME2ID_MAP = new Object2ObjectArrayMap<>(50);

// This constructor takes in a comparator that is triggered to decide where to add the element in the tree map
// This causes it to be sorted at all times. This is for rendering them in a sort of easy-to-read manner.
private static final Object2IntAVLTreeMap<Text> SHOWN_REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> {
String o1String = o1.getString();
String o2String = o2.getString();
int priority1 = comparePriority(o1String);
int priority2 = comparePriority(o2String);
if (priority1 != priority2) return Integer.compare(priority1, priority2);
return o1String.compareTo(o2String);
});
private static final Object2IntAVLTreeMap<Text> SHOWN_REWARDS = new Object2IntAVLTreeMap<>(Comparator.<Text>comparingInt(text -> comparePriority(text.getString())).thenComparing(Text::getString));

/**
* Holds the total reward maps for all accounts and profiles. {@link #currentProfileRewards} is a subset of this map, updated on profile change.
*
* @implNote This is a map from (account uuid + "+" + profile uuid) to itemId/amount map.
*/
private static final Object2ObjectArrayMap<String, Object2IntMap<String>> ALL_REWARDS = new Object2ObjectArrayMap<>();
private static final ProfiledData<Object2IntMap<String>> ALL_REWARDS = new ProfiledData<>(getRewardFilePath(), REWARDS_CODEC);

/**
* <p>
Expand Down Expand Up @@ -98,8 +84,7 @@ public static void init() {
if (isEnabled()) recalculatePrices();
});

ClientLifecycleEvents.CLIENT_STARTED.register(PowderMiningTracker::loadRewards);
ClientLifecycleEvents.CLIENT_STOPPING.register(PowderMiningTracker::saveRewards);
ALL_REWARDS.init();

SkyblockEvents.PROFILE_CHANGE.register(PowderMiningTracker::onProfileChange);
SkyblockEvents.PROFILE_INIT.register(PowderMiningTracker::onProfileInit);
Expand Down Expand Up @@ -163,7 +148,7 @@ private static void onProfileChange(String prevProfileId, String newProfileId) {

private static void onProfileInit(String profileId) {
if (!isEnabled()) return;
currentProfileRewards = ALL_REWARDS.computeIfAbsent(getCombinedId(profileId), k -> new Object2IntArrayMap<>());
currentProfileRewards = ALL_REWARDS.computeIfAbsent(Object2IntArrayMap::new);
recalculateAll();
}

Expand Down Expand Up @@ -247,33 +232,6 @@ public static Object2ObjectMap<String, String> getName2IdMap() {
return Object2ObjectMaps.unmodifiable(NAME2ID_MAP);
}

private static void loadRewards(MinecraftClient client) {
if (Files.notExists(getRewardFilePath())) return;
try {
String jsonString = Files.readString(getRewardFilePath());
JsonElement json = SkyblockerMod.GSON.fromJson(jsonString, JsonElement.class);
ALL_REWARDS.clear();
ALL_REWARDS.putAll(ALL_REWARDS_CODEC.decode(JsonOps.INSTANCE, json).getOrThrow().getFirst());
LOGGER.info("Loaded powder mining rewards from file.");
} catch (Exception e) {
LOGGER.error("Failed to load powder mining rewards from file!", e);
}
}

private static void saveRewards(MinecraftClient client) {
try {
String jsonString = ALL_REWARDS_CODEC.encodeStart(JsonOps.INSTANCE, ALL_REWARDS).getOrThrow().toString();
if (Files.notExists(getRewardFilePath())) {
Files.createDirectories(getRewardFilePath().getParent()); // Create all parent directories if they don't exist
Files.createFile(getRewardFilePath());
}
Files.writeString(getRewardFilePath(), jsonString);
LOGGER.info("Saved powder mining rewards to file.");
} catch (Exception e) {
LOGGER.error("Failed to save powder mining rewards to file!", e);
}
}

static {
NAME2ID_MAP.put("Gemstone Powder", "GEMSTONE_POWDER"); // Not an actual item, but since we're using IDs for mapping to colored text we need to have this here

Expand Down Expand Up @@ -347,10 +305,6 @@ private static Path getRewardFilePath() {
return SkyblockerMod.CONFIG_DIR.resolve("reward-trackers/powder-mining.json");
}

private static String getCombinedId(String profileUuid) {
return Utils.getUndashedUuid() + "+" + profileUuid;
}

private static void render(DrawContext context, RenderTickCounter tickCounter) {
if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return;
int y = MinecraftClient.getInstance().getWindow().getScaledHeight() / 2 - 100;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ public Set<Location> availableLocations() {
public void updateContent() {
// Zealots
if (SkyblockerConfigManager.get().otherLocations.end.zealotKillsEnabled) {
TheEnd.EndStats endStats = TheEnd.PROFILES_STATS.putIfAbsent(TheEnd.EndStats.EMPTY);
addComponent(new IcoTextComponent(ENDERMAN_HEAD, Text.literal("Zealots").formatted(Formatting.BOLD)));
addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsSinceLastEye", TheEnd.zealotsSinceLastEye)));
addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsTotalKills", TheEnd.zealotsKilled)));
String avg = TheEnd.eyes == 0 ? "???" : DECIMAL_FORMAT.format((float) TheEnd.zealotsKilled / TheEnd.eyes);
addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsSinceLastEye", endStats.zealotsSinceLastEye())));
addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsTotalKills", endStats.totalZealotKills())));
String avg = endStats.eyes() == 0 ? "???" : DECIMAL_FORMAT.format((float) endStats.totalZealotKills() / endStats.eyes());
addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.avgKillsPerEye", avg)));
}

Expand Down
Loading

0 comments on commit e4ea0c5

Please sign in to comment.