aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
blob: 8ddcd60e0a51902fbc74eb91ecc57576fb149601 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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 com.mojang.serialization.codecs.RecordCodecBuilder;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
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.nbt.NbtCompound;
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.Optional;
import java.util.concurrent.CompletableFuture;

/**
 * Doesn't work with auto pet right now because thats complicated.
 * 
 * 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<>();

	/**
	 * Used in case the server lags to prevent the screen tick check from overwriting the clicked pet logic
	 */
	private static boolean shouldLook4Pets;

	public static void init() {
		load();

		ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> {
			if (Utils.isOnSkyblock() && screen instanceof GenericContainerScreen genericContainerScreen) {
				if (genericContainerScreen.getTitle().getString().startsWith("Pets")) {
					shouldLook4Pets = true;

					ScreenEvents.afterTick(screen).register(screen1 -> {
						if (shouldLook4Pets) {
							for (Slot slot : genericContainerScreen.getScreenHandler().slots) {
								ItemStack stack = slot.getStack();

								if (!stack.isEmpty() && ItemUtils.getLoreLineIf(stack, line -> line.equals("Click to despawn!")) != null) {
									shouldLook4Pets = false;
									parsePet(stack, false);

									break;
								}
							}
						}
					});
				}
			}
		});
	}

	private static void load() {
		CompletableFuture.runAsync(() -> {
			try (BufferedReader reader = Files.newBufferedReader(FILE)) {
				CACHED_PETS.putAll(PetInfo.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(PetInfo.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) {
			ItemStack stack = slot.getStack();

			if (!stack.isEmpty()) parsePet(stack, true);
		}
	}

	private static void parsePet(ItemStack stack, boolean clicked) {
		String id = ItemUtils.getItemId(stack);
		String profileId = Utils.getProfileId();

		if (id.equals("PET") && !profileId.isEmpty()) {
			NbtCompound customData = ItemUtils.getCustomData(stack);

			//Should never fail, all pets must have this but you never know with Hypixel
			try {
				PetInfo petInfo = PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(customData.getString("petInfo"))).getOrThrow();
				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);
				} else {
					playerData.put(profileId, petInfo);
				}

				save();
			} catch (Exception e) {
				LOGGER.error(LogUtils.FATAL_MARKER, "[Skyblocker Pet Cache] Failed to parse pet's pet info!", e);
			}
		}
	}

	@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;
	}

	public record PetInfo(String type, double exp, String tier, Optional<String> uuid, Optional<String> item, Optional<String> skin) {
		public static final Codec<PetInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group(
				Codec.STRING.fieldOf("type").forGetter(PetInfo::type),
				Codec.DOUBLE.fieldOf("exp").forGetter(PetInfo::exp),
				Codec.STRING.fieldOf("tier").forGetter(PetInfo::tier),
				Codec.STRING.optionalFieldOf("uuid").forGetter(PetInfo::uuid),
				Codec.STRING.optionalFieldOf("heldItem").forGetter(PetInfo::item),
				Codec.STRING.optionalFieldOf("skin").forGetter(PetInfo::skin))
				.apply(instance, PetInfo::new));
		private static final Codec<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING,
				Codec.unboundedMap(Codec.STRING, CODEC).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)
				).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new);
	}
}