package com.example.essentialsplus.loot.ui; import com.example.essentialsplus.loot.LootRecord; import com.example.essentialsplus.loot.droptable.DropTable; import com.example.essentialsplus.ui.Ui; import com.example.essentialsplus.wiki.WikiApi; import java.awt.BorderLayout; import java.awt.Dimension; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; import java.util.function.Function; import javax.swing.BorderFactory; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; import net.runelite.client.game.ItemManager; import net.runelite.http.api.item.ItemPrice; /** Drops of the current target: the player's recorded loot (opens the loot view) and the wiki drop table. */ @Slf4j class DropsSection extends JPanel { private static final Map TABLES = new ConcurrentHashMap<>(); /** item name (lower case) -> item id, -1 if not found */ private static final Map IDS = new ConcurrentHashMap<>(); private final WikiApi api; private final ExecutorService executor; private final ItemManager itemManager; private final Function records; private final Consumer openLoot; private final Consumer openWiki; private String name; private long recordStamp = -1; private int variant; private boolean loading; private String error; DropsSection(WikiApi api, ExecutorService executor, ItemManager itemManager, Function records, Consumer openLoot, Consumer openWiki) { this.openWiki = openWiki; this.api = api; this.executor = executor; this.itemManager = itemManager; this.records = records; this.openLoot = openLoot; setLayout(new VerticalLayout(3)); setOpaque(false); render(); } /** Target name; rebuilds only when it or the recorded loot changes. */ void setTarget(String target) { LootRecord r = target == null ? null : records.apply(target); long stamp = r == null ? 0 : r.getLastSeen() + r.getKills(); if (java.util.Objects.equals(target, name) && stamp == recordStamp) { return; } if (!java.util.Objects.equals(target, name)) { variant = 0; error = null; } name = target; recordStamp = stamp; render(); } private void render() { removeAll(); if (name == null) { add(note("Attack a monster to see what it drops.")); refreshLayout(); return; } add(LiveFightCard.sectionLabel("YOUR LOOT")); LootRecord record = records.apply(name); if (record != null && record.getKills() + record.getItems().size() > 0) { add(new LootTargetRow(record, record.getTotalValue(), itemManager, openLoot)); } else { add(note("No kills recorded yet.")); } DropTable table = TABLES.get(name); add(LiveFightCard.sectionLabel("DROP TABLE (WIKI)")); if (table == null) { if (error != null) { add(note("Could not load drop table: " + error)); } else { add(note("Loading...")); load(name); } } else if (table.isEmpty()) { add(note("The wiki has no drop table for " + name + ".")); } else { if (table.variants.size() > 1) { JComboBox box = new JComboBox<>(table.variants.toArray(new DropTable.Variant[0])); box.setSelectedIndex(Math.min(variant, table.variants.size() - 1)); box.setFocusable(false); box.addActionListener(e -> { variant = box.getSelectedIndex(); render(); }); add(box); } DropTable.Variant v = table.variants.get(Math.min(variant, table.variants.size() - 1)); for (DropTable.Rate rate : DropTable.rates(v).values()) { add(row(rate)); } } refreshLayout(); } private void refreshLayout() { revalidate(); repaint(); } private JPanel row(DropTable.Rate rate) { JPanel row = new JPanel(new BorderLayout(6, 0)) { @Override protected void paintComponent(java.awt.Graphics g0) { java.awt.Graphics2D g = Ui.prepare(g0); g.setColor(Ui.CARD); g.fillRoundRect(0, 0, getWidth(), getHeight(), 6, 6); g.dispose(); } }; row.setOpaque(false); row.setBorder(BorderFactory.createEmptyBorder(1, 3, 1, 7)); row.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); row.setToolTipText(rate.item + " ยท click to open the wiki"); row.addMouseListener(new java.awt.event.MouseAdapter() { @Override public void mousePressed(java.awt.event.MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { openWiki.accept(rate.item); } } }); JLabel icon = new JLabel(); icon.setPreferredSize(new Dimension(32, 28)); icon.setHorizontalAlignment(JLabel.CENTER); Integer id = IDS.get(DropTable.key(rate.item)); if (id != null && id > 0) { itemManager.getImage(id).addTo(icon); } row.add(icon, BorderLayout.WEST); JLabel label = new JLabel(rate.item); label.setFont(net.runelite.client.ui.FontManager.getRunescapeSmallFont()); label.setForeground(Ui.TEXT); row.add(label, BorderLayout.CENTER); JLabel chance = new JLabel(rate.text); chance.setFont(net.runelite.client.ui.FontManager.getRunescapeSmallFont()); chance.setForeground(rarityColor(rate.perKill)); row.add(chance, BorderLayout.EAST); return row; } private static java.awt.Color rarityColor(double perKill) { if (Double.isNaN(perKill)) { return Ui.MUTED; } if (perKill >= 1) { return Ui.TEXT; } if (perKill >= 1 / 100.0) { return Ui.UP; } if (perKill >= 1 / 1000.0) { return Ui.GOLD; } return Ui.DOWN; } private void load(String target) { if (loading || api == null || executor == null) { return; } loading = true; executor.execute(() -> { String err = null; try { DropTable table = api.fetchDropTable(target); // icons: item ids by name (tradeable items only) for (DropTable.Variant v : table.variants) { for (DropTable.Rate r : DropTable.rates(v).values()) { IDS.computeIfAbsent(DropTable.key(r.item), k -> findId(r.item)); } } TABLES.put(target, table); } catch (Exception e) { log.debug("drop table for {} failed", target, e); err = e.getMessage() == null ? "wiki not reachable" : e.getMessage(); } String failure = err; SwingUtilities.invokeLater(() -> { loading = false; if (target.equals(name)) { error = failure; render(); } }); }); } private int findId(String item) { try { for (ItemPrice p : itemManager.search(item)) { if (p.getName().equalsIgnoreCase(item)) { return p.getId(); } } } catch (RuntimeException e) { log.debug("item search for {} failed", item, e); } return -1; } private static JLabel note(String text) { JLabel l = new JLabel("" + text.replace("&", "&").replace("<", "<") + ""); l.setFont(net.runelite.client.ui.FontManager.getRunescapeSmallFont()); l.setForeground(Ui.FAINT); return l; } }