Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 159 additions & 23 deletions src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.serialization.Codec;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.config.configs.OtherLocationsConfig;
Expand All @@ -20,11 +22,14 @@
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.commands.arguments.StringRepresentableArgument;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.DyeColor;
import org.apache.commons.text.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -37,19 +42,23 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static de.hysky.skyblocker.utils.command.CommandUtils.failOnMissingProfile;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommands.argument;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommands.literal;

public class EnigmaSouls {
private static final Logger LOGGER = LoggerFactory.getLogger(EnigmaSouls.class);
private static final Supplier<Waypoint.Type> TYPE_SUPPLIER = () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType;
private static final Identifier WAYPOINTS_JSON = SkyblockerMod.id("rift/enigma_soul_waypoints.json");
private static final Map<BlockPos, ProfileAwareWaypoint> SOUL_WAYPOINTS = new HashMap<>(52);
private static final Map<RiftZone, Map<BlockPos, ProfileAwareWaypoint>> SOUL_WAYPOINTS = HashMap.newHashMap(9);
private static final Path FOUND_SOULS_FILE = SkyblockerMod.CONFIG_DIR.resolve("found_enigma_souls.json");
private static final float[] GREEN = ColorUtils.getFloatComponents(DyeColor.GREEN);
private static final float[] RED = ColorUtils.getFloatComponents(DyeColor.RED);
Expand All @@ -60,26 +69,47 @@ static void load(Minecraft client) {
//Load waypoints
soulsLoaded = CompletableFuture.runAsync(() -> {
try (BufferedReader reader = client.getResourceManager().openAsReader(WAYPOINTS_JSON)) {
JsonObject file = JsonParser.parseReader(reader).getAsJsonObject();
JsonArray waypoints = file.get("waypoints").getAsJsonArray();
JsonObject zones = JsonParser.parseReader(reader).getAsJsonObject().getAsJsonObject("zones");

for (int i = 0; i < waypoints.size(); i++) {
JsonObject waypoint = waypoints.get(i).getAsJsonObject();
BlockPos pos = new BlockPos(waypoint.get("x").getAsInt(), waypoint.get("y").getAsInt(), waypoint.get("z").getAsInt());
SOUL_WAYPOINTS.put(pos, new EnigmaSoul(pos, TYPE_SUPPLIER, GREEN, RED));
}
for (Map.Entry<String, JsonElement> zoneJson : zones.entrySet()) {
RiftZone zone = RiftZone.fromSerializedName(zoneJson.getKey());
JsonArray waypoints_list = zoneJson.getValue().getAsJsonArray();
Map<BlockPos, ProfileAwareWaypoint> waypoints = HashMap.newHashMap(waypoints_list.size());

for (JsonElement wp : waypoints_list) {
JsonObject waypoint = wp.getAsJsonObject();
BlockPos pos = new BlockPos(waypoint.get("x").getAsInt(), waypoint.get("y").getAsInt(), waypoint.get("z").getAsInt());
waypoints.put(pos, new EnigmaSoul(pos, zone, waypoint.get("name").getAsString()));
}

SOUL_WAYPOINTS.put(zone, waypoints);
}
} catch (IOException e) {
LOGGER.error("[Skyblocker] There was an error while loading enigma soul waypoints!", e);
}

LOGGER.info("[Skyblocker] Loaded {} enigma souls across {} locations", SOUL_WAYPOINTS.values().stream().mapToInt(Map::size).sum(), SOUL_WAYPOINTS.size());

if (SOUL_WAYPOINTS.size() != RiftZone.values().length) {
LOGGER.debug("[Skyblocker] Zones from enigma soul json do not match RiftZone enum!");
}

//Load found souls
try (BufferedReader reader = Files.newBufferedReader(FOUND_SOULS_FILE)) {
for (Map.Entry<String, JsonElement> profile : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) {
for (JsonElement foundSoul : profile.getValue().getAsJsonArray().asList()) {
SOUL_WAYPOINTS.get(PosUtils.parsePosString(foundSoul.getAsString())).setFound(profile.getKey());
for (JsonElement foundSoul : profile.getValue().getAsJsonArray()) {
BlockPos pos = PosUtils.parsePosString(foundSoul.getAsString());
for (Map<BlockPos, ProfileAwareWaypoint> zone : SOUL_WAYPOINTS.values()) {
ProfileAwareWaypoint waypoint = zone.get(pos);
if (waypoint != null) {
waypoint.setFound(profile.getKey());
break;
}
}
}
}

LOGGER.debug("[Skyblocker] Loaded found enigma souls");
} catch (NoSuchFileException _) {
} catch (IOException e) {
LOGGER.error("[Skyblocker] There was an error while loading found enigma souls!", e);
Expand All @@ -89,12 +119,12 @@ static void load(Minecraft client) {

static void save(Minecraft client) {
Map<String, Set<BlockPos>> foundSouls = new HashMap<>();
for (ProfileAwareWaypoint soul : SOUL_WAYPOINTS.values()) {
streamWaypoints().forEach(soul -> {
for (String profile : soul.foundProfiles) {
foundSouls.computeIfAbsent(profile, _ -> new HashSet<>());
foundSouls.get(profile).add(soul.pos);
}
}
});

JsonObject json = new JsonObject();
for (Map.Entry<String, Set<BlockPos>> foundSoulsForProfile : foundSouls.entrySet()) {
Expand All @@ -109,6 +139,7 @@ static void save(Minecraft client) {

try (BufferedWriter writer = Files.newBufferedWriter(FOUND_SOULS_FILE)) {
SkyblockerMod.GSON.toJson(json, writer);
LOGGER.info("[Skyblocker] Saved found enigma souls");
} catch (IOException e) {
LOGGER.error("[Skyblocker] There was an error while saving found enigma souls!", e);
}
Expand All @@ -118,11 +149,11 @@ static void extractRendering(PrimitiveCollector collector) {
OtherLocationsConfig.Rift config = SkyblockerConfigManager.get().otherLocations.rift;

if (Utils.isInTheRift() && config.enigmaSoulWaypoints && soulsLoaded.isDone()) {
for (Waypoint soul : SOUL_WAYPOINTS.values()) {
if (soul.shouldRender() || config.highlightFoundEnigmaSouls) {
streamWaypoints().forEach(soul -> {
if (soul.shouldRender()) {
soul.extractRendering(collector);
}
}
});
}
}

Expand All @@ -142,51 +173,156 @@ static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispat
.then(literal("rift")
.then(literal("enigmaSouls")
.then(literal("markAllFound").executes(context -> {
SOUL_WAYPOINTS.values().forEach(Waypoint::setFound);
if (failOnMissingProfile(context)) return 0;

streamWaypoints().forEach(Waypoint::setFound);
context.getSource().sendFeedback(Constants.PREFIX.get().append(Component.translatable("skyblocker.rift.enigmaSouls.markAllFound")));

return Command.SINGLE_SUCCESS;
}))
.then(literal("markAllMissing").executes(context -> {
SOUL_WAYPOINTS.values().forEach(Waypoint::setMissing);
if (failOnMissingProfile(context)) return 0;

streamWaypoints().forEach(Waypoint::setMissing);
context.getSource().sendFeedback(Constants.PREFIX.get().append(Component.translatable("skyblocker.rift.enigmaSouls.markAllMissing")));

return Command.SINGLE_SUCCESS;
}))
.then(literal("markClosestFound").executes(context -> {
if (failIfNotInRift(context)) return 0;

markClosestSoul(true);
context.getSource().sendFeedback(Constants.PREFIX.get().append(Component.translatable("skyblocker.rift.enigmaSouls.markClosestFound")));

return Command.SINGLE_SUCCESS;
}))
.then(literal("markClosestMissing").executes(context -> {
if (failIfNotInRift(context)) return 0;

markClosestSoul(false);
context.getSource().sendFeedback(Constants.PREFIX.get().append(Component.translatable("skyblocker.rift.enigmaSouls.markClosestMissing")));

return Command.SINGLE_SUCCESS;
})))));
}))
.then(literal("markZoneFound").then(argument("zone", RiftZone.RiftZoneArgumentType.riftZone()).executes(context -> {
if (failOnMissingProfile(context)) return 0;

RiftZone zone = context.getArgument("zone", RiftZone.class);
SOUL_WAYPOINTS.get(zone).values().forEach(Waypoint::setFound);
context.getSource().sendFeedback(Constants.PREFIX.get().append(
Component.translatableEscape("skyblocker.rift.enigmaSouls.markZoneFound", zone.displayName())));

return Command.SINGLE_SUCCESS;
})))
.then(literal("markZoneMissing").then(argument("zone", RiftZone.RiftZoneArgumentType.riftZone()).executes(context -> {
if (failOnMissingProfile(context)) return 0;

RiftZone zone = context.getArgument("zone", RiftZone.class);
SOUL_WAYPOINTS.get(zone).values().forEach(Waypoint::setMissing);
context.getSource().sendFeedback(Constants.PREFIX.get().append(
Component.translatableEscape("skyblocker.rift.enigmaSouls.markZoneMissing", zone.displayName())));

return Command.SINGLE_SUCCESS;
})))
)));
}

private static void markClosestSoul(boolean asFound) {
LocalPlayer player = Minecraft.getInstance().player;

if (!soulsLoaded.isDone() || player == null) return;
if (!soulsLoaded.isDone() || player == null || !Utils.isInTheRift()) return;

SOUL_WAYPOINTS.values().stream()
.filter(Waypoint::shouldRender)
streamWaypoints()
.min(Comparator.comparingDouble(soul -> soul.pos.distToCenterSqr(player.position())))
.filter(soul -> soul.pos.distToCenterSqr(player.position()) <= 16)
.ifPresent(asFound ? Waypoint::setFound : Waypoint::setMissing);
}

public static boolean failIfNotInRift(CommandContext<FabricClientCommandSource> context) {
if (!Utils.isInTheRift()) {
context.getSource().sendFeedback(Constants.PREFIX.get().append(Component.translatable("skyblocker.rift.notInRift").withStyle(ChatFormatting.RED)));
return true;
}
return false;
}

private static Stream<ProfileAwareWaypoint> streamWaypoints() {
return SOUL_WAYPOINTS.values().stream().flatMap(zone -> zone.values().stream());
}

private static String canonicalProfileName(String profile) {
if (Character.isUpperCase(profile.charAt(0))) {
// profile is a normal profile name, reverse it into a rift profile name
return new StringBuilder(profile).reverse().toString();
} else {
return profile;
}
}

private static class EnigmaSoul extends ProfileAwareWaypoint {
private EnigmaSoul(BlockPos pos, Supplier<Type> typeSupplier, float[] missingColor, float[] foundColor) {
super(pos, typeSupplier, missingColor, foundColor);
public final RiftZone zone;
public final String name;

private EnigmaSoul(BlockPos pos, RiftZone zone, String name) {
super(pos, TYPE_SUPPLIER, GREEN, RED);
this.zone = zone;
this.name = name;
}

@Override
public boolean shouldRender() {
return super.shouldRender() || SkyblockerConfigManager.get().otherLocations.rift.highlightFoundEnigmaSouls;
}

@Override
public void setFound(String profile) {
profile = canonicalProfileName(profile);
LOGGER.debug("[Skyblocker] Set enigma soul found for {}: {}/{}", profile, zone.displayName(), name);
super.setFound(profile);
}

@Override
public void setMissing(String profile) {
profile = canonicalProfileName(profile);
LOGGER.debug("[Skyblocker] Set enigma soul missing for {}: {}/{}", profile, zone.displayName(), name);
super.setMissing(profile);
}
}

private enum RiftZone implements StringRepresentable {
WYLD_WOODS,
BLACK_LAGOON,
WEST_VILLAGE,
DREADFARM,
VILLAGE_PLAZA,
LIVING_CAVE,
COLOSSEUM,
STILLGORE_CHATEAU,
MOUNTAINTOP;

private static final Codec<RiftZone> CODEC = StringRepresentable.fromEnum(RiftZone::values);

@Override
public String getSerializedName() {
return name().toLowerCase(Locale.ENGLISH);
}

public String displayName() {
return WordUtils.capitalizeFully(name().replace("_", " "));
}

public static RiftZone fromSerializedName(String name) {
return valueOf(name.toUpperCase(Locale.ENGLISH));
}

public static class RiftZoneArgumentType extends StringRepresentableArgument<RiftZone> {
private RiftZoneArgumentType() {
super(CODEC, RiftZone::values);
}

public static RiftZoneArgumentType riftZone() {
return new RiftZoneArgumentType();
}
}
}
}
13 changes: 13 additions & 0 deletions src/main/java/de/hysky/skyblocker/utils/command/CommandUtils.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
package de.hysky.skyblocker.utils.command;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Utils;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;

public final class CommandUtils {
public static final Command<FabricClientCommandSource> noOp = _ -> -1;

public static boolean failOnMissingProfile(CommandContext<FabricClientCommandSource> context) {
if (Utils.getProfile() == null || Utils.getProfile().isEmpty()) {
context.getSource().sendFeedback(Constants.PREFIX.get().append(Component.translatable("skyblocker.command.profileMissing").withStyle(ChatFormatting.RED)));
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public boolean shouldRender() {

@Override
public void setFound() {
foundProfiles.add(Utils.getProfile());
setFound(Utils.getProfile());
}

public void setFound(String profile) {
Expand All @@ -33,7 +33,11 @@ public void setFound(String profile) {

@Override
public void setMissing() {
foundProfiles.remove(Utils.getProfile());
setMissing(Utils.getProfile());
}

public void setMissing(String profile) {
foundProfiles.remove(profile);
}

@Override
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/assets/skyblocker/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@

"skyblocker.chat.confirmationPromptNotification": "Click anywhere on screen within 60 seconds to accept the prompt.",

"skyblocker.command.profileMissing": "Failed to detect current profile, please check you are in Skyblock.",

"skyblocker.config.chat": "Chat",

"skyblocker.config.chat.chatRules": "Custom Chat Rules",
Expand Down Expand Up @@ -1871,9 +1873,12 @@
"skyblocker.rift.enigmaSouls.markAllMissing": "Marked all enigma souls as missing!",
"skyblocker.rift.enigmaSouls.markClosestFound": "Marked closest enigma soul as found!",
"skyblocker.rift.enigmaSouls.markClosestMissing": "Marked closest enigma soul as missing!",
"skyblocker.rift.enigmaSouls.markZoneFound": "Marked all enigma souls in %s as found!",
"skyblocker.rift.enigmaSouls.markZoneMissing": "Marked all enigma souls in %s as missing!",
"skyblocker.rift.healNow": "Heal now!",
"skyblocker.rift.iceNow": "Ice now!",
"skyblocker.rift.mania": "Mania!",
"skyblocker.rift.notInRift": "This command only works in the rift!",
"skyblocker.rift.stakeNow": "Stake now!",

"skyblocker.shortcuts.command.replacement": "Replacement Command",
Expand Down
Loading