|
119 | 119 | import java.util.Collection; |
120 | 120 | import java.util.Collections; |
121 | 121 | import java.util.Comparator; |
| 122 | +import java.util.EnumSet; |
122 | 123 | import java.util.List; |
123 | 124 | import java.util.Map; |
124 | 125 | import java.util.Optional; |
@@ -217,6 +218,18 @@ public class RenderListener { |
217 | 218 | private final ArrayList<Component> deployableDisplayBuffer = new ArrayList<>(); |
218 | 219 | private final ArrayList<Component> deployableExpandBuffer = new ArrayList<>(); |
219 | 220 |
|
| 221 | + /** |
| 222 | + * Tracks which stacks have their corresponding armor bonus active (§6 Tiered Bonus line in lore). |
| 223 | + * Updated only when equipment changes, not every frame. |
| 224 | + */ |
| 225 | + private final EnumSet<CrimsonArmorAbilityStack> equippedStacks = EnumSet.noneOf(CrimsonArmorAbilityStack.class); |
| 226 | + /** |
| 227 | + * Last seen {@link ItemStack} reference per equipment slot, indexed by {@link EquipmentSlot#ordinal()}. |
| 228 | + * Reference equality is used for change detection — {@link net.minecraft.world.entity.LivingEntity#getItemBySlot} |
| 229 | + * returns the same instance from an {@link java.util.EnumMap} as long as the item has not changed. |
| 230 | + */ |
| 231 | + private final ItemStack[] lastEquipmentStacks = new ItemStack[EquipmentSlot.values().length]; |
| 232 | + |
220 | 233 | public RenderListener() { |
221 | 234 | // HudElementRegistryImpl.attachElementAfter( |
222 | 235 | // VanillaHudElements.INFO_BAR, |
@@ -1571,87 +1584,135 @@ public void drawText(GuiGraphics graphics, Feature feature, float scale, ButtonL |
1571 | 1584 |
|
1572 | 1585 | } |
1573 | 1586 |
|
| 1587 | + /** |
| 1588 | + * Returns a formatted string of Crimson Isles armor ability stack values for display. |
| 1589 | + * Shows all stacks whose Tiered Bonus is active (§6 Tiered Bonus line present in lore), |
| 1590 | + * even if the current stack count is 0. Multiple stacks are separated by ", ". |
| 1591 | + * <p> |
| 1592 | + * If armor is removed mid-combat, stacks that are still winding down ({@code currentValue > 0}) |
| 1593 | + * continue to be shown until they naturally reach 0 via {@link ActionBarParser}. |
| 1594 | + * <p> |
| 1595 | + * Equipment lore is only re-scanned when the {@link ItemStack} reference of any equipped |
| 1596 | + * armor piece changes. {@link net.minecraft.world.entity.LivingEntity#getItemBySlot} returns |
| 1597 | + * the same instance from an internal {@link java.util.EnumMap} as long as the slot has not |
| 1598 | + * changed, making change detection a pointer comparison — near-zero cost on the render thread. |
| 1599 | + * Non-armor slots (offhand, saddle, body) are excluded from both change detection and lore scanning. |
| 1600 | + * @return Formatted stack string, or {@code null} if no relevant armor is equipped or winding down |
| 1601 | + */ |
1574 | 1602 | private String getCrimsonArmorAbilityStacks() { |
1575 | 1603 | LocalPlayer player = MC.player; |
1576 | 1604 | if (player == null) return null; |
1577 | 1605 |
|
1578 | | - StringBuilder builder = new StringBuilder(); |
1579 | | - out: |
1580 | | - for (CrimsonArmorAbilityStack crimsonArmorAbilityStack : CrimsonArmorAbilityStack.values()) { |
1581 | | - for (EquipmentSlot equipmentSlot : Inventory.EQUIPMENT_SLOT_MAPPING.values()) { // 1.21.5 |
1582 | | - ItemStack itemStack = player.getItemBySlot(equipmentSlot); |
1583 | | - if (itemStack == ItemStack.EMPTY) continue; |
1584 | | - for (String line : ItemUtils.getItemLore(itemStack)) { |
1585 | | - if (line.contains("§6Tiered Bonus: ")) { |
1586 | | - String abilityName = crimsonArmorAbilityStack.getAbilityName(); |
1587 | | - if (line.contains(abilityName)) { |
1588 | | - String symbol = crimsonArmorAbilityStack.getSymbol(); |
1589 | | - int stack = crimsonArmorAbilityStack.getCurrentValue(); |
1590 | | - builder.append(abilityName).append(" ").append(symbol).append(" ").append(stack); |
1591 | | - continue out; |
1592 | | - } |
1593 | | - } |
1594 | | - } |
| 1606 | + boolean changed = false; |
| 1607 | + for (EquipmentSlot slot : Inventory.EQUIPMENT_SLOT_MAPPING.values()) { |
| 1608 | + if (!slot.isArmor()) continue; |
| 1609 | + |
| 1610 | + int ordinal = slot.ordinal(); |
| 1611 | + ItemStack itemStack = player.getItemBySlot(slot); |
| 1612 | + |
| 1613 | + if (lastEquipmentStacks[ordinal] != itemStack) { |
| 1614 | + lastEquipmentStacks[ordinal] = itemStack; |
| 1615 | + changed = true; |
1595 | 1616 | } |
1596 | 1617 | } |
| 1618 | + if (changed) refreshEquippedStacks(player); |
| 1619 | + |
| 1620 | + StringBuilder builder = new StringBuilder(); |
| 1621 | + for (CrimsonArmorAbilityStack stack : CrimsonArmorAbilityStack.values()) { |
| 1622 | + // Skip if armor is no longer equipped and the stack has fully wound down |
| 1623 | + if (!equippedStacks.contains(stack) && stack.getCurrentValue() == 0) continue; |
| 1624 | + |
| 1625 | + if (!builder.isEmpty()) builder.append(", "); |
| 1626 | + builder.append(stack.getAbilityName()) |
| 1627 | + .append(" ").append(stack.getSymbol()) |
| 1628 | + .append(" ").append(stack.getCurrentValue()); |
| 1629 | + } |
| 1630 | + |
1597 | 1631 | return builder.isEmpty() ? null : builder.toString(); |
1598 | 1632 | } |
1599 | 1633 |
|
1600 | | -public void drawCollectedEssences(GuiGraphics graphics, float x, float y, boolean usePlaceholders, boolean hideZeroes) { |
1601 | | - InventoryType inventoryType = main.getInventoryUtils().getInventoryType(); |
| 1634 | + /** |
| 1635 | + * Scans equipped armor lore to refresh {@link #equippedStacks}. |
| 1636 | + * Only called when equipment has changed since the last scan. |
| 1637 | + * Non-armor slots (offhand, saddle, body) are skipped as they cannot carry Tiered Bonuses. |
| 1638 | + */ |
| 1639 | + private void refreshEquippedStacks(LocalPlayer player) { |
| 1640 | + equippedStacks.clear(); |
| 1641 | + CrimsonArmorAbilityStack[] stacks = CrimsonArmorAbilityStack.values(); |
| 1642 | + |
| 1643 | + for (EquipmentSlot slot : Inventory.EQUIPMENT_SLOT_MAPPING.values()) { |
| 1644 | + if (!slot.isArmor()) continue; |
1602 | 1645 |
|
1603 | | - float currentX = x; |
1604 | | - float currentY; |
| 1646 | + ItemStack itemStack = player.getItemBySlot(slot); |
| 1647 | + if (itemStack == ItemStack.EMPTY) continue; |
1605 | 1648 |
|
1606 | | - int maxNumberWidth; |
1607 | | - if (inventoryType == InventoryType.SALVAGING) { |
1608 | | - Set<Map.Entry<EssenceType, Integer>> entrySet = main.getDungeonManager().getSalvagedEssences().entrySet(); |
1609 | | - if (entrySet.isEmpty()) return; |
| 1649 | + for (String line : ItemUtils.getItemLore(itemStack)) { |
| 1650 | + if (!line.contains("§6Tiered Bonus: ")) continue; |
1610 | 1651 |
|
1611 | | - String highestAmountStr = Collections.max(entrySet, Map.Entry.comparingByValue()).getValue().toString(); |
1612 | | - maxNumberWidth = MC.font.width(highestAmountStr); |
1613 | | - } else { |
1614 | | - maxNumberWidth = MC.font.width("99"); |
| 1652 | + for (CrimsonArmorAbilityStack stack : stacks) { |
| 1653 | + if (line.contains(stack.getAbilityName())) { |
| 1654 | + equippedStacks.add(stack); |
| 1655 | + } |
| 1656 | + } |
| 1657 | + } |
| 1658 | + } |
1615 | 1659 | } |
1616 | 1660 |
|
1617 | | - int color = Feature.DUNGEONS_COLLECTED_ESSENCES_DISPLAY.getColor(); |
| 1661 | + public void drawCollectedEssences(GuiGraphics graphics, float x, float y, boolean usePlaceholders, boolean hideZeroes) { |
| 1662 | + InventoryType inventoryType = main.getInventoryUtils().getInventoryType(); |
1618 | 1663 |
|
1619 | | - int count = 0; |
1620 | | - for (EssenceType essenceType : EssenceType.values()) { |
1621 | | - int value; |
| 1664 | + float currentX = x; |
| 1665 | + float currentY; |
1622 | 1666 |
|
| 1667 | + int maxNumberWidth; |
1623 | 1668 | if (inventoryType == InventoryType.SALVAGING) { |
1624 | | - value = main.getDungeonManager().getSalvagedEssences().getOrDefault(essenceType, 0); |
| 1669 | + Set<Map.Entry<EssenceType, Integer>> entrySet = main.getDungeonManager().getSalvagedEssences().entrySet(); |
| 1670 | + if (entrySet.isEmpty()) return; |
| 1671 | + |
| 1672 | + String highestAmountStr = Collections.max(entrySet, Map.Entry.comparingByValue()).getValue().toString(); |
| 1673 | + maxNumberWidth = MC.font.width(highestAmountStr); |
1625 | 1674 | } else { |
1626 | | - value = main.getDungeonManager().getCollectedEssences().getOrDefault(essenceType, 0); |
| 1675 | + maxNumberWidth = MC.font.width("99"); |
1627 | 1676 | } |
1628 | 1677 |
|
1629 | | - if (usePlaceholders) { |
1630 | | - value = 99; |
1631 | | - } else if (value <= 0 && hideZeroes) { |
1632 | | - continue; |
1633 | | - } |
| 1678 | + int color = Feature.DUNGEONS_COLLECTED_ESSENCES_DISPLAY.getColor(); |
1634 | 1679 |
|
1635 | | - int column = count % 2; |
1636 | | - int row = count / 2; |
| 1680 | + int count = 0; |
| 1681 | + for (EssenceType essenceType : EssenceType.values()) { |
| 1682 | + int value; |
1637 | 1683 |
|
1638 | | - if (column == 0) { |
1639 | | - currentX = x; |
1640 | | - } else if (column == 1) { |
1641 | | - currentX = x + 18 + 2 + maxNumberWidth + 5; |
1642 | | - } |
1643 | | - currentY = y + row * 18; |
| 1684 | + if (inventoryType == InventoryType.SALVAGING) { |
| 1685 | + value = main.getDungeonManager().getSalvagedEssences().getOrDefault(essenceType, 0); |
| 1686 | + } else { |
| 1687 | + value = main.getDungeonManager().getCollectedEssences().getOrDefault(essenceType, 0); |
| 1688 | + } |
1644 | 1689 |
|
1645 | | - graphics.guiRenderState.submitGuiElement( |
1646 | | - new BlitAbsoluteRenderState(RenderPipelines.GUI_TEXTURED, textureSetup(essenceType.getResourceLocation()), graphics.pose(), currentX, currentY, 0, 0, 16, 16, 16, 16, -1, graphics.scissorStack.peek()) |
1647 | | - ); |
| 1690 | + if (usePlaceholders) { |
| 1691 | + value = 99; |
| 1692 | + } else if (value <= 0 && hideZeroes) { |
| 1693 | + continue; |
| 1694 | + } |
| 1695 | + |
| 1696 | + int column = count % 2; |
| 1697 | + int row = count / 2; |
1648 | 1698 |
|
1649 | | - Component formattedValue = Component.literal(TextUtils.formatNumber(value)); |
1650 | | - DrawUtils.drawText(graphics, formattedValue, currentX + 18 + 2, currentY + 5, color); |
| 1699 | + if (column == 0) { |
| 1700 | + currentX = x; |
| 1701 | + } else if (column == 1) { |
| 1702 | + currentX = x + 18 + 2 + maxNumberWidth + 5; |
| 1703 | + } |
| 1704 | + currentY = y + row * 18; |
1651 | 1705 |
|
1652 | | - count++; |
| 1706 | + graphics.guiRenderState.submitGuiElement( |
| 1707 | + new BlitAbsoluteRenderState(RenderPipelines.GUI_TEXTURED, textureSetup(essenceType.getResourceLocation()), graphics.pose(), currentX, currentY, 0, 0, 16, 16, 16, 16, -1, graphics.scissorStack.peek()) |
| 1708 | + ); |
| 1709 | + |
| 1710 | + Component formattedValue = Component.literal(TextUtils.formatNumber(value)); |
| 1711 | + DrawUtils.drawText(graphics, formattedValue, currentX + 18 + 2, currentY + 5, color); |
| 1712 | + |
| 1713 | + count++; |
| 1714 | + } |
1653 | 1715 | } |
1654 | | -} |
1655 | 1716 |
|
1656 | 1717 | /** |
1657 | 1718 | * Displays the bait list. Only shows bait with count > 0. |
|
0 commit comments