chore: init

This commit is contained in:
Hikyu
2026-09-18 17:18:57 +02:00
commit 15463c2e3c
200 changed files with 51032 additions and 0 deletions
@@ -0,0 +1,284 @@
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<String, DropTable> TABLES = new ConcurrentHashMap<>();
/** item name (lower case) -> item id, -1 if not found */
private static final Map<String, Integer> IDS = new ConcurrentHashMap<>();
private final WikiApi api;
private final ExecutorService executor;
private final ItemManager itemManager;
private final Function<String, LootRecord> records;
private final Consumer<LootRecord> openLoot;
private final Consumer<String> 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<String, LootRecord> records,
Consumer<LootRecord> openLoot, Consumer<String> 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<DropTable.Variant> 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("<html><body style='width:180px'>" + text.replace("&", "&amp;").replace("<", "&lt;") + "</html>");
l.setFont(net.runelite.client.ui.FontManager.getRunescapeSmallFont());
l.setForeground(Ui.FAINT);
return l;
}
}