diff options
6 files changed, 90 insertions, 36 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c65f95..2f69cc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Overlay can be moved more precisely - Dungeons can be "joined" and "left" manually (if the automatic detection fails): `/moo dungeon <enter/leave>` - Improved handling of invalid/missing Hypixel API key +- `/moo stalkskyblock` Switched from sky.lea.moe (discontinued) to sky.shiiyu.moe + +### Fixed +- Fixed crash caused by another, outdated and buggy mod which sadly too many people still use +- various smaller fixes here and there, e.g.: + - 'Create Auction' and 'Create BIN Auction' now show the price per item if multiple items are sold + - Dungeon party finder: entered vs queued floor wasn't detected correctly + - A dead player was counted as another death when they left the SkyBlock dungeon ## [1.8.9-0.10.2] - 15.09.2020 ### Added diff --git a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java index edbfc62..d3dc658 100644 --- a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java +++ b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java @@ -378,7 +378,7 @@ public class MooCommand extends CommandBase { wealthHover.appendFreshSibling(new MooChatComponent.KeyValueTooltipComponent("Co-ops' purses sum", Utils.formatNumberWithAbbreviations(activeProfile.getCoopCoinPurses(stalkedPlayer.getUuid())))); } - MooChatComponent sbStats = new MooChatComponent("SkyBlock stats of " + stalkedPlayer.getName() + " (" + activeProfile.getCuteName() + ")").gold().bold().setUrl("https://sky.lea.moe/stats/" + stalkedPlayer.getName() + "/" + activeProfile.getCuteName(), "Click to view SkyBlock stats on sky.lea.moe") + MooChatComponent sbStats = new MooChatComponent("SkyBlock stats of " + stalkedPlayer.getName() + " (" + activeProfile.getCuteName() + ")").gold().bold().setUrl("https://sky.shiiyu.moe/stats/" + stalkedPlayer.getName() + "/" + activeProfile.getCuteName(), "Click to view SkyBlock stats on sky.shiiyu.moe") .appendFreshSibling(new MooChatComponent.KeyValueChatComponent("Coins", coinsBankAndPurse).setHover(wealthHover)); // highest skill + skill average: if (highestSkill != null) { diff --git a/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java b/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java index 07a1fe4..d7fd5da 100644 --- a/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java +++ b/src/main/java/de/cowtipper/cowlection/handler/DungeonCache.java @@ -15,6 +15,7 @@ import java.util.stream.Collectors; public class DungeonCache { private final Cowlection main; private final Map<String, Integer> deathCounter; + private final Set<String> deadPlayers; private final Set<String> failedPuzzles; private final Set<UUID> destroyedCrypts; @@ -22,11 +23,13 @@ public class DungeonCache { private int elapsedMinutes; private int classMilestone; private long lastScoreboardCheck; + private long nextPerformanceSend; private String queuedFloor; public DungeonCache(Cowlection main) { this.main = main; deathCounter = new HashMap<>(); + deadPlayers = new HashSet<>(); failedPuzzles = new HashSet<>(); destroyedCrypts = new HashSet<>(); } @@ -58,6 +61,10 @@ public class DungeonCache { } public void sendDungeonPerformance() { + if (System.currentTimeMillis() < nextPerformanceSend) { + // already sent dungeon performance less than 260ms ago + return; + } String dungeonPerformance; boolean hasPointPenalty = false; if (deathCounter.isEmpty()) { @@ -77,6 +84,7 @@ public class DungeonCache { dungeonPerformance += "\n" + EnumChatFormatting.LIGHT_PURPLE + "➜ " + EnumChatFormatting.RED + EnumChatFormatting.BOLD + "Skill " + EnumChatFormatting.RED + "score penalty: " + EnumChatFormatting.DARK_RED + getSkillScorePenalty() + " points"; } main.getChatHelper().sendMessage(EnumChatFormatting.WHITE, dungeonPerformance); + nextPerformanceSend = System.currentTimeMillis() + 260; } public void updateElapsedMinutesFromScoreboard() { @@ -117,13 +125,21 @@ public class DungeonCache { this.queuedFloor = floorNr; } - public void addDeath(String playerName) { + public void addDeath(String playerName, boolean ghostByDisconnecting) { + if (!deadPlayers.add(playerName) && ghostByDisconnecting) { + // dead player disconnected from the game; don't count again! + return; + } int previousPlayerDeaths = deathCounter.getOrDefault(playerName, 0); deathCounter.put(playerName, previousPlayerDeaths + 1); new TickDelay(this::sendDungeonPerformance, 1); } + public void revivedPlayer(String playerName) { + deadPlayers.remove(playerName); + } + public void addFailedPuzzle(String text) { failedPuzzles.add(text); } @@ -176,10 +192,12 @@ public class DungeonCache { // resetter private void resetCounters() { deathCounter.clear(); + deadPlayers.clear(); failedPuzzles.clear(); destroyedCrypts.clear(); elapsedMinutes = 0; classMilestone = 0; + nextPerformanceSend = 0; queuedFloor = null; } } diff --git a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java index 318907d..68d602e 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java @@ -11,6 +11,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiChat; import net.minecraft.client.gui.GuiControls; import net.minecraft.client.gui.GuiNewChat; +import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; import net.minecraft.util.StringUtils; @@ -73,8 +74,7 @@ public class ChatListener { } if (MooConfig.showBestFriendNotifications) { // replace default (friend/guild) notification with best friend notification - main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, "" + EnumChatFormatting.DARK_GREEN + EnumChatFormatting.BOLD + "Best friend" + EnumChatFormatting.DARK_GREEN + " > " + EnumChatFormatting.RESET + rank + playerName + joinLeaveSuffix); - e.setCanceled(true); + e.message = new ChatComponentText("" + EnumChatFormatting.DARK_GREEN + EnumChatFormatting.BOLD + "Best friend" + EnumChatFormatting.DARK_GREEN + " > " + EnumChatFormatting.RESET + rank + playerName + joinLeaveSuffix); return; } } 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 edbadb0..e4fea69 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java @@ -6,6 +6,7 @@ import de.cowtipper.cowlection.config.gui.MooConfigGui; import de.cowtipper.cowlection.data.DataHelper.DungeonClass; import de.cowtipper.cowlection.handler.DungeonCache; import de.cowtipper.cowlection.util.TickDelay; +import de.cowtipper.cowlection.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.client.audio.SoundCategory; import net.minecraft.client.gui.FontRenderer; @@ -57,6 +58,11 @@ public class DungeonsListener { */ private final Pattern DUNGEON_PARTY_FINDER_PLAYER = Pattern.compile("^ (?:\\w+): ([A-Za-z]+) \\((\\d+)\\)$"); /** + * examples: "Floor: Entrance", "Floor: Floor 4", "Floor: Floor IV" + */ + private final Pattern DUNGEON_PARTY_FINDER_FLOOR = Pattern.compile("^Floor: (Entrance)?(?:Floor ([IVX]+)?([0-9]+)?)?$"); + private final Pattern DUNGEON_PARTY_FINDER_SELECTED_FLOOR = Pattern.compile("^Currently Selected: (Entrance)?(?:Floor ([IVX]+)?([0-9]+)?)?$"); + /** * example: (Adventuring|Playing|Plundering|Looting|...) The Catacombs with 5/5 players on Floor II! */ private final Pattern DUNGEON_ENTERED_DUNGEON = Pattern.compile("^[A-Za-z ]+ The Catacombs( Entrance)? with [0-9]+/[0-9]+ players(?: on Floor ([IVX]+))?!$"); @@ -92,7 +98,8 @@ public class DungeonsListener { * <li> ☠ You were killed by [mob] and became a ghost.</li> * </ul> */ - 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: * <ul> @@ -274,7 +281,7 @@ public class DungeonsListener { IInventory inventory = inventorySlots.getSlot(0).inventory; if (inventory.getName().equals("Catacombs Gate")) { // update active selected class - ItemStack dungeonClassIndicator = inventory.getStackInSlot(47); + ItemStack dungeonClassIndicator = getStackInSlotOrByName(inventory, 47, "Dungeon Classes"); if (dungeonClassIndicator == null) { // couldn't detect dungeon class indicator return; @@ -392,6 +399,21 @@ public class DungeonsListener { } } + private ItemStack getStackInSlotOrByName(IInventory inventory, int slot, String itemDisplayName) { + ItemStack item = inventory.getStackInSlot(slot); + if (item != null && item.hasDisplayName() && itemDisplayName.equals(EnumChatFormatting.getTextWithoutFormattingCodes(item.getDisplayName()))) { + return item; + } + // an update might have moved the item to another slot, search for it: + for (int checkedSlot = 0; checkedSlot < inventory.getSizeInventory(); checkedSlot++) { + item = inventory.getStackInSlot(slot); + if (item != null && item.hasDisplayName() && itemDisplayName.equals(EnumChatFormatting.getTextWithoutFormattingCodes(item.getDisplayName()))) { + return item; + } + } + return null; + } + // Events inside dungeons @SubscribeEvent public void onDungeonsEnterOrLeave(PlayerSetSpawnEvent e) { @@ -452,12 +474,15 @@ public class DungeonsListener { } // player is in dungeon: Matcher dungeonDeathMatcher = DUNGEON_DEATH_PATTERN.matcher(text); + Matcher dungeonRevivedMatcher = DUNGEON_REVIVED_PATTERN.matcher(text); if (dungeonDeathMatcher.matches()) { String playerName = dungeonDeathMatcher.group(1); if (playerName.equals("You")) { playerName = Minecraft.getMinecraft().thePlayer.getName(); } - main.getDungeonCache().addDeath(playerName); + main.getDungeonCache().addDeath(playerName, dungeonDeathMatcher.group(2).contains("disconnected")); + } else if (dungeonRevivedMatcher.matches()) { + main.getDungeonCache().revivedPlayer(dungeonRevivedMatcher.group(1)); } else if (text.trim().equals("> EXTRA STATS <")) { // dungeon "end screen" new TickDelay(() -> main.getDungeonCache().sendDungeonPerformance(), 5); @@ -502,18 +527,7 @@ public class DungeonsListener { // not a valid dungeon party tooltip return; } - for (String toolTipLine : itemToolTip) { - String line = EnumChatFormatting.getTextWithoutFormattingCodes(toolTipLine); - if (line.startsWith("Floor: ")) { - // extract floor number - int lastSpace = line.lastIndexOf(' '); - if (lastSpace > 5) { - String floorNr = line.substring(lastSpace + 1); - main.getDungeonCache().setQueuedFloor(floorNr); - } - break; - } - } + extractQueuedFloorNr(itemToolTip, DUNGEON_PARTY_FINDER_FLOOR); } } else if (inventory.getName().equals("Group Builder")) { // get dungeon floor nr when creating a dungeon party for party finder @@ -523,25 +537,14 @@ public class DungeonsListener { String clickedItemName = EnumChatFormatting.getTextWithoutFormattingCodes(hoveredSlot.getStack().getDisplayName()); if (clickedItemName.equals("Confirm Group")) { // created dungeon party group - ItemStack selectedFloorItem = inventory.getStackInSlot(13); - if (selectedFloorItem != null && selectedFloorItem.hasDisplayName() && EnumChatFormatting.getTextWithoutFormattingCodes(selectedFloorItem.getDisplayName()).equals("Select Floor")) { + ItemStack selectedFloorItem = getStackInSlotOrByName(inventory, 12, "Select Floor"); + if (selectedFloorItem != null) { List<String> itemToolTip = selectedFloorItem.getTooltip(Minecraft.getMinecraft().thePlayer, false); if (itemToolTip.size() < 5) { // not a valid dungeon floor tooltip return; } - for (String toolTipLine : itemToolTip) { - String line = EnumChatFormatting.getTextWithoutFormattingCodes(toolTipLine); - if (line.startsWith("Currently Selected: ")) { - // extract floor number - int lastSpace = line.lastIndexOf(' '); - if (lastSpace > 18) { - String floorNr = line.substring(lastSpace + 1); - main.getDungeonCache().setQueuedFloor(floorNr); - break; - } - } - } + extractQueuedFloorNr(itemToolTip, DUNGEON_PARTY_FINDER_SELECTED_FLOOR); } } } @@ -549,6 +552,30 @@ public class DungeonsListener { } } + private void extractQueuedFloorNr(List<String> itemToolTip, Pattern pattern) { + for (String toolTipLine : itemToolTip) { + String line = EnumChatFormatting.getTextWithoutFormattingCodes(toolTipLine); + + Matcher floorMatcher = pattern.matcher(line); + if (floorMatcher.matches()) { + String floorNr = floorMatcher.group(1); // floor == Entrance + if (floorNr == null) { + floorNr = floorMatcher.group(2); // floor == [IVX]+ + } + if (floorNr == null) { + try { + int floorNrArabic = Integer.parseInt(floorMatcher.group(3)); + floorNr = Utils.convertArabicToRoman(floorNrArabic); // floor == [0-9]+ + } catch (NumberFormatException ex) { + floorNr = null; + } + } + main.getDungeonCache().setQueuedFloor(floorNr); + break; + } + } + } + private Slot getSlotUnderMouse(GuiChest guiChest) { try { return ReflectionHelper.getPrivateValue(GuiContainer.class, guiChest, "theSlot", "field_147006_u"); diff --git a/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java b/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java index 29b5e51..11f2cda 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java @@ -134,7 +134,7 @@ public class SkyBlockListener { String toolTipLineUnformatted = EnumChatFormatting.getTextWithoutFormattingCodes(toolTip.get(i)); if (toolTipLineUnformatted.startsWith("Top bid: ") || toolTipLineUnformatted.startsWith("Starting bid: ") - || toolTipLineUnformatted.startsWith("Create BIN Auction: ") + || toolTipLineUnformatted.startsWith("Item price: ") || toolTipLineUnformatted.startsWith("Buy it now: ") || toolTipLineUnformatted.startsWith("Sold for: ") || toolTipLineUnformatted.startsWith("New bid: ") /* special case: 'Submit Bid' item */) { @@ -167,7 +167,8 @@ public class SkyBlockListener { } private boolean isSubmitBidItem(ItemStack itemStack) { - return (itemStack.getItem().equals(Items.gold_nugget) || itemStack.getItem().equals(Item.getItemFromBlock(Blocks.gold_block))) - && (itemStack.hasDisplayName() && (itemStack.getDisplayName().endsWith("Submit Bid") || itemStack.getDisplayName().endsWith("Collect Auction"))); + return ((itemStack.getItem().equals(Items.gold_nugget) || itemStack.getItem().equals(Item.getItemFromBlock(Blocks.gold_block))) + && (itemStack.hasDisplayName() && (itemStack.getDisplayName().endsWith("Submit Bid") || itemStack.getDisplayName().endsWith("Collect Auction")))) + || (/* green hardened clay + */ itemStack.hasDisplayName() && (itemStack.getDisplayName().endsWith("Create BIN Auction") || itemStack.getDisplayName().endsWith("Create Auction"))); } } |