chore: init
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
package com.example.essentialsplus.loot.ui;
|
||||
|
||||
import com.example.essentialsplus.loot.LootRecord;
|
||||
import com.example.essentialsplus.loot.LootTrackerStore;
|
||||
import com.example.essentialsplus.loot.TrackedLootItem;
|
||||
import com.example.essentialsplus.ui.TimeFilter;
|
||||
import com.example.essentialsplus.ui.Ui;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.SwingUtilities;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.util.QuantityFormatter;
|
||||
|
||||
/**
|
||||
* Clean entry card for Loot Tracker on the Essentials Plus Overview panel.
|
||||
* Height strictly bounded to 110-120px to prevent layout bloat.
|
||||
*/
|
||||
public class LootSummaryCard extends JComponent
|
||||
{
|
||||
private final LootTrackerStore store;
|
||||
private final ItemManager itemManager;
|
||||
private final Runnable onClick;
|
||||
private final TimeFilter timeFilter;
|
||||
private int sourceCount = 0;
|
||||
|
||||
private boolean hover = false;
|
||||
private long totalValue = 0;
|
||||
private int totalKills = 0;
|
||||
private String topSource = "";
|
||||
private long topSourceVal = 0;
|
||||
/** 0 = total value, 1 = latest drop, 2 = top source (overview right-click menu) */
|
||||
private int mode;
|
||||
private String latestSource = "";
|
||||
private long latestVal;
|
||||
private long latestAt;
|
||||
private int latestKills;
|
||||
|
||||
/** live fight shown instead of the loot figures (overview right-click options) */
|
||||
private com.example.essentialsplus.loot.gear.FightSnapshot fight = com.example.essentialsplus.loot.gear.FightSnapshot.LOGGED_OUT;
|
||||
private boolean showTarget = true;
|
||||
private boolean keepLast;
|
||||
private boolean showHp = true;
|
||||
private boolean showDps = true;
|
||||
private boolean showSession = true;
|
||||
private boolean showKills = true;
|
||||
|
||||
public LootSummaryCard(LootTrackerStore store, ItemManager itemManager, TimeFilter timeFilter, Runnable onClick)
|
||||
{
|
||||
this.store = store;
|
||||
this.timeFilter = timeFilter;
|
||||
this.itemManager = itemManager;
|
||||
this.onClick = onClick;
|
||||
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
hover = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
{
|
||||
hover = false;
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e)
|
||||
{
|
||||
if (SwingUtilities.isLeftMouseButton(e) && onClick != null)
|
||||
{
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
store.addChangeListener(this::refresh);
|
||||
timeFilter.addListener(this::refresh);
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh()
|
||||
{
|
||||
List<LootRecord> records = store.getRecords(timeFilter);
|
||||
totalValue = LootTrackerStore.totalValue(records);
|
||||
totalKills = LootTrackerStore.totalKills(records);
|
||||
sourceCount = records.size();
|
||||
|
||||
LootRecord top = records.stream()
|
||||
.max(Comparator.comparingLong(LootRecord::getTotalValue))
|
||||
.orElse(null);
|
||||
|
||||
LootRecord latest = records.stream()
|
||||
.filter(r -> r.getTotalValue() > 0)
|
||||
.max(Comparator.comparingLong(LootRecord::getLastSeen))
|
||||
.orElse(null);
|
||||
latestSource = latest == null ? "" : latest.getName();
|
||||
latestVal = latest == null ? 0 : latest.getTotalValue();
|
||||
latestAt = latest == null ? 0 : latest.getLastSeen();
|
||||
latestKills = latest == null ? 0 : latest.getKills();
|
||||
|
||||
if (top != null && top.getTotalValue() > 0)
|
||||
{
|
||||
topSource = top.getName();
|
||||
topSourceVal = top.getTotalValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
topSource = "";
|
||||
topSourceVal = 0;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setFight(com.example.essentialsplus.loot.gear.FightSnapshot snap)
|
||||
{
|
||||
boolean was = fightShown();
|
||||
fight = snap;
|
||||
if (was || fightShown())
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFightOptions(boolean showTarget, boolean keepLast, boolean showHp, boolean showDps, boolean showSession, boolean showKills)
|
||||
{
|
||||
this.showTarget = showTarget;
|
||||
this.keepLast = keepLast;
|
||||
this.showHp = showHp;
|
||||
this.showDps = showDps;
|
||||
this.showSession = showSession;
|
||||
this.showKills = showKills;
|
||||
repaint();
|
||||
}
|
||||
|
||||
private boolean fightShown()
|
||||
{
|
||||
com.example.essentialsplus.loot.gear.FightSnapshot.Target t = fight.loggedIn ? fight.target : null;
|
||||
return showTarget && t != null && (t.live || keepLast);
|
||||
}
|
||||
|
||||
public void setMode(int mode)
|
||||
{
|
||||
this.mode = mode;
|
||||
repaint();
|
||||
}
|
||||
|
||||
private static String ago(long t)
|
||||
{
|
||||
long d = System.currentTimeMillis() - t;
|
||||
return d < Ui.MINUTE ? "just now" : d < Ui.HOUR ? d / Ui.MINUTE + "m ago" : d < Ui.DAY ? d / Ui.HOUR + "h ago" : d / Ui.DAY + "d ago";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize()
|
||||
{
|
||||
return new Dimension(net.runelite.client.ui.PluginPanel.PANEL_WIDTH - 16, 120);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize()
|
||||
{
|
||||
return new Dimension(Integer.MAX_VALUE, 130);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize()
|
||||
{
|
||||
return new Dimension(10, 110);
|
||||
}
|
||||
|
||||
/** Current target: name, badge, health bar, own max hit / dps, fight time and damage. */
|
||||
private void paintFight(Graphics2D g, int pad, int right, FontMetrics headerFm)
|
||||
{
|
||||
com.example.essentialsplus.loot.gear.FightSnapshot.Target t = fight.target;
|
||||
Font bold = FontManager.getRunescapeBoldFont();
|
||||
g.setFont(FontManager.getRunescapeSmallFont());
|
||||
FontMetrics sfm = g.getFontMetrics();
|
||||
String badge = t.dead ? "DEAD" : t.live ? "FIGHTING" : "LAST TARGET";
|
||||
Color bc = t.dead ? Ui.MUTED : t.live ? Ui.DOWN : Ui.FAINT;
|
||||
int bw = sfm.stringWidth(badge) + 6;
|
||||
int bx = right - 14 - bw;
|
||||
g.setColor(Ui.alpha(bc, 50));
|
||||
g.fillRoundRect(bx, 6, bw, sfm.getAscent() + 3, 4, 4);
|
||||
g.setColor(bc);
|
||||
g.drawString(badge, bx + 3, 7 + sfm.getAscent());
|
||||
|
||||
int y = 24;
|
||||
g.setFont(bold.deriveFont(18f));
|
||||
FontMetrics nfm = g.getFontMetrics();
|
||||
g.setColor(Color.WHITE);
|
||||
String name = t.name + " (" + t.combatLevel + ")";
|
||||
g.drawString(Ui.ellipsize(nfm, name, right - pad), pad, y + nfm.getAscent());
|
||||
y += nfm.getHeight() + 2;
|
||||
|
||||
if (showHp)
|
||||
{
|
||||
int barW = right - pad;
|
||||
g.setColor(new Color(60, 20, 20));
|
||||
g.fillRoundRect(pad, y, barW, 12, 4, 4);
|
||||
double ratio = t.healthRatio >= 0 && t.healthScale > 0 ? (double) t.healthRatio / t.healthScale : t.dead ? 0 : 1;
|
||||
g.setColor(t.live ? new Color(40, 170, 70) : new Color(60, 110, 70));
|
||||
g.fillRoundRect(pad, y, (int) Math.round(barW * ratio), 12, 4, 4);
|
||||
String hp = t.monster != null && t.monster.hitpoints > 0
|
||||
? (int) Math.round(t.monster.hitpoints * ratio) + " / " + t.monster.hitpoints
|
||||
: String.format(java.util.Locale.US, "%.0f%%", ratio * 100);
|
||||
g.setFont(FontManager.getRunescapeSmallFont());
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawString(hp, pad + (barW - sfm.stringWidth(hp)) / 2, y + 10);
|
||||
y += 16;
|
||||
}
|
||||
|
||||
g.setFont(FontManager.getRunescapeSmallFont());
|
||||
if (showDps)
|
||||
{
|
||||
com.example.essentialsplus.loot.gear.DpsCalc.Result r = com.example.essentialsplus.loot.gear.DpsCalc.calc(fight.worn, fight.setup,
|
||||
t.calcMonster(), com.example.essentialsplus.loot.gear.DpsCalc.choiceFor(fight.worn, fight.worn, fight.setup));
|
||||
y += sfm.getAscent() + 2;
|
||||
int x = pad;
|
||||
x = pair(g, sfm, x, y, "Max ", String.valueOf(r.maxHit), Ui.GOLD);
|
||||
x = pair(g, sfm, x, y, " DPS ", String.format(java.util.Locale.US, "%.2f", r.dps), Ui.UP);
|
||||
pair(g, sfm, x, y, " Hit ", String.format(java.util.Locale.US, "%.0f%%", r.hitChance * 100), Ui.TEXT);
|
||||
}
|
||||
if (showSession && fight.session != null)
|
||||
{
|
||||
com.example.essentialsplus.loot.gear.FightSnapshot.Session se = fight.session;
|
||||
y += sfm.getAscent() + 4;
|
||||
int sec = (int) Math.round(se.durationTicks * 0.6);
|
||||
int x = pad;
|
||||
x = pair(g, sfm, x, y, "", sec >= 60 ? sec / 60 + "m " + sec % 60 + "s" : sec + "s", Ui.TEXT);
|
||||
x = pair(g, sfm, x, y, " Dealt ", String.valueOf(se.damageDealt), Ui.UP);
|
||||
pair(g, sfm, x, y, " Taken ", String.valueOf(se.damageTaken), se.damageTaken > 0 ? Ui.DOWN : Ui.TEXT);
|
||||
}
|
||||
else if (showKills && totalKills > 0)
|
||||
{
|
||||
y += sfm.getAscent() + 4;
|
||||
g.setColor(Ui.FAINT);
|
||||
g.drawString(Ui.gp(totalValue) + " loot • " + QuantityFormatter.formatNumber(totalKills) + " kills • " + timeFilter.getLabel(), pad, y);
|
||||
}
|
||||
}
|
||||
|
||||
private static int pair(Graphics2D g, FontMetrics fm, int x, int y, String label, String value, Color color)
|
||||
{
|
||||
g.setColor(Ui.MUTED);
|
||||
g.drawString(label, x, y);
|
||||
x += fm.stringWidth(label);
|
||||
g.setColor(color);
|
||||
g.drawString(value, x, y);
|
||||
return x + fm.stringWidth(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g0)
|
||||
{
|
||||
Graphics2D g = Ui.prepare(g0);
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
|
||||
g.setColor(hover ? Ui.CARD_HOVER : Ui.CARD);
|
||||
g.fillRoundRect(0, 0, w, h, 8, 8);
|
||||
|
||||
int pad = 10;
|
||||
int right = w - pad;
|
||||
Font bold = FontManager.getRunescapeBoldFont();
|
||||
Font small = FontManager.getRunescapeSmallFont();
|
||||
|
||||
// Header: "Loot Tracker" + chevron ›
|
||||
g.setFont(bold);
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
g.setColor(Ui.ACCENT);
|
||||
g.drawString("Fights & Loot", pad, 6 + fm.getAscent());
|
||||
|
||||
g.setFont(bold.deriveFont(14f));
|
||||
g.setColor(hover ? Ui.ACCENT : Ui.FAINT);
|
||||
Ui.drawRight(g, "›", right, 6 + fm.getAscent());
|
||||
|
||||
if (fightShown())
|
||||
{
|
||||
paintFight(g, pad, right, fm);
|
||||
Ui.paintDragOutline(g, this);
|
||||
g.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (totalKills > 0 && showKills)
|
||||
{
|
||||
g.setFont(small);
|
||||
g.setColor(Ui.MUTED);
|
||||
String killsText = QuantityFormatter.formatNumber(totalKills) + " kills • " + timeFilter.getLabel();
|
||||
FontMetrics sfm = g.getFontMetrics();
|
||||
int titleW = fm.stringWidth("Fights & Loot");
|
||||
int killsW = sfm.stringWidth(killsText);
|
||||
int maxKillsX = right - 14;
|
||||
if (maxKillsX - killsW > pad + titleW + 8)
|
||||
{
|
||||
Ui.drawRight(g, killsText, maxKillsX, 6 + fm.getAscent());
|
||||
}
|
||||
}
|
||||
|
||||
if (mode != 0 && totalKills > 0)
|
||||
{
|
||||
boolean latestMode = mode == 1;
|
||||
String src = latestMode ? latestSource : topSource;
|
||||
long val = latestMode ? latestVal : topSourceVal;
|
||||
g.setFont(bold.deriveFont(26f));
|
||||
FontMetrics bfm = g.getFontMetrics();
|
||||
int valueBase = 22 + bfm.getAscent();
|
||||
g.setColor(val > 0 ? Ui.GOLD : Ui.FAINT);
|
||||
g.drawString(Ui.gp(val), pad, valueBase);
|
||||
g.setFont(small);
|
||||
FontMetrics sfm = g.getFontMetrics();
|
||||
int line1 = valueBase + sfm.getAscent() + 4;
|
||||
g.setColor(Ui.MUTED);
|
||||
String prefix = latestMode ? "Latest: " : "Top: ";
|
||||
g.drawString(prefix, pad, line1);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawString(src.isEmpty() ? "-" : src, pad + sfm.stringWidth(prefix), line1);
|
||||
int line2 = line1 + sfm.getAscent() + 4;
|
||||
g.setColor(Ui.FAINT);
|
||||
g.drawString(latestMode && latestAt > 0 ? ago(latestAt) + " • " + latestKills + " kills" : "Total " + Ui.gp(totalValue) + " • " + timeFilter.getLabel(),
|
||||
pad, line2);
|
||||
Ui.paintDragOutline(g, this);
|
||||
g.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
// Big Value
|
||||
Font big = bold.deriveFont(26f);
|
||||
g.setFont(big);
|
||||
FontMetrics bfm = g.getFontMetrics();
|
||||
int valueBase = 22 + bfm.getAscent();
|
||||
g.setColor(totalValue > 0 ? Ui.GOLD : Ui.FAINT);
|
||||
g.drawString(totalValue > 0 ? Ui.gp(totalValue) : "0 gp", pad, valueBase);
|
||||
|
||||
// Subtitle & highlight
|
||||
g.setFont(small);
|
||||
FontMetrics sfm = g.getFontMetrics();
|
||||
int line1 = valueBase + sfm.getAscent() + 4;
|
||||
|
||||
if (totalKills == 0)
|
||||
{
|
||||
g.setColor(Ui.FAINT);
|
||||
g.drawString((timeFilter.start() > 0 ? "No loot " + timeFilter.inLabel() : "No loot recorded yet") + " • Click to open", pad, line1);
|
||||
}
|
||||
else
|
||||
{
|
||||
g.setColor(Ui.TEXT);
|
||||
String summaryDesc = "Tracked drops from " + sourceCount + " sources";
|
||||
g.drawString(summaryDesc, pad, line1);
|
||||
|
||||
if (!topSource.isEmpty())
|
||||
{
|
||||
int line2 = line1 + sfm.getAscent() + 4;
|
||||
g.setColor(Ui.MUTED);
|
||||
g.drawString("Top: ", pad, line2);
|
||||
|
||||
int topW = sfm.stringWidth("Top: ");
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawString(topSource, pad + topW, line2);
|
||||
|
||||
int nameW = sfm.stringWidth(topSource);
|
||||
g.setColor(Ui.GOLD);
|
||||
g.drawString(" (" + Ui.gp(topSourceVal) + ")", pad + topW + nameW, line2);
|
||||
}
|
||||
}
|
||||
|
||||
Ui.paintDragOutline(g, this);
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user