aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/hysky')
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java596
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java1
3 files changed, 353 insertions, 263 deletions
diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
index 4015dfa5..b3fc871b 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
@@ -1,19 +1,25 @@
package de.hysky.skyblocker.mixin;
import com.llamalad7.mixinextras.injector.WrapWithCondition;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import de.hysky.skyblocker.skyblock.FishingHelper;
+import de.hysky.skyblocker.skyblock.dungeon.DungeonScore;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual;
import de.hysky.skyblocker.utils.Utils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityStatuses;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
+import net.minecraft.network.packet.s2c.play.EntityStatusS2CPacket;
import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
import net.minecraft.util.Identifier;
-
+import net.minecraft.world.World;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -49,12 +55,12 @@ public abstract class ClientPlayNetworkHandlerMixin {
private boolean skyblocker$cancelTeamWarning(Logger instance, String format, Object... arg) {
return !Utils.isOnHypixel();
}
-
+
@WrapWithCondition(method = { "onScoreboardScoreUpdate", "onScoreboardScoreReset" }, at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false))
private boolean skyblocker$cancelUnknownScoreboardObjectiveWarnings(Logger instance, String message, Object objectiveName) {
return !Utils.isOnHypixel();
}
-
+
@WrapWithCondition(method = "warnOnUnknownPayload", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false))
private boolean skyblocker$dropBadlionPacketWarnings(Logger instance, String message, Object identifier) {
return !(Utils.isOnHypixel() && ((Identifier) identifier).getNamespace().equals("badlion"));
@@ -64,4 +70,11 @@ public abstract class ClientPlayNetworkHandlerMixin {
private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) {
MythologicalRitual.onParticle(packet);
}
+
+ @WrapOperation(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;"))
+ private Entity skyblocker$onEntityDeath(EntityStatusS2CPacket packet, World world, Operation<Entity> original) {
+ Entity entity = original.call(packet, world);
+ if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) DungeonScore.handleEntityDeath(entity);
+ return entity;
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
index 6016813c..0334290d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
@@ -1,21 +1,26 @@
package de.hysky.skyblocker.skyblock.dungeon;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.events.SkyblockEvents;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.utils.ApiUtils;
import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Http;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
-import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import net.minecraft.entity.mob.ZombieEntity;
import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.NbtCompound;
-import net.minecraft.nbt.NbtElement;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.collection.DefaultedList;
import org.slf4j.Logger;
@@ -23,265 +28,338 @@ import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
-import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.StreamSupport;
public class DungeonScore {
- private static final SkyblockerConfig.DungeonScore SCORE_CONFIG = SkyblockerConfigManager.get().locations.dungeons.dungeonScore;
- private static final SkyblockerConfig.MimicMessages MIMIC_MESSAGES_CONFIG = SkyblockerConfigManager.get().locations.dungeons.mimicMessages;
- private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Dungeon Score");
- private static final Pattern CLEARED_PATTERN = Pattern.compile("Cleared: (?<cleared>\\d+)%.*");
- private static final Pattern SECRETS_PATTERN = Pattern.compile("Secrets Found: (?<secper>\\d+\\.?\\d*)%");
- private static final Pattern PUZZLES_PATTERN = Pattern.compile(".+?(?=:): \\[(?<state>.)](?: \\(\\w+\\))?");
- private static final Pattern PUZZLE_COUNT_PATTERN = Pattern.compile("Puzzles: \\((?<count>\\d+)\\)");
- private static final Pattern CRYPTS_PATTERN = Pattern.compile("Crypts: (?<crypts>\\d+)");
- private static final Pattern COMPLETED_ROOMS_PATTERN = Pattern.compile(" *Completed Rooms: (?<rooms>\\d+)");
- private static final Pattern DUNGEON_START_PATTERN = Pattern.compile("(?:Auto-closing|Starting) in: \\d:\\d+");
- private static final Pattern FLOOR_PATTERN = Pattern.compile(".*?(?=T)The Catacombs \\((?<floor>[EFM]\\D*\\d*)\\)");
- private static final Pattern DEATHS_PATTERN = Pattern.compile("Team Deaths: (?<deaths>\\d+)");
- private static String currentFloor;
- private static boolean sent270;
- private static boolean sent300;
- private static boolean isMimicKilled;
- private static int puzzleCount;
- //Caching the dungeon start state to prevent unnecessary scoreboard pattern matching after dungeon starts
- private static boolean isDungeonStarted;
- private static boolean isMayorPaul;
- private static long startingTime;
- private static final HashMap<String, FloorRequirement> floorRequirements = new HashMap<>(Map.ofEntries(
- Map.entry("E", new FloorRequirement(30, 1200)),
- Map.entry("F1", new FloorRequirement(30, 600)),
- Map.entry("F2", new FloorRequirement(40, 600)),
- Map.entry("F3", new FloorRequirement(50, 600)),
- Map.entry("F4", new FloorRequirement(60, 720)),
- Map.entry("F5", new FloorRequirement(70, 600)),
- Map.entry("F6", new FloorRequirement(85, 720)),
- Map.entry("F7", new FloorRequirement(100, 840)),
- Map.entry("M1", new FloorRequirement(100, 480)),
- Map.entry("M2", new FloorRequirement(100, 480)),
- Map.entry("M3", new FloorRequirement(100, 480)),
- Map.entry("M4", new FloorRequirement(100, 480)),
- Map.entry("M5", new FloorRequirement(100, 480)),
- Map.entry("M6", new FloorRequirement(100, 600)),
- Map.entry("M7", new FloorRequirement(100, 840))
- ));
-
- public static void init() {
- Scheduler.INSTANCE.scheduleCyclic(DungeonScore::tick, 20);
- ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset());
- ServerLivingEntityEvents.AFTER_DEATH.register((entity, source) -> {
- if (isEntityMimic(entity)) {
- if (MIMIC_MESSAGES_CONFIG.sendMimicMessages) MessageScheduler.INSTANCE.sendMessageAfterCooldown(MIMIC_MESSAGES_CONFIG.mimicMessage);
- setMimicKilled(true);
- }
- });
- }
-
- public static void tick() {
- MinecraftClient client = MinecraftClient.getInstance();
- if (!Utils.isInDungeons() || client.player == null) {
- reset();
- return;
- }
- if (!isDungeonStarted) {
- if (checkIfDungeonStarted()) onDungeonStart();
- return;
- }
- int score = calculateScore();
- if (SCORE_CONFIG.enableDungeonScore270 && !sent270 && score >= 270 && score < 300) {
- MessageScheduler.INSTANCE.sendMessageAfterCooldown("/pc " + Constants.PREFIX.get().getString() + SCORE_CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270"));
- client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1f, 0.1f);
- sent270 = true;
- }
- if (SCORE_CONFIG.enableDungeonScore300 && !sent300 && score >= 300) {
- MessageScheduler.INSTANCE.sendMessageAfterCooldown("/pc " + Constants.PREFIX.get().getString() + SCORE_CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300"));
- client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1f, 1f);
- sent300 = true;
- }
- }
-
- public static boolean isEntityMimic(Entity entity) {
- if (!(entity instanceof ZombieEntity zombie)) return false;
- if (!zombie.isBaby()) return false;
- try {
- DefaultedList<ItemStack> armor = (DefaultedList<ItemStack>) zombie.getArmorItems();
- if (armor.isEmpty()) return false;
- NbtCompound helmetNbt = armor.get(3).getNbt();
- if (helmetNbt == null) return false;
- return helmetNbt.getCompound("SkullOwner")
- .getCompound("Properties")
- .getList("textures", NbtElement.COMPOUND_TYPE)
- .getCompound(0).getString("Value")
- .equals("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTE5YzEyNTQzYmM3NzkyNjA1ZWY2OGUxZjg3NDlhZThmMmEzODFkOTA4NWQ0ZDRiNzgwYmExMjgyZDM1OTdhMCJ9fX0K");
- } catch (NullPointerException e) {
- return false;
- } catch (ClassCastException f) {
- f.printStackTrace();
- return false;
- }
- }
-
- private static boolean checkIfDungeonStarted() {
- for (String sidebarLine : Utils.STRING_SCOREBOARD) {
- Matcher matcher = DUNGEON_START_PATTERN.matcher(sidebarLine);
- if (matcher.matches()) return false;
- }
- return true;
- }
-
- private static void onDungeonStart() {
- setCurrentFloor();
- isDungeonStarted = true;
- puzzleCount = getPuzzleCount();
- isMayorPaul = Utils.getMayor().equals("Paul");
- startingTime = System.currentTimeMillis();
- }
-
- private static int calculateScore() {
- int timeScore = calculateTimeScore();
- int exploreScore = calculateExploreScore();
- int skillScore = calculateSkillScore();
- int paulScore = isMayorPaul ? 10 : 0;
- int cryptsScore = Math.min(getCrypts(), 5);
- int mimicScore = isMimicKilled ? 2 : 0;
- int totalScore = timeScore + exploreScore + skillScore + paulScore + cryptsScore + mimicScore;
- //Will be this way until ready for pr, so it's easy to debug.
- LOGGER.info("Total Score: {} (Time: {}, Explore: {}, Skill: {}, Paul: {}, Crypts: {}, Mimic: {})", totalScore, timeScore, exploreScore, skillScore, paulScore, cryptsScore, mimicScore);
- return totalScore;
- }
-
- private static int calculateExploreScore() {
- int completedRoomScore = (int) Math.floor(60.0 * getCompletedRooms() / getTotalRooms());
- int percentageRequirement = floorRequirements.get(currentFloor).percentage;
- int secretsScore = (int) Math.floor(40 * Math.min(percentageRequirement, getSecretsPercentage()) / percentageRequirement);
- return completedRoomScore + secretsScore;
- }
-
- private static int calculateTimeScore() {
- int score = 100;
- int timeSpent = (int) (System.currentTimeMillis() - startingTime) / 1000;
- int timeRequirement = floorRequirements.get(currentFloor).timeLimit;
- if (timeSpent < timeRequirement) return score;
-
- double timePastRequirement = ((double) (timeSpent - timeRequirement) / timeRequirement) * 100;
- if (timePastRequirement >= 0 && timePastRequirement < 20) {
- score -= (int) timePastRequirement / 2;
- } else if (timePastRequirement >= 20 && timePastRequirement < 40) {
- score -= (int) (10 + (timePastRequirement - 20) / 4);
- } else if (timePastRequirement >= 40 && timePastRequirement < 50) {
- score -= (int) (15 + (timePastRequirement - 40) / 5);
- } else if (timePastRequirement >= 50 && timePastRequirement < 60) {
- score -= (int) (17 + (timePastRequirement - 50) / 6);
- } else if (timePastRequirement >= 60) {
- score -= (int) (18 + (2.0 / 3.0) + (timePastRequirement - 60) / 7);
- }
- return score;
- }
-
- private static int calculateSkillScore() {
- return 20 + (int) Math.floor(80.0 * getCompletedRooms() / getTotalRooms()) - (2 * getDeathCount()) - (10 * getFailedPuzzles());
- }
-
- private static void reset() {
- sent270 = false;
- sent300 = false;
- isDungeonStarted = false;
- isMimicKilled = false;
- isMayorPaul = false;
- puzzleCount = 0;
- currentFloor = "";
- }
-
- public static void setMimicKilled(boolean killed) {
- isMimicKilled = killed;
- }
-
- private static int getTotalRooms() {
- return (int) Math.round((getCompletedRooms()) / getClearPercentage()); //Clear% rounds to the closest integer so it can be off by 0.5% at most, this should be accurate enough
- }
-
- private static int getCompletedRooms() {
- Matcher completedRoomsMatcher = PlayerListMgr.regexAt(43, COMPLETED_ROOMS_PATTERN);
- if (completedRoomsMatcher == null) {
- LOGGER.error("Completed rooms pattern doesn't match");
- return 0;
- }
- return Integer.parseInt(completedRoomsMatcher.group("rooms"));
- }
-
- private static double getClearPercentage() {
- for (String sidebarLine : Utils.STRING_SCOREBOARD) {
- Matcher clearMatcher = CLEARED_PATTERN.matcher(sidebarLine);
- if (!clearMatcher.matches()) continue;
- return Double.parseDouble(clearMatcher.group("cleared")) / 100;
- }
- LOGGER.error("Clear pattern doesn't match");
- return 0;
- }
-
- private static int getDeathCount() {
- Matcher matcher = PlayerListMgr.regexAt(25, DEATHS_PATTERN);
- if (matcher == null) {
- LOGGER.error("Death count pattern doesn't match");
- return 0;
- }
- //TODO: Turn this into a map of players and their deathcounts, get party members' pets, check if they have spirit pet, if they have it reduce their death count by 0.5
- return Integer.parseInt(matcher.group("deaths"));
- }
-
- private static int getPuzzleCount() {
- Matcher matcher = PlayerListMgr.regexAt(47, PUZZLE_COUNT_PATTERN);
- if (matcher == null) {
- LOGGER.error("Puzzle count pattern doesn't match");
- return 0;
- }
- return Integer.parseInt(matcher.group("count"));
- }
-
- //Might be replaced to look for puzzle fail messages on chat instead of playerlist
- private static int getFailedPuzzles() {
- int failedPuzzles = 0;
- for (int index = 0; index < puzzleCount; index++) {
- Matcher puzzleMatcher = PlayerListMgr.regexAt(48 + index, PUZZLES_PATTERN);
- if (puzzleMatcher == null) {
- LOGGER.error("Puzzle pattern doesn't match");
- return 0;
- }
- if (puzzleMatcher.group("state").equals("✖")) failedPuzzles++;
- }
- return failedPuzzles;
- }
-
- private static double getSecretsPercentage() {
- Matcher matcher = PlayerListMgr.regexAt(44, SECRETS_PATTERN);
- if (matcher == null) {
- LOGGER.error("Secrets pattern doesn't match");
- return 0;
- }
- return Double.parseDouble(matcher.group("secper"));
- }
-
- private static int getCrypts() {
- Matcher matcher = PlayerListMgr.regexAt(33, CRYPTS_PATTERN);
- if (matcher == null) {
- LOGGER.error("Crypts pattern doesn't match");
- return 0;
- }
- return Integer.parseInt(matcher.group("crypts"));
- }
-
- public static void setCurrentFloor() {
- for (String sidebarLine : Utils.STRING_SCOREBOARD) {
- Matcher floorMatcher = FLOOR_PATTERN.matcher(sidebarLine);
- if (!floorMatcher.matches()) continue;
- currentFloor = floorMatcher.group("floor");
- return;
- }
- LOGGER.error("Floor pattern doesn't match");
- }
-
- record FloorRequirement(int percentage, int timeLimit) {
- }
+ private static final SkyblockerConfig.DungeonScore SCORE_CONFIG = SkyblockerConfigManager.get().locations.dungeons.dungeonScore;
+ private static final SkyblockerConfig.MimicMessages MIMIC_MESSAGES_CONFIG = SkyblockerConfigManager.get().locations.dungeons.mimicMessages;
+ private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Dungeon Score");
+ //Scoreboard patterns
+ private static final Pattern CLEARED_PATTERN = Pattern.compile("Cleared: (?<cleared>\\d+)%.*");
+ private static final Pattern DUNGEON_START_PATTERN = Pattern.compile("(?:Auto-closing|Starting) in: \\d:\\d+");
+ private static final Pattern FLOOR_PATTERN = Pattern.compile(".*?(?=T)The Catacombs \\((?<floor>[EFM]\\D*\\d*)\\)");
+ //Playerlist patterns
+ private static final Pattern SECRETS_PATTERN = Pattern.compile("Secrets Found: (?<secper>\\d+\\.?\\d*)%");
+ private static final Pattern PUZZLES_PATTERN = Pattern.compile(".+?(?=:): \\[(?<state>.)](?: \\(\\w+\\))?");
+ private static final Pattern PUZZLE_COUNT_PATTERN = Pattern.compile("Puzzles: \\((?<count>\\d+)\\)");
+ private static final Pattern CRYPTS_PATTERN = Pattern.compile("Crypts: (?<crypts>\\d+)");
+ private static final Pattern COMPLETED_ROOMS_PATTERN = Pattern.compile(" *Completed Rooms: (?<rooms>\\d+)");
+ //Chat patterns
+ private static final Pattern DEATHS_PATTERN = Pattern.compile(".*?\u2620 (?<whodied>\\S+) .*");
+ //Other patterns
+ private static final Pattern MIMIC_FLOOR_FILTER_PATTERN = Pattern.compile("[EFM][12345]?");
+
+ private static String currentFloor;
+ private static boolean sent270;
+ private static boolean sent300;
+ private static boolean isMimicKilled;
+ private static int puzzleCount;
+ private static boolean isDungeonStarted;
+ private static boolean isMayorPaul;
+ private static long startingTime;
+ private static int deathCount;
+ private static boolean firstDeathHasSpiritPet;
+ private static boolean bloodRoomCompleted;
+ private static final Map<String, Boolean> SpiritPetCache = new HashMap<>();
+
+ public static void init() {
+ Scheduler.INSTANCE.scheduleCyclic(DungeonScore::tick, 20);
+ SkyblockEvents.LEAVE.register(SpiritPetCache::clear);
+ ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset());
+ ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+ String str = message.getString();
+ checkMessageForDeaths(str);
+ checkMessageForWatcher(str);
+ });
+ }
+
+ public static void tick() {
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (!Utils.isInDungeons() || client.player == null) {
+ reset();
+ return;
+ }
+ if (!isDungeonStarted) {
+ if (checkIfDungeonStarted()) onDungeonStart();
+ return;
+ }
+ int score = calculateScore();
+ if (!sent270 && score >= 270 && score < 300) {
+ if (SCORE_CONFIG.enableDungeonScore270Message) {
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown(SCORE_CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270"));
+ }
+ if (SCORE_CONFIG.enableDungeonScore270Title) {
+ client.inGameHud.setDefaultTitleFade();
+ client.inGameHud.setTitle(Constants.PREFIX.get().append(SCORE_CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270")));
+ }
+ if (SCORE_CONFIG.enableDungeonScore270Sound) {
+ client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f);
+ }
+ sent270 = true;
+ }
+ if (!sent300 && score >= 300) {
+ if (SCORE_CONFIG.enableDungeonScore300Message) {
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown(SCORE_CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300"));
+ }
+ if (SCORE_CONFIG.enableDungeonScore300Title) {
+ client.inGameHud.setDefaultTitleFade();
+ client.inGameHud.setTitle(Constants.PREFIX.get().append(SCORE_CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300")));
+ }
+ if (SCORE_CONFIG.enableDungeonScore300Sound) {
+ client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f);
+ }
+ sent300 = true;
+ }
+ }
+
+ private static void reset() {
+ sent270 = false;
+ sent300 = false;
+ isDungeonStarted = false;
+ isMimicKilled = false;
+ isMayorPaul = false;
+ firstDeathHasSpiritPet = false;
+ deathCount = 0;
+ currentFloor = "";
+ }
+
+ private static void onDungeonStart() {
+ setCurrentFloor();
+ isDungeonStarted = true;
+ puzzleCount = getPuzzleCount();
+ isMayorPaul = Utils.getMayor().equals("Paul");
+ startingTime = System.currentTimeMillis();
+ }
+
+ private static int calculateScore() {
+ int timeScore = calculateTimeScore();
+ int exploreScore = calculateExploreScore();
+ int skillScore = calculateSkillScore();
+ int bonusScore = calculateBonusScore();
+ int totalScore = timeScore + exploreScore + skillScore + bonusScore;
+ if (currentFloor.equals("E")) totalScore = (int) (totalScore * 0.7);
+ //Will be this way until ready for pr, so it's easy to debug.
+ LOGGER.info("Total Score: {} (Time: {}, Explore: {}, Skill: {}, Bonus: {})", totalScore, timeScore, exploreScore, skillScore, bonusScore);
+ return totalScore;
+ }
+
+ private static int calculateSkillScore() {
+ int extraCompletedRooms = 0; //This is needed for calculating the score before going in, so we have the result sooner
+ if (!DungeonManager.isInBoss()) extraCompletedRooms = bloodRoomCompleted ? 1 : 2;
+ return 20 + (int) Math.floor(80.0 * (getCompletedRooms() + extraCompletedRooms) / getTotalRooms()) - getPuzzlePenalty() - getDeathScorePenalty();
+ }
+
+ private static int calculateExploreScore() {
+ int extraCompletedRooms = 0;
+ if (!DungeonManager.isInBoss()) extraCompletedRooms = bloodRoomCompleted ? 1 : 2;
+ int completedRoomScore = (int) Math.floor(60.0 * (getCompletedRooms() + extraCompletedRooms) / getTotalRooms());
+ int percentageRequirement = FloorRequirement.valueOf(currentFloor).percentage;
+ int secretsScore = (int) Math.floor(40 * Math.min(percentageRequirement, getSecretsPercentage()) / percentageRequirement);
+ return completedRoomScore + secretsScore;
+ }
+
+ private static int calculateTimeScore() {
+ int score = 100;
+ int timeSpent = (int) (System.currentTimeMillis() - startingTime) / 1000;
+ int timeRequirement = FloorRequirement.valueOf(currentFloor).timeLimit;
+ if (timeSpent < timeRequirement) return score;
+
+ double timePastRequirement = ((double) (timeSpent - timeRequirement) / timeRequirement) * 100;
+ if (timePastRequirement >= 0 && timePastRequirement < 20) {
+ score -= (int) timePastRequirement / 2;
+ } else if (timePastRequirement >= 20 && timePastRequirement < 40) {
+ score -= (int) (10 + (timePastRequirement - 20) / 4);
+ } else if (timePastRequirement >= 40 && timePastRequirement < 50) {
+ score -= (int) (15 + (timePastRequirement - 40) / 5);
+ } else if (timePastRequirement >= 50 && timePastRequirement < 60) {
+ score -= (int) (17 + (timePastRequirement - 50) / 6);
+ } else if (timePastRequirement >= 60) {
+ score -= (int) (18 + (2.0 / 3.0) + (timePastRequirement - 60) / 7);
+ }
+ return score;
+ }
+
+ private static int calculateBonusScore() {
+ int paulScore = isMayorPaul ? 10 : 0;
+ int cryptsScore = Math.min(getCrypts(), 5);
+ int mimicScore = isMimicKilled ? 2 : 0;
+ if (getSecretsPercentage() >= 100 && !MIMIC_FLOOR_FILTER_PATTERN.matcher(currentFloor).matches()) mimicScore = 2; //If mimic kill is not announced but all secrets are found, mimic must've been killed
+ return paulScore + cryptsScore + mimicScore;
+ }
+
+ private static boolean checkIfDungeonStarted() {
+ return Utils.STRING_SCOREBOARD.stream().anyMatch(s -> DUNGEON_START_PATTERN.matcher(s).matches());
+ }
+
+ public static boolean isEntityMimic(Entity entity) {
+ if (!Utils.isInDungeons()) return false;
+ if (MIMIC_FLOOR_FILTER_PATTERN.matcher(currentFloor).matches()) return false;
+ if (entity == null) return false;
+ if (!(entity instanceof ZombieEntity zombie)) return false;
+ if (!zombie.isBaby()) return false;
+ try {
+ DefaultedList<ItemStack> armor = (DefaultedList<ItemStack>) zombie.getArmorItems();
+ return armor.stream().allMatch(ItemStack::isEmpty);
+ } catch (NullPointerException e) {
+ return false;
+ } catch (ClassCastException f) {
+ f.printStackTrace();
+ return false;
+ }
+ }
+
+ public static void handleEntityDeath(Entity entity) {
+ if (isMimicKilled) return;
+ if (!isEntityMimic(entity)) return;
+ isMimicKilled = true;
+ }
+
+ public static void setMimicKilled(boolean state) {
+ isMimicKilled = state;
+ }
+
+ private static int getTotalRooms() {
+ int completedRooms = getCompletedRooms();
+ return (int) Math.round(completedRooms / getClearPercentage());
+ }
+
+ private static int getCompletedRooms() {
+ Matcher matcher = PlayerListMgr.regexAt(43, COMPLETED_ROOMS_PATTERN);
+ return matcher != null ? Integer.parseInt(matcher.group("rooms")) : 0;
+ }
+
+ private static double getClearPercentage() {
+ for (String sidebarLine : Utils.STRING_SCOREBOARD) {
+ Matcher clearMatcher = CLEARED_PATTERN.matcher(sidebarLine);
+ if (!clearMatcher.matches()) continue;
+ return Double.parseDouble(clearMatcher.group("cleared")) / 100.0;
+ }
+ LOGGER.error("Clear pattern doesn't match");
+ return 0;
+ }
+
+ private static int getDeathScorePenalty() {
+ return deathCount * 2 - (firstDeathHasSpiritPet ? 1 : 0);
+ }
+
+ private static int getPuzzleCount() {
+ Matcher matcher = PlayerListMgr.regexAt(47, PUZZLE_COUNT_PATTERN);
+ return matcher != null ? Integer.parseInt(matcher.group("count")) : 0;
+ }
+
+ //Possible states: ✖, ✦, ✔
+ private static int getPuzzlePenalty() {
+ int incompletePuzzles = 0;
+ for (int index = 0; index < puzzleCount; index++) {
+ Matcher puzzleMatcher = PlayerListMgr.regexAt(48 + index, PUZZLES_PATTERN);
+ if (puzzleMatcher == null) break;
+ if (puzzleMatcher.group("state").matches("[✖✦]")) incompletePuzzles++;
+ }
+ return incompletePuzzles * 10;
+ }
+
+ private static double getSecretsPercentage() {
+ Matcher matcher = PlayerListMgr.regexAt(44, SECRETS_PATTERN);
+ return matcher != null ? Double.parseDouble(matcher.group("secper")) : 0;
+ }
+
+ private static int getCrypts() {
+ Matcher matcher = PlayerListMgr.regexAt(33, CRYPTS_PATTERN);
+ if (matcher == null) matcher = PlayerListMgr.regexAt(32, CRYPTS_PATTERN); //If class milestone 9 is reached, crypts goes up by 1
+ return matcher != null ? Integer.parseInt(matcher.group("crypts")) : 0;
+ }
+
+ public static boolean hasSpiritPet(String name) {
+ return SpiritPetCache.computeIfAbsent(name, k -> {
+ String playeruuid = ApiUtils.name2Uuid(name);
+ try (Http.ApiResponse response = Http.sendHypixelRequest("skyblock/profiles", "?uuid=" + playeruuid)) {
+ if (!response.ok()) throw new IllegalStateException("Failed to get profile uuid for player " + name + "! Response: " + response.content());
+ JsonObject responseJson = JsonParser.parseString(response.content()).getAsJsonObject();
+
+ JsonObject player = StreamSupport.stream(responseJson.getAsJsonArray("profiles").spliterator(), false)
+ .map(JsonElement::getAsJsonObject)
+ .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean())
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("No selected profile found!?"))
+ .getAsJsonObject("members").entrySet().stream()
+ .filter(entry -> entry.getKey().equals(playeruuid))
+ .map(Map.Entry::getValue)
+ .map(JsonElement::getAsJsonObject)
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("Player somehow not found inside their own profile!"));
+
+ for (JsonElement element : player.getAsJsonObject("pets_data").getAsJsonArray("pets")) {
+ if (!element.getAsJsonObject().get("type").getAsString().equals("SPIRIT")) continue;
+ if (!element.getAsJsonObject().get("tier").getAsString().equals("LEGENDARY")) continue;
+
+ return true;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ LOGGER.error("[Skyblocker] Spirit pet lookup by name failed! Name: {} - Cause: {}", name, e.getMessage());
+ }
+ return false;
+ });
+ }
+
+ private static void checkMessageForDeaths(String message) {
+ if (!Utils.isInDungeons()) return;
+ if (!message.startsWith("\u2620", 1)) return;
+ Matcher matcher = DEATHS_PATTERN.matcher(message);
+ if (!matcher.matches()) return;
+ deathCount++;
+ if (deathCount > 1) return;
+ final String whoDied = matcher.group("whodied").transform(s -> {
+ if (s.equals("You")) return MinecraftClient.getInstance().player.getName().getString(); //This will be wrong if the dead player is called 'You' but that's unlikely
+ else return s;
+ });
+ CompletableFuture.supplyAsync(() -> hasSpiritPet(whoDied))
+ .thenAccept(hasSpiritPet -> {
+ firstDeathHasSpiritPet = hasSpiritPet;
+ });
+ }
+
+ private static void checkMessageForWatcher(String message) {
+ if (message.equals("[BOSS] The Watcher: You have proven yourself. You may pass.")) bloodRoomCompleted = true;
+ }
+
+ public static void setCurrentFloor() {
+ for (String sidebarLine : Utils.STRING_SCOREBOARD) {
+ Matcher floorMatcher = FLOOR_PATTERN.matcher(sidebarLine);
+ if (!floorMatcher.matches()) continue;
+ currentFloor = floorMatcher.group("floor");
+ return;
+ }
+ LOGGER.error("Floor pattern doesn't match");
+ }
+
+ enum FloorRequirement {
+ E(30, 1200),
+ F1(30, 600),
+ F2(40, 600),
+ F3(50, 600),
+ F4(60, 720),
+ F5(70, 600),
+ F6(85, 720),
+ F7(100, 840),
+ M1(100, 480),
+ M2(100, 480),
+ M3(100, 480),
+ M4(100, 480),
+ M5(100, 480),
+ M6(100, 600),
+ M7(100, 840);
+
+ private final int percentage;
+ private final int timeLimit;
+
+ FloorRequirement(int percentage, int timeLimit) {
+ this.percentage = percentage;
+ this.timeLimit = timeLimit;
+ }
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index 1fc718be..3336a0a7 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -400,7 +400,6 @@ public class Utils {
JsonObject json = JsonParser.parseString(Http.sendGetRequest("https://api.hypixel.net/v2/resources/skyblock/election")).getAsJsonObject();
if (json.get("success").getAsBoolean()) {
mayor = json.get("mayor").getAsJsonObject().get("name").getAsString();
- System.out.println(mayor);
} else {
throw new IOException("API call for mayor failed: " + json.get("cause").getAsString());
}