Skip to content

Commit d4868c7

Browse files
committed
fix: show all active Tiered Bonuses instead of stopping at first match
fix: continue showing Crimson Armor stacks after removal until they wind down to zero fix(ItemUtils): add ItemStack.EMPTY guards to prevent unnecessary component lookups perf: replace per-frame Crimson Armor lore scan with ItemStack reference equality change detection perf(ActionBarParser): defer string ops and cache NumberFormat to reduce per-frame allocations
1 parent 24ecbbf commit d4868c7

File tree

4 files changed

+267
-148
lines changed

4 files changed

+267
-148
lines changed

src/main/java/com/fix3dll/skyblockaddons/core/CrimsonArmorAbilityStack.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import lombok.Getter;
44
import lombok.Setter;
5+
import lombok.ToString;
56

6-
@Getter
7+
@Getter @ToString
78
public enum CrimsonArmorAbilityStack {
89
CRIMSON("Crimson", "Dominus", "ᝐ"),
910
TERROR("Terror", "Hydra Strike", "⁑"),

src/main/java/com/fix3dll/skyblockaddons/listeners/RenderListener.java

Lines changed: 116 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
import java.util.Collection;
120120
import java.util.Collections;
121121
import java.util.Comparator;
122+
import java.util.EnumSet;
122123
import java.util.List;
123124
import java.util.Map;
124125
import java.util.Optional;
@@ -217,6 +218,18 @@ public class RenderListener {
217218
private final ArrayList<Component> deployableDisplayBuffer = new ArrayList<>();
218219
private final ArrayList<Component> deployableExpandBuffer = new ArrayList<>();
219220

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+
220233
public RenderListener() {
221234
// HudElementRegistryImpl.attachElementAfter(
222235
// VanillaHudElements.INFO_BAR,
@@ -1571,87 +1584,135 @@ public void drawText(GuiGraphics graphics, Feature feature, float scale, ButtonL
15711584

15721585
}
15731586

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+
*/
15741602
private String getCrimsonArmorAbilityStacks() {
15751603
LocalPlayer player = MC.player;
15761604
if (player == null) return null;
15771605

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;
15951616
}
15961617
}
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+
15971631
return builder.isEmpty() ? null : builder.toString();
15981632
}
15991633

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;
16021645

1603-
float currentX = x;
1604-
float currentY;
1646+
ItemStack itemStack = player.getItemBySlot(slot);
1647+
if (itemStack == ItemStack.EMPTY) continue;
16051648

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;
16101651

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+
}
16151659
}
16161660

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();
16181663

1619-
int count = 0;
1620-
for (EssenceType essenceType : EssenceType.values()) {
1621-
int value;
1664+
float currentX = x;
1665+
float currentY;
16221666

1667+
int maxNumberWidth;
16231668
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);
16251674
} else {
1626-
value = main.getDungeonManager().getCollectedEssences().getOrDefault(essenceType, 0);
1675+
maxNumberWidth = MC.font.width("99");
16271676
}
16281677

1629-
if (usePlaceholders) {
1630-
value = 99;
1631-
} else if (value <= 0 && hideZeroes) {
1632-
continue;
1633-
}
1678+
int color = Feature.DUNGEONS_COLLECTED_ESSENCES_DISPLAY.getColor();
16341679

1635-
int column = count % 2;
1636-
int row = count / 2;
1680+
int count = 0;
1681+
for (EssenceType essenceType : EssenceType.values()) {
1682+
int value;
16371683

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+
}
16441689

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;
16481698

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;
16511705

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+
}
16531715
}
1654-
}
16551716

16561717
/**
16571718
* Displays the bait list. Only shows bait with count > 0.

0 commit comments

Comments
 (0)