From 0c9d088dcb47a422c1566baeefae702c4cad0a0a Mon Sep 17 00:00:00 2001 From: Cow Date: Thu, 31 Dec 2020 14:29:30 +0100 Subject: Small dungeons related fixes - Fixed deaths sometimes being counted multiple times - Read destroyed crypts from tab list (if available) - Fixed rarely occurring infinite message loop --- CHANGELOG.md | 4 + .../de/cowtipper/cowlection/config/MooConfig.java | 4 +- .../cowtipper/cowlection/handler/DungeonCache.java | 106 +++++++++++++++------ .../listener/skyblock/DungeonsListener.java | 11 +-- .../listener/skyblock/DungeonsPartyListener.java | 6 +- 5 files changed, 93 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a9330..0a71e18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fixed sending 'offline' messages (new version notification and online best friends) - Fixed rare occurrence of repeated triggering of server join and leave events - Fixed other mods interfering with detection of specific chat messages +- SkyBlock Dungeons related: + - Fixed deaths sometimes being counted multiple times + - Read destroyed crypts from tab list (if available) for more accurate numbers + - Fixed rarely occurring infinite message loop ## [1.8.9-0.11.0] - 28.09.2020 ### Added diff --git a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java index f666181..63856af 100644 --- a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java +++ b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java @@ -398,8 +398,8 @@ public class MooConfig { subCat = configCat.addSubCategory("Performance Overlay"); subCat.addExplanations(EnumChatFormatting.UNDERLINE + "Keeps track of:", " ‣ skill score " + EnumChatFormatting.GRAY + "(reduced by deaths and failed puzzles)", - " ‣ speed score " + EnumChatFormatting.GRAY + "(-2.2 points when over 20 minutes)", - " ‣ bonus score " + EnumChatFormatting.GRAY + "(+1 [max 5] for each destroyed crypt; can only be detected up to ~50 blocks away from the player)", + " ‣ speed score " + EnumChatFormatting.GRAY + "(-2.2 points/minute when over 20 minutes)", + " ‣ bonus score " + EnumChatFormatting.GRAY + "(+1 [max 5] for each destroyed crypt; if 'enhanced tab list' is disabled: limited to ~50 blocks away from the player)", "Does " + EnumChatFormatting.ITALIC + "not" + EnumChatFormatting.RESET + " track explorer score " + EnumChatFormatting.GRAY + "(explored rooms, secrets, ...)"); Property propDungOverlayEnabled = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), diff --git a/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java b/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java index d7fd5da..e06ba7d 100644 --- a/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java +++ b/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java @@ -1,23 +1,31 @@ package de.cowtipper.cowlection.handler; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; import de.cowtipper.cowlection.Cowlection; import de.cowtipper.cowlection.util.TickDelay; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiPlayerTabOverlay; +import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.scoreboard.Score; import net.minecraft.scoreboard.ScoreObjective; import net.minecraft.scoreboard.ScorePlayerTeam; import net.minecraft.scoreboard.Scoreboard; import net.minecraft.util.EnumChatFormatting; +import net.minecraft.world.WorldSettings; import java.util.*; import java.util.stream.Collectors; public class DungeonCache { + private static final Ordering playerOrderer = Ordering.from(new PlayerComparator()); + private final Cowlection main; private final Map deathCounter; private final Set deadPlayers; private final Set failedPuzzles; private final Set destroyedCrypts; + private int cryptsOffset; private boolean isInDungeon; private int elapsedMinutes; @@ -32,6 +40,7 @@ public class DungeonCache { deadPlayers = new HashSet<>(); failedPuzzles = new HashSet<>(); destroyedCrypts = new HashSet<>(); + cryptsOffset = 0; } public boolean isInDungeon() { @@ -87,36 +96,63 @@ public class DungeonCache { nextPerformanceSend = System.currentTimeMillis() + 260; } - public void updateElapsedMinutesFromScoreboard() { + /** + * Fetch info from scoreboard (right) and tab list + */ + public void fetchScoreboardData() { long now = System.currentTimeMillis(); if (now - lastScoreboardCheck > 10000) { // run every 10 seconds lastScoreboardCheck = now; - Scoreboard scoreboard = Minecraft.getMinecraft().theWorld.getScoreboard(); - ScoreObjective scoreboardSidebar = scoreboard.getObjectiveInDisplaySlot(1); - if (scoreboardSidebar != null) { - Collection scoreboardLines = scoreboard.getSortedScores(scoreboardSidebar); - for (Score line : scoreboardLines) { - ScorePlayerTeam scorePlayerTeam = scoreboard.getPlayersTeam(line.getPlayerName()); - if (scorePlayerTeam != null) { - String lineWithoutFormatting = EnumChatFormatting.getTextWithoutFormattingCodes(scorePlayerTeam.getColorPrefix() + scorePlayerTeam.getColorSuffix()); - - String timeElapsed = "Time Elapsed: "; - if (lineWithoutFormatting.startsWith(timeElapsed)) { - // dungeon timer: 05m 22s - String timeString = lineWithoutFormatting.substring(timeElapsed.length()); - try { - int indexOfMinute = timeString.indexOf('m'); - if (indexOfMinute > -1) { - elapsedMinutes = (Integer.parseInt(timeString.substring(0, indexOfMinute))); + Minecraft mc = Minecraft.getMinecraft(); + if (mc.theWorld != null) { + Scoreboard scoreboard = mc.theWorld.getScoreboard(); + + // check scoreboard (right) + ScoreObjective scoreboardSidebar = scoreboard.getObjectiveInDisplaySlot(1); + if (scoreboardSidebar != null) { + Collection scoreboardLines = scoreboard.getSortedScores(scoreboardSidebar); + for (Score line : scoreboardLines) { + ScorePlayerTeam scorePlayerTeam = scoreboard.getPlayersTeam(line.getPlayerName()); + if (scorePlayerTeam != null) { + String lineWithoutFormatting = EnumChatFormatting.getTextWithoutFormattingCodes(scorePlayerTeam.getColorPrefix() + scorePlayerTeam.getColorSuffix()); + + String timeElapsed = "Time Elapsed: "; + if (lineWithoutFormatting.startsWith(timeElapsed)) { + // dungeon timer: 05m 22s + String timeString = lineWithoutFormatting.substring(timeElapsed.length()); + try { + int indexOfMinute = timeString.indexOf('m'); + if (indexOfMinute > -1) { + elapsedMinutes = (Integer.parseInt(timeString.substring(0, indexOfMinute))); + } + } catch (NumberFormatException ex) { + // couldn't parse dungeon time from scoreboard + ex.printStackTrace(); } - } catch (NumberFormatException ex) { - // couldn't parse dungeon time from scoreboard - ex.printStackTrace(); } } } } } + + // check tab list + Collection playerInfoMap = mc.thePlayer.sendQueue.getPlayerInfoMap(); + List networkPlayerInfos = playerOrderer.sortedCopy(playerInfoMap); + GuiPlayerTabOverlay tabList = mc.ingameGUI.getTabList(); + for (NetworkPlayerInfo playerInfo : networkPlayerInfos) { + if (playerInfo.getGameProfile().getName().startsWith("!")) { + String tabListEntry = EnumChatFormatting.getTextWithoutFormattingCodes(tabList.getPlayerName(playerInfo)); + if (tabListEntry != null && tabListEntry.startsWith(" Crypts: ")) { + try { + int cryptsFromTabList = Integer.parseInt(tabListEntry.substring(" Crypts: ".length()).trim()); + cryptsOffset = cryptsFromTabList - destroyedCrypts.size(); + } catch (NumberFormatException | IndexOutOfBoundsException ex) { + // couldn't parse crypts count from tab list + ex.printStackTrace(); + } + } + } + } } } @@ -125,9 +161,10 @@ public class DungeonCache { this.queuedFloor = floorNr; } - public void addDeath(String playerName, boolean ghostByDisconnecting) { - if (!deadPlayers.add(playerName) && ghostByDisconnecting) { - // dead player disconnected from the game; don't count again! + public void addDeath(String playerName) { + boolean playerWasDeadAlready = !deadPlayers.add(playerName); + if (playerWasDeadAlready) { + // dead player "died" again (e.g. caused by disconnecting while being dead); don't count again! return; } int previousPlayerDeaths = deathCounter.getOrDefault(playerName, 0); @@ -148,8 +185,8 @@ public class DungeonCache { this.classMilestone = classMilestone; } - public boolean addDestroyedCrypt(UUID uuid) { - return destroyedCrypts.add(uuid); + public void addDestroyedCrypt(UUID uuid) { + destroyedCrypts.add(uuid); } // getter @@ -182,7 +219,7 @@ public class DungeonCache { } public int getDestroyedCrypts() { - return destroyedCrypts.size(); + return destroyedCrypts.size() + cryptsOffset; } public int getElapsedMinutes() { @@ -195,9 +232,24 @@ public class DungeonCache { deadPlayers.clear(); failedPuzzles.clear(); destroyedCrypts.clear(); + cryptsOffset = 0; elapsedMinutes = 0; classMilestone = 0; nextPerformanceSend = 0; queuedFloor = null; } + + /** + * see: GuiPlayerTabOverlay.PlayerComparator + */ + static class PlayerComparator implements Comparator { + private PlayerComparator() { + } + + public int compare(NetworkPlayerInfo playerInfo1, NetworkPlayerInfo playerInfo2) { + ScorePlayerTeam scorePlayerTeam1 = playerInfo1.getPlayerTeam(); + ScorePlayerTeam scorePlayerTeam2 = playerInfo2.getPlayerTeam(); + return ComparisonChain.start().compareTrueFirst(playerInfo1.getGameType() != WorldSettings.GameType.SPECTATOR, playerInfo2.getGameType() != WorldSettings.GameType.SPECTATOR).compare(scorePlayerTeam1 != null ? scorePlayerTeam1.getRegisteredName() : "", scorePlayerTeam2 != null ? scorePlayerTeam2.getRegisteredName() : "").compare(playerInfo1.getGameProfile().getName(), playerInfo2.getGameProfile().getName()).result(); + } + } } diff --git a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java index 918c63d..5a65a36 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java @@ -91,7 +91,7 @@ public class DungeonsListener { *
  • ☠ You were killed by [mob] and became a ghost.
  • * */ - private final Pattern DUNGEON_DEATH_PATTERN = Pattern.compile("^ ☠ (\\w+) (.+) and became a ghost\\.$"); + private final Pattern DUNGEON_DEATH_PATTERN = Pattern.compile("^ ☠ (\\w+) (?:.+) and became a ghost\\.$"); private final Pattern DUNGEON_REVIVED_PATTERN = Pattern.compile("^ ❣ (\\w+) was revived(?:.*?)$"); /** * Class milestones: @@ -443,7 +443,7 @@ public class DungeonsListener { if (playerName.equals("You")) { playerName = Minecraft.getMinecraft().thePlayer.getName(); } - main.getDungeonCache().addDeath(playerName, dungeonDeathMatcher.group(2).contains("disconnected")); + main.getDungeonCache().addDeath(playerName); } else if (dungeonRevivedMatcher.matches()) { main.getDungeonCache().revivedPlayer(dungeonRevivedMatcher.group(1)); } else if (text.trim().equals("> EXTRA STATS <")) { @@ -559,10 +559,7 @@ public class DungeonsListener { return; } if ("Crypt Undead".equals(e.player.getName())) { - boolean isNewDestroyedCrypt = main.getDungeonCache().addDestroyedCrypt(e.player.getUniqueID()); - if (isNewDestroyedCrypt) { - main.getLogger().info("[Dungeon Bonus Score] Crypt Undead spawned @ " + e.player.getPosition() + " - distance to player: " + Math.sqrt(e.player.getPosition().distanceSq(Minecraft.getMinecraft().thePlayer.getPosition()))); - } + main.getDungeonCache().addDestroyedCrypt(e.player.getUniqueID()); } } @@ -572,7 +569,7 @@ public class DungeonsListener { DungeonCache dungeonCache = main.getDungeonCache(); if (dungeonCache.isInDungeon()) { - dungeonCache.updateElapsedMinutesFromScoreboard(); + dungeonCache.fetchScoreboardData(); } boolean isEditingDungeonOverlaySettings = MooConfigGui.showDungeonPerformanceOverlay(); diff --git a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsPartyListener.java b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsPartyListener.java index 775634d..84a58ee 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsPartyListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsPartyListener.java @@ -62,10 +62,12 @@ public class DungeonsPartyListener { @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) public void onMessageReceived(ClientChatReceivedEvent e) { if (e.type != 2 && listenForChatMsgs) { // normal chat or system msg (not above action bar), and not stopped - if (msgCounter > 15) { + if (msgCounter == 15) { // received too many messages without detecting any party-related lines, abort! + listenForChatMsgs = false; + shutdown(); main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Wasn't able to detect the party member list. Maybe the chat formatting was changed?"); - nextStep = Step.STOP; + return; } ++msgCounter; -- cgit