diff options
author | Emirlol <81419447+Emirlol@users.noreply.github.com> | 2024-01-14 18:21:20 +0300 |
---|---|---|
committer | Rime <81419447+Emirlol@users.noreply.github.com> | 2024-01-21 09:37:01 +0300 |
commit | 6a350f931609432bd93362b2dbdc26e23450e2b8 (patch) | |
tree | d2b68098ad9f3ae87d45a9a58b1f2a18c3775733 | |
parent | 2b1239950626b4480b0ac7120f4c8429dfaf8b68 (diff) | |
download | Skyblocker-6a350f931609432bd93362b2dbdc26e23450e2b8.tar.gz Skyblocker-6a350f931609432bd93362b2dbdc26e23450e2b8.tar.bz2 Skyblocker-6a350f931609432bd93362b2dbdc26e23450e2b8.zip |
Fixed most things and cleaned up code
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()); } |