diff options
Diffstat (limited to 'src')
5 files changed, 282 insertions, 2 deletions
diff --git a/src/main/java/eu/olli/cowlection/Cowlection.java b/src/main/java/eu/olli/cowlection/Cowlection.java index dfa3456..ab538ee 100644 --- a/src/main/java/eu/olli/cowlection/Cowlection.java +++ b/src/main/java/eu/olli/cowlection/Cowlection.java @@ -7,6 +7,7 @@ import eu.olli.cowlection.config.MooConfig; import eu.olli.cowlection.handler.FriendsHandler; import eu.olli.cowlection.handler.PlayerCache; import eu.olli.cowlection.listener.ChatListener; +import eu.olli.cowlection.listener.DungeonsListener; import eu.olli.cowlection.listener.PlayerListener; import eu.olli.cowlection.util.ChatHelper; import eu.olli.cowlection.util.VersionChecker; @@ -61,6 +62,7 @@ public class Cowlection { @EventHandler public void init(FMLInitializationEvent e) { MinecraftForge.EVENT_BUS.register(new ChatListener(this)); + MinecraftForge.EVENT_BUS.register(new DungeonsListener(this)); MinecraftForge.EVENT_BUS.register(new PlayerListener(this)); ClientCommandHandler.instance.registerCommand(new MooCommand(this)); ClientCommandHandler.instance.registerCommand(new ShrugCommand(this)); diff --git a/src/main/java/eu/olli/cowlection/config/MooConfig.java b/src/main/java/eu/olli/cowlection/config/MooConfig.java index ff54ab9..9604c22 100644 --- a/src/main/java/eu/olli/cowlection/config/MooConfig.java +++ b/src/main/java/eu/olli/cowlection/config/MooConfig.java @@ -41,6 +41,9 @@ public class MooConfig { public static boolean showAdvancedTooltips; public static String[] tabCompletableNamesCommands; private static String numeralSystem; + // SkyBlock dungeon + public static int[] dungClassRange; + public static boolean dungFilterPartiesWithDupes; // logs search config public static String[] logsDirs; private static String defaultStartDate; @@ -138,6 +141,13 @@ public class MooConfig { Property propMoo = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, "moo", "", "The answer to life the universe and everything. Don't edit this entry manually!", Utils.VALID_UUID_PATTERN), false); + // SkyBlock dungeon + Property propDungClassRange = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, + "dungClassRange", new int[]{-1, -1}, "Accepted level range for the dungeon party finder. Set to -1 to disable"), true) + .setMinValue(-1).setIsListLengthFixed(true); + Property propDungFilterPartiesWithDupes = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, + "dungFilterPartiesWithDupes", false, "Mark parties with duplicated classes?"), true); + cfg.setCategoryPropertyOrder(Configuration.CATEGORY_CLIENT, propOrderGeneral); // config section: log files search @@ -166,6 +176,10 @@ public class MooConfig { tabCompletableNamesCommands = propTabCompletableNamesCommands.getStringList(); moo = propMoo.getString(); + // SkyBlock dungeon + dungClassRange = propDungClassRange.getIntList(); + dungFilterPartiesWithDupes = propDungFilterPartiesWithDupes.getBoolean(); + // logs search config logsDirs = propLogsDirs.getStringList(); defaultStartDate = propDefaultStartDate.getString().trim(); @@ -185,13 +199,22 @@ public class MooConfig { propTabCompletableNamesCommands.set(tabCompletableNamesCommands); propMoo.set(moo); + // SkyBlock dungeon + propDungClassRange.set(dungClassRange); + propDungFilterPartiesWithDupes.set(dungFilterPartiesWithDupes); + // logs search config propLogsDirs.set(logsDirs); propDefaultStartDate.set(defaultStartDate); if (cfg.hasChanged()) { - if (modifiedTabCompletableCommandsList && Minecraft.getMinecraft().thePlayer != null) { - main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Added or removed commands with tab-completable usernames take effect after a game restart!"); + if (Minecraft.getMinecraft().thePlayer != null) { + if (modifiedTabCompletableCommandsList) { + main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Added or removed commands with tab-completable usernames take effect after a game restart!"); + } + if (dungClassRange[0] > -1 && dungClassRange[1] > -1 && dungClassRange[0] > dungClassRange[1]) { + main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Dungeon class range minimum value cannot be higher than the maximum value."); + } } cfg.save(); } diff --git a/src/main/java/eu/olli/cowlection/listener/DungeonsListener.java b/src/main/java/eu/olli/cowlection/listener/DungeonsListener.java new file mode 100644 index 0000000..b903cc2 --- /dev/null +++ b/src/main/java/eu/olli/cowlection/listener/DungeonsListener.java @@ -0,0 +1,248 @@ +package eu.olli.cowlection.listener; + +import eu.olli.cowlection.Cowlection; +import eu.olli.cowlection.config.MooConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiChest; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.MathHelper; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.event.entity.player.ItemTooltipEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.apache.commons.lang3.StringUtils; +import org.lwjgl.input.Keyboard; + +import java.awt.*; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DungeonsListener { + private static final String FORMATTING_CODE = "§[0-9a-fl-or]"; + private final Cowlection main; + /** + * example: (space)Robin_Hood: Archer (42) + */ + private final Pattern DUNGEON_PARTY_FINDER_PLAYER = Pattern.compile("^ (?:\\w+): ([A-Za-z]+) \\((\\d+)\\)$"); + /** + * Example tooltip lines: + * <ul> + * <li>§7Crit Damage: §c+23% §8(Heavy -3%) §8(+28.75%)</li> + * <li>§7Health: §a+107 HP §8(+133.75 HP)</li> + * <li>§7Defense: §a+130 §8(Heavy +65) §8(+162.5)</li> + * <li>§7Speed: §a-1 §8(Heavy -1)</li> + * </ul> + * <pre> + * | Groups | Example matches | + * |----------------------------|-------------------| + * | Group `prefix` | §7Crit Damage: §c | + * | Group `statNonDungeon` | +23 | + * | Group `statNonDungeonUnit` | % | + * | Group `colorReforge` | §8 | + * | Group `reforge` | Heavy | + * | Group `statReforge` | -3 | + * | Group `statReforgeUnit` | % | + * | Group `colorDungeon` | §8 | + * </pre> + */ + private final Pattern TOOLTIP_LINE_PATTERN = Pattern.compile("^(?<prefix>(?:" + FORMATTING_CODE + ")+[A-Za-z ]+: " + FORMATTING_CODE + ")(?<statNonDungeon>[+-][0-9]+)(?<statNonDungeonUnit>%| HP|)(?: (?<colorReforge>" + FORMATTING_CODE + ")\\((?<reforge>[A-Za-z]+) (?<statReforge>[+-][0-9]+)(?<statReforgeUnit>%| HP|)\\))?(?: (?<colorDungeon>" + FORMATTING_CODE + ")\\((?<statDungeon>[+-][.0-9]+)(?<statDungeonUnit>%| HP|)\\))?$"); + + private String activeDungeonClass; + + public DungeonsListener(Cowlection main) { + this.main = main; + activeDungeonClass = "unknown"; + } + + @SubscribeEvent + public void onItemTooltip(ItemTooltipEvent e) { + if (e.itemStack == null || e.toolTip == null) { + return; + } + if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) && isDungeonItem(e.toolTip)) { + // simplify dungeon armor stats + String originalItemName = e.itemStack.getDisplayName(); + NBTTagCompound extraAttributes = e.itemStack.getSubCompound("ExtraAttributes", false); + if (extraAttributes != null && extraAttributes.hasKey("modifier")) { + String reforge = StringUtils.capitalize(extraAttributes.getString("modifier")); + int modifierSuffix = Math.max(reforge.indexOf("_sword"), reforge.indexOf("_bow")); + if (modifierSuffix != -1) { + reforge = reforge.substring(0, modifierSuffix); + } + int reforgeInItemName = originalItemName.indexOf(reforge); + if (reforgeInItemName > 0 && !originalItemName.contains(EnumChatFormatting.STRIKETHROUGH.toString())) { + // we have a reforged item! strike through reforge in item name and remove any essence upgrades (✪) + StringBuffer modifiedItemName = new StringBuffer(originalItemName) + .insert(reforgeInItemName, "" + EnumChatFormatting.GRAY + EnumChatFormatting.STRIKETHROUGH) + .insert(reforgeInItemName + reforge.length() + /*formatting codes for gray + strikethrough:*/4, originalItemName.substring(0, reforgeInItemName)); + // remove essence upgrade indicators (✪) + String essenceUpgradeIndicator = EnumChatFormatting.GOLD + "✪"; + int essenceModifier = modifiedItemName.indexOf(essenceUpgradeIndicator); + while (essenceModifier > 0) { + modifiedItemName.replace(essenceModifier, essenceModifier + essenceUpgradeIndicator.length(), "" + EnumChatFormatting.GRAY + EnumChatFormatting.STRIKETHROUGH + "✪"); + essenceModifier = modifiedItemName.indexOf(essenceUpgradeIndicator); + } + e.toolTip.set(0, modifiedItemName.toString()); // replace item name + + // subtract stat boosts from reforge and update stats for dungeons + ListIterator<String> tooltipIterator = e.toolTip.listIterator(); + while (tooltipIterator.hasNext()) { + String line = tooltipIterator.next(); + Matcher lineMatcher = TOOLTIP_LINE_PATTERN.matcher(line); + if (lineMatcher.matches()) { + try { + int statNonDungeon = Integer.parseInt(lineMatcher.group("statNonDungeon")); + + int statBase = statNonDungeon; + if (reforge.equalsIgnoreCase(lineMatcher.group("reforge"))) { + // tooltip line has reforge stats; subtract them from base stats + statBase -= Integer.parseInt(lineMatcher.group("statReforge")); + } + + if (statBase == 0) { + // don't redraw 0 stats + tooltipIterator.remove(); + continue; + } + String newToolTipLine = String.format("%s%+d%s", lineMatcher.group("prefix"), statBase, lineMatcher.group("statNonDungeonUnit")); + if (lineMatcher.group("statDungeon") != null) { + // tooltip line has dungeon stats; update them! + double statDungeon = Double.parseDouble(lineMatcher.group("statDungeon")); + + double dungeonStatModifier = statDungeon / statNonDungeon; // modified through skill level or gear essence upgrades + if (extraAttributes.hasKey("dungeon_item_level")) { + // with essences upgraded item => calculate base (level based) dungeon modfier + dungeonStatModifier -= extraAttributes.getInteger("dungeon_item_level") / 10d; + } + + double statBaseDungeon = statBase * dungeonStatModifier; + double statDungeonWithMaxEssenceUpgrades = statBase * (dungeonStatModifier + /*5x essence à +10% each => +50% stats */0.5d); + newToolTipLine += String.format(" %s(₀ₓ✪ %+.1f%s) %s(₅ₓ✪ %+.1f%s)", lineMatcher.group("colorDungeon"), statBaseDungeon, lineMatcher.group("statDungeonUnit"), + lineMatcher.group("colorDungeon"), statDungeonWithMaxEssenceUpgrades, lineMatcher.group("statDungeonUnit")); + } + + tooltipIterator.set(newToolTipLine); + } catch (NumberFormatException ignored) { + } + } + } + } + } + } + } + + private boolean isDungeonItem(List<String> toolTip) { + ListIterator<String> toolTipIterator = toolTip.listIterator(toolTip.size()); + while (toolTipIterator.hasPrevious()) { + if (toolTipIterator.previous().contains(" DUNGEON ")) { + return true; + } + } + return false; + } + + @SubscribeEvent + public void onRenderGuiBackground(GuiScreenEvent.DrawScreenEvent.Pre e) { + if (e.gui instanceof GuiChest) { + GuiChest guiChest = (GuiChest) e.gui; + + Container inventorySlots = guiChest.inventorySlots; + IInventory inventory = inventorySlots.getSlot(0).inventory; + if (inventory.getName().equals("Catacombs Gate")) { + // update active selected class + ItemStack dungeonClassIndicator = inventory.getStackInSlot(47); + if (dungeonClassIndicator == null) { + // couldn't detect dungeon class indicator + return; + } + for (String toolTipLine : dungeonClassIndicator.getTooltip(Minecraft.getMinecraft().thePlayer, false)) { + String line = EnumChatFormatting.getTextWithoutFormattingCodes(toolTipLine); + if (line.startsWith("Currently Selected: ")) { + String selectedClass = line.substring(line.lastIndexOf(' ') + 1); + if (!selectedClass.equals(activeDungeonClass)) { + activeDungeonClass = selectedClass; + } + } + } + } else if (inventory.getName().equals("Party Finder")) { + // enhance party finder + + // formulas from GuiContainer#initGui (guiLeft, guiTop) and GuiChest (ySize) + int guiLeft = (guiChest.width - 176) / 2; + int inventoryRows = inventory.getSizeInventory() / 9; + int ySize = 222 - 108 + inventoryRows * 18; + int guiTop = (guiChest.height - ySize) / 2; + GlStateManager.pushMatrix(); + + GlStateManager.translate(0, 0, 1); + float scaleFactor = 0.8f; + GlStateManager.scale(scaleFactor, scaleFactor, 0); + for (Slot inventorySlot : inventorySlots.inventorySlots) { + if (inventorySlot.getHasStack()) { + int slotRow = inventorySlot.slotNumber / 9; + int slotColumn = inventorySlot.slotNumber % 9; + // check if slot is one of the middle slots with actual minions + int maxRow = inventoryRows - 2; + if (slotRow > 0 && slotRow < maxRow && slotColumn > 0 && slotColumn < 8) { + int slotX = (int) ((guiLeft + inventorySlot.xDisplayPosition) / scaleFactor); + int slotY = (int) ((guiTop + inventorySlot.yDisplayPosition) / scaleFactor); + renderPartyStatus(inventorySlot.getStack(), slotX, slotY); + } + } + } + GlStateManager.popMatrix(); + } + } + } + + private void renderPartyStatus(ItemStack item, int x, int y) { + String status = "⬛"; // ok + Color color = new Color(20, 200, 20, 255); + + List<String> itemTooltip = item.getTooltip(Minecraft.getMinecraft().thePlayer, false); + if (itemTooltip.get(itemTooltip.size() - 1).endsWith("Complete previous floor first!")) { + // cannot enter dungeon + status = "✗"; + color = new Color(220, 20, 20, 255); + } else { + int dungClassMin = MooConfig.dungClassRange[0]; + int dungClassMax = MooConfig.dungClassRange[1]; + Set<String> dungClassesInParty = new HashSet<>(); + dungClassesInParty.add(activeDungeonClass); // add our own class + + for (String toolTipLine : itemTooltip) { + Matcher playerDetailMatcher = DUNGEON_PARTY_FINDER_PLAYER.matcher(EnumChatFormatting.getTextWithoutFormattingCodes(toolTipLine)); + if (playerDetailMatcher.matches()) { + String clazz = playerDetailMatcher.group(1); + int classLevel = MathHelper.parseIntWithDefault(playerDetailMatcher.group(2), -1); + if (MooConfig.dungFilterPartiesWithDupes && !dungClassesInParty.add(clazz)) { + // duped class! + status = "²⁺"; // 2+ + color = new Color(220, 120, 20, 255); + break; + } else if (dungClassMin > -1 && classLevel < dungClassMin) { + // party member too low level + status = EnumChatFormatting.BOLD + "ᐯ"; + color = new Color(200, 20, 20, 255); + break; + } else if (dungClassMax > -1 && classLevel > dungClassMax) { + // party member too high level + status = EnumChatFormatting.BOLD + "ᐱ"; + color = new Color(20, 120, 230, 255); + break; + } + } + } + } + Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow(status, x, y, color.getRGB()); + } +} diff --git a/src/main/java/eu/olli/cowlection/listener/PlayerListener.java b/src/main/java/eu/olli/cowlection/listener/PlayerListener.java index c68d814..af946d4 100644 --- a/src/main/java/eu/olli/cowlection/listener/PlayerListener.java +++ b/src/main/java/eu/olli/cowlection/listener/PlayerListener.java @@ -44,6 +44,9 @@ public class PlayerListener { @SubscribeEvent public void onItemTooltip(ItemTooltipEvent e) { + if (e.itemStack == null || e.toolTip == null) { + return; + } if (!MooConfig.showAdvancedTooltips && !Keyboard.isKeyDown(Keyboard.KEY_LMENU)) { return; } diff --git a/src/main/resources/assets/cowlection/lang/en_US.lang b/src/main/resources/assets/cowlection/lang/en_US.lang index eadc42a..b84140f 100644 --- a/src/main/resources/assets/cowlection/lang/en_US.lang +++ b/src/main/resources/assets/cowlection/lang/en_US.lang @@ -12,6 +12,10 @@ cowlection.config.numeralSystem=Numeral system cowlection.config.numeralSystem.tooltip=Use Roman or Arabic numeral system? cowlection.config.tabCompletableNamesCommands=Commands with Tab-completable usernames cowlection.config.tabCompletableNamesCommands.tooltip=List of commands with a username argument that should be Tab-completable.\nRequires game restart to take effect! +cowlection.config.dungClassRange=Dungeon Parties: Class level range +cowlection.config.dungClassRange.tooltip=Accepted level range for the dungeon party finder. Set to -1 to disable +cowlection.config.dungFilterPartiesWithDupes=Dungeon Parties: Mark duplicated classes? +cowlection.config.dungFilterPartiesWithDupes.tooltip=Mark parties with duplicated classes? cowlection.config.logsDirs=Directories with Minecraft log files cowlection.config.logsDirs.tooltip=List of directories containing Minecraft log files cowlection.config.defaultStartDate=Start date for log file search |