diff --git a/res/gfx/hud/wnd/lg/qlistbtnd.res b/res/gfx/hud/wnd/lg/qlistbtnd.res new file mode 100644 index 000000000..19fcb6244 Binary files /dev/null and b/res/gfx/hud/wnd/lg/qlistbtnd.res differ diff --git a/res/gfx/hud/wnd/lg/qlistbtnh.res b/res/gfx/hud/wnd/lg/qlistbtnh.res new file mode 100644 index 000000000..d3b422d98 Binary files /dev/null and b/res/gfx/hud/wnd/lg/qlistbtnh.res differ diff --git a/res/gfx/hud/wnd/lg/qlistbtnu.res b/res/gfx/hud/wnd/lg/qlistbtnu.res new file mode 100644 index 000000000..af049f255 Binary files /dev/null and b/res/gfx/hud/wnd/lg/qlistbtnu.res differ diff --git a/src/haven/ExtInventory.java b/src/haven/ExtInventory.java new file mode 100644 index 000000000..f9308f268 --- /dev/null +++ b/src/haven/ExtInventory.java @@ -0,0 +1,857 @@ +package haven; + +import haven.res.ui.stackinv.ItemStack; +import haven.res.ui.tt.q.qbuff.QBuff; +import haven.res.ui.tt.q.quality.Quality; + +import java.awt.Color; +import java.util.*; + +import static haven.Inventory.sqsz; + +public class ExtInventory extends Widget { + private static final int MARGIN = UI.scale(5); + private static final int PANEL_W = UI.scale(180); + private static final int HEADER_H = UI.scale(20); + private static final int ROW_H = UI.scale(20); + private static final int MIN_ROWS = 8; + + private static final Color BG = new Color(16, 16, 16, 128); + private static final Color ROW_EVEN = new Color(255, 255, 255, 16); + private static final Color ROW_ODD = new Color(255, 255, 255, 32); + private static final Color HEADER = new Color(255, 255, 255, 64); + + private static final boolean DEBUG_EXTINV = false; + + public final Inventory inv; + private final QualityPanel panel; + + private Coord lastLayoutInvSz = null; + private boolean lastLayoutExpanded = false; + + private boolean expanded = false; + private volatile boolean pendingUnresolvedItems = false; + + public ExtInventory(Inventory inv) { + super(Coord.z); + this.inv = inv; + add(inv, Coord.z); + this.panel = add(new QualityPanel(inv), new Coord(inv.sz.x + MARGIN, 0)); + setExpanded(false); + relayout(); + } + + private static void dbg(String fmt, Object... args) { + if (DEBUG_EXTINV) { + System.out.println("[ExtInventory] " + String.format(fmt, args)); + } + } + + private static String qstr(WItem w) { + Double q = qualityOf(w); + return (q == null) ? "q?" : String.format("q%.1f", q); + } + + private static String itemdbg(WItem w) { + String name; + try { + name = w.item.getname(); + } catch (Loading l) { + name = "???"; + } + + String res; + try { + res = w.item.res.get().name; + } catch (Loading l) { + res = "???"; + } + + return String.format("%s [%s] %s id=%d infoseq=%d", + name, res, qstr(w), w.item.wdgid(), w.item.infoseq); + } + + public void togglePanel() { + setExpanded(!expanded); + } + + public void setExpanded(boolean v) { + expanded = v; + panel.visible = v; + if (v) { + panel.refresh(); + } + relayout(); + } + + private void relayout() { + if (!expanded) { + panel.hide(); + resize(inv.sz); + + Window wnd = getparent(Window.class); + if (wnd != null) { + wnd.pack(); + } + lastLayoutInvSz = new Coord(inv.sz); + lastLayoutExpanded = expanded; + return; + } + + panel.show(); + int panelH = Math.max(inv.sz.y, HEADER_H + (ROW_H * MIN_ROWS)); + panel.resize(new Coord(PANEL_W, panelH)); + panel.move(new Coord(inv.sz.x + MARGIN, 0)); + resize(new Coord(inv.sz.x + MARGIN + panel.sz.x, Math.max(inv.sz.y, panel.sz.y))); + + Window wnd = getparent(Window.class); + if (wnd != null) { + wnd.pack(); + } + + lastLayoutInvSz = new Coord(inv.sz); + lastLayoutExpanded = expanded; + } + + @Override + public void tick(double dt) { + super.tick(dt); + + boolean invSizeChanged = (lastLayoutInvSz == null) || !lastLayoutInvSz.equals(inv.sz); + boolean expandedChanged = (lastLayoutExpanded != expanded); + + boolean panelGeometryChanged = expanded && + ((panel.c.x != inv.sz.x + MARGIN) || + (panel.sz.y != Math.max(inv.sz.y, HEADER_H + (ROW_H * MIN_ROWS)))); + + if (invSizeChanged || expandedChanged || panelGeometryChanged) { + relayout(); + } + } + + private static Inventory inventory(Widget w) { + if (w instanceof Inventory) + return (Inventory) w; + if (w instanceof ExtInventory) + return ((ExtInventory) w).inv; + return null; + } + + private static boolean isInPlayerInventory(WItem item) { + Window window = item.getparent(Window.class); + return window != null && Objects.equals("Inventory", window.cap); + } + + private static List getExternalInventoryIds(UI ui) { + List externalInventoryIds = ui.gui.getAllWindows() + .stream() + .flatMap(w -> w.children().stream()) + .map(ExtInventory::inventory) + .filter(Objects::nonNull) + .filter(i -> { + Window window = i.getparent(Window.class); + return window != null && !Inventory.PLAYER_INVENTORY_NAMES.contains(window.cap); + }) + .map(Widget::wdgid) + .collect(java.util.stream.Collectors.toList()); + + List stockpileIds = ui.gui.getAllWindows() + .stream() + .map(w -> w.getchild(ISBox.class)) + .filter(Objects::nonNull) + .map(Widget::wdgid) + .collect(java.util.stream.Collectors.toList()); + + externalInventoryIds.addAll(stockpileIds); + return externalInventoryIds; + } + + private long inventoryStateStamp() { + long sig = 1469598103934665603L; + + for (WItem w : inv.getAllItems()) { + sig ^= w.item.wdgid(); + sig *= 1099511628211L; + + sig ^= w.item.infoseq; + sig *= 1099511628211L; + + Double q = qualityOf(w); + sig ^= (q == null) ? 0L : Double.doubleToLongBits(q); + sig *= 1099511628211L; + + Widget contents = w.item.contents; + if (contents instanceof ItemStack) { + ItemStack stack = (ItemStack) contents; + sig ^= stack.order.size(); + sig *= 1099511628211L; + + for (GItem gi : stack.order) { + sig ^= gi.wdgid(); + sig *= 1099511628211L; + } + } + } + + return sig; + } + + private static String rawName(WItem w) { + try { + String n = w.item.getname(); + if (n == null) + return null; + n = n.trim(); + if (n.isEmpty()) + return null; + if ("it's null".equals(n) || "exception".equals(n)) + return null; + return n; + } catch (Loading l) { + return null; + } catch (Exception e) { + return null; + } + } + + private static boolean isStackWrapperName(String name) { + return name != null && name.endsWith(", stack of"); + } + + private static Double qualityOf(WItem w) { + QBuff qb = w.item.getQBuff(); + if (qb != null) + return qb.q; + + try { + Quality q = ItemInfo.find(Quality.class, w.item.info()); + if (q != null) + return q.q; + } catch (Loading ignored) { + } + + return null; + } + + private static int byQuality(WItem a, WItem b) { + Double qa = qualityOf(a); + Double qb = qualityOf(b); + + if (Objects.equals(qa, qb)) + return 0; + if (qa == null) + return 1; + if (qb == null) + return -1; + + return Double.compare(qb, qa); + } + + private static int byReverseQuality(WItem a, WItem b) { + return byQuality(b, a); + } + + private static void sortByQuality(List items, boolean reverse) { + if (reverse) { + items.sort(ExtInventory::byReverseQuality); + } else { + items.sort(ExtInventory::byQuality); + } + } + + private static boolean isResolvedForList(WItem w) { + String name = rawName(w); + if (name == null) + return false; + if (isStackWrapperName(name)) + return false; + if (w.item.infoseq <= 0) + return false; + return qualityOf(w) != null; + } + + private Map buildTopLevelMap() { + Map map = new IdentityHashMap<>(); + + for (WItem top : inv.getAllItems()) { + map.put(top, top); + + Widget contents = top.item.contents; + if (contents instanceof ItemStack) { + ItemStack stack = (ItemStack) contents; + for (GItem gi : stack.order) { + WItem child = stack.wmap.get(gi); + if (child != null) { + map.put(child, top); + } + } + } + } + + return map; + } + + private static void processGroup(List items, boolean reverse, String action, Object... args) { + dbg("processGroup action=%s count=%d reverse=%s", action, items.size(), reverse); + + sortByQuality(items, reverse); + + for (WItem item : items) { + if (item.parent != null) { + dbg(" send %s -> %s", action, itemdbg(item)); + item.item.wdgmsg(action, args); + } + } + } + + private static void sendInvxf2(GItem item, int amount, int externalInventoryId) { + Object[] invxf2Args = new Object[3]; + invxf2Args[0] = 0; + invxf2Args[1] = amount; + invxf2Args[2] = externalInventoryId; + item.wdgmsg("invxf2", invxf2Args); + } + + private void transferGroupSmart(List items, boolean reverse) { + List externalInventoryIds = getExternalInventoryIds(ui); + + if (externalInventoryIds.isEmpty()) { + processGroup(items, reverse, "transfer", sqsz.div(2)); + return; + } + + List ordered = new ArrayList<>(items); + sortByQuality(ordered, reverse); + + Map topLevelMap = buildTopLevelMap(); + LinkedHashMap> grouped = new LinkedHashMap<>(); + + for (WItem item : ordered) { + WItem top = topLevelMap.getOrDefault(item, item); + grouped.computeIfAbsent(top, k -> new ArrayList<>()).add(item); + } + + dbg("transferGroupSmart groups=%d reverse=%s", grouped.size(), reverse); + + for (Map.Entry> e : grouped.entrySet()) { + WItem top = e.getKey(); + List members = e.getValue(); + + Widget contents = top.item.contents; + + if (contents instanceof ItemStack && isInPlayerInventory(top)) { + ItemStack stack = (ItemStack) contents; + int amount = Math.min(members.size(), stack.order.size()); + boolean wholeStackSelected = (amount == stack.order.size()); + + dbg(" stack transfer top=%s members=%d stack.order=%d amount=%d whole=%s", + itemdbg(top), members.size(), stack.order.size(), amount, wholeStackSelected); + + if (amount > 0 && !stack.order.isEmpty()) { + if (wholeStackSelected) { + for (Integer externalInventoryId : externalInventoryIds) { + dbg(" send whole-stack invxf2 via first child id=%d target=%d amount=%d", + stack.order.get(0).wdgid(), externalInventoryId, amount); + sendInvxf2(stack.order.get(0), amount, externalInventoryId); + } + } else { + List exact = new ArrayList<>(members); + exact.sort((a, b) -> { + int ia = stack.order.indexOf(a.item); + int ib = stack.order.indexOf(b.item); + return Integer.compare(ib, ia); + }); + + dbg(" mixed stack exact ids ordered=%s", + exact.stream().map(w -> w.item.wdgid()).collect(java.util.stream.Collectors.toList())); + + for (WItem child : exact) { + if (child.parent != null) { + for (Integer externalInventoryId : externalInventoryIds) { + dbg(" send mixed-stack exact invxf2 -> %s target=%d", + itemdbg(child), externalInventoryId); + sendInvxf2(child.item, 1, externalInventoryId); + } + } + } + } + } + } else { + dbg(" loose transfer top=%s members=%d", itemdbg(top), members.size()); + + for (WItem item : members) { + if (item.parent != null) { + dbg(" send loose transfer -> %s", itemdbg(item)); + item.item.wdgmsg("transfer", sqsz.div(2)); + } + } + } + } + } + + private List snapshotMatchingItems(String name, String resname, Double q, Grouping grouping) { + List flat = new ArrayList<>(); + for (WItem w : inv.getAllItems()) { + collectItemsForSnapshot(flat, w); + } + + List out = new ArrayList<>(); + for (WItem w : flat) { + String wn = safeName(w); + String wr = safeResname(w); + Double wq = quantizeQ(qualityOf(w), grouping); + + if (Objects.equals(name, wn) && + Objects.equals(resname, wr) && + Objects.equals(q, wq)) { + out.add(w); + } + } + return out; + } + + private void collectItemsForSnapshot(List out, WItem w) { + String name = rawName(w); + Widget contents = w.item.contents; + + if ((contents instanceof ItemStack) || isStackWrapperName(name)) { + if (contents instanceof ItemStack) { + ItemStack stack = (ItemStack) contents; + if (!stack.order.isEmpty()) { + for (GItem gi : stack.order) { + WItem sw = stack.wmap.get(gi); + if (sw == null) { + pendingUnresolvedItems = true; + continue; + } + if (!isResolvedForList(sw)) { + pendingUnresolvedItems = true; + continue; + } + out.add(sw); + } + } else { + pendingUnresolvedItems = true; + } + } + return; + } + + if (!isResolvedForList(w)) { + pendingUnresolvedItems = true; + return; + } + + out.add(w); + } + + private static String safeName(WItem w) { + String n = rawName(w); + return (n == null) ? "???" : n; + } + + private static String safeResname(WItem w) { + try { + return w.item.res.get().name; + } catch (Loading l) { + return "???"; + } + } + + private static Double quantizeQ(Double q, Grouping g) { + if (q == null || g == Grouping.NONE) return q; + if (g == Grouping.Q) return q; + + q = Math.floor(q); + if (g == Grouping.Q1) return q; + if (g == Grouping.Q5) return q - (q % 5.0); + if (g == Grouping.Q10) return q - (q % 10.0); + return q; + } + + private void transferRowWithRetries(String name, String resname, Double q, Grouping grouping, boolean reverse) { + for (int pass = 0; pass < 5; pass++) { + if (pass > 0) { + try { + Thread.sleep(120); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + + pendingUnresolvedItems = false; + List matching = snapshotMatchingItems(name, resname, q, grouping); + + dbg("retry pass=%d row=%s/%s q=%s matching=%d pending=%s", + pass, name, resname, q, matching.size(), pendingUnresolvedItems); + + if (matching.isEmpty()) { + return; + } + + transferGroupSmart(matching, reverse); + + if (!pendingUnresolvedItems) { + return; + } + } + } + + private enum Grouping { + NONE("Type"), + Q("Quality"), + Q1("Q1"), + Q5("Q5"), + Q10("Q10"); + + public final String label; + + Grouping(String label) { + this.label = label; + } + + public Grouping next() { + Grouping[] vals = values(); + return vals[(ordinal() + 1) % vals.length]; + } + } + + private static final class GroupKey { + final String name; + final String resname; + final Double q; + + GroupKey(String name, String resname, Double q) { + this.name = name; + this.resname = resname; + this.q = q; + } + } + + private static final class GroupRow { + final GroupKey key; + final List items; + final WItem highSample; + final WItem lowSample; + final Double avgQ; + private String text; + private Tex tex; + + GroupRow(GroupKey key, List items) { + this.key = key; + this.items = items; + this.highSample = highestQ(items); + this.lowSample = lowestQ(items); + this.avgQ = averageQ(items); + } + + private static WItem highestQ(List items) { + WItem best = items.get(0); + for (WItem w : items) { + if (byQuality(w, best) < 0) { + best = w; + } + } + return best; + } + + private static WItem lowestQ(List items) { + WItem worst = items.get(0); + for (WItem w : items) { + if (byQuality(w, worst) > 0) { + worst = w; + } + } + return worst; + } + + private static Double averageQ(List items) { + double sum = 0.0; + int n = 0; + for (WItem w : items) { + Double q = qualityOf(w); + if (q != null) { + sum += q; + n++; + } + } + return (n == 0) ? null : (sum / n); + } + + String text() { + if (text == null) { + String qtxt = (avgQ == null) ? "q?" : String.format("q%.1f", avgQ); + text = String.format("x%d %s %s", items.size(), key.name, qtxt); + } + return text; + } + + Tex tex() { + if (tex == null) { + tex = Text.render(text()).tex(); + } + return tex; + } + } + + private static final class GroupRowComparator implements Comparator { + @Override + public int compare(GroupRow a, GroupRow b) { + int cmp = a.key.name.compareToIgnoreCase(b.key.name); + if (cmp != 0) return cmp; + + if (a.key.q == null && b.key.q == null) return a.key.resname.compareTo(b.key.resname); + if (a.key.q == null) return 1; + if (b.key.q == null) return -1; + + cmp = -Double.compare(a.key.q, b.key.q); + if (cmp != 0) return cmp; + + return a.key.resname.compareTo(b.key.resname); + } + } + + private final class QualityPanel extends Widget implements DTarget { + private final Inventory inv; + private final List rows = new ArrayList<>(); + + private Grouping grouping = Grouping.Q1; + private int scroll = 0; + private Tex headerTex = null; + private long lastStamp = Long.MIN_VALUE; + + QualityPanel(Inventory inv) { + super(new Coord(PANEL_W, Math.max(inv.sz.y, HEADER_H + (ROW_H * MIN_ROWS)))); + this.inv = inv; + rebuild(); + } + + private void rebuildHeader() { + headerTex = Text.render("Group: " + grouping.label + " (click to cycle)").tex(); + } + + private void rebuild() { + rows.clear(); + pendingUnresolvedItems = false; + + Map> grouped = new LinkedHashMap<>(); + Map keys = new LinkedHashMap<>(); + + List flat = new ArrayList<>(); + for (WItem w : inv.getAllItems()) { + collectItems(flat, w); + } + + for (WItem w : flat) { + String name = ExtInventory.safeName(w); + String resname = ExtInventory.safeResname(w); + Double q = ExtInventory.quantizeQ(qualityOf(w), grouping); + + String bucket; + if (grouping == Grouping.NONE) { + bucket = name + "\u0000" + resname; + } else { + bucket = name + "\u0000" + resname + "\u0000" + ((q == null) ? "null" : String.format("%.4f", q)); + } + + keys.put(bucket, new GroupKey(name, resname, q)); + grouped.computeIfAbsent(bucket, k -> new ArrayList<>()).add(w); + } + + for (Map.Entry> e : grouped.entrySet()) { + rows.add(new GroupRow(keys.get(e.getKey()), e.getValue())); + } + + Collections.sort(rows, new GroupRowComparator()); + scroll = Math.max(0, Math.min(scroll, maxScroll())); + rebuildHeader(); + } + + private void refresh() { + rebuild(); + lastStamp = ExtInventory.this.inventoryStateStamp(); + } + + private void collectItems(List out, WItem w) { + String name = rawName(w); + Widget contents = w.item.contents; + + if ((contents instanceof ItemStack) || isStackWrapperName(name)) { + if (contents instanceof ItemStack) { + ItemStack stack = (ItemStack) contents; + + dbg("collect stack wrapper: %s order=%d", itemdbg(w), stack.order.size()); + + if (!stack.order.isEmpty()) { + for (GItem gi : stack.order) { + WItem sw = stack.wmap.get(gi); + if (sw == null) { + dbg(" missing child widget for stack item id=%d", gi.wdgid()); + continue; + } + + if (!isResolvedForList(sw)) { + pendingUnresolvedItems = true; + dbg(" skip unresolved child: %s", itemdbg(sw)); + continue; + } + + dbg(" collect child: %s", itemdbg(sw)); + out.add(sw); + } + } else { + dbg(" stack empty for now, skipping outer wrapper"); + } + } else { + dbg("skip wrapper without contents: %s", itemdbg(w)); + } + return; + } + + if (!isResolvedForList(w)) { + pendingUnresolvedItems = true; + dbg("skip unresolved item: %s", itemdbg(w)); + return; + } + + dbg("collect item: %s", itemdbg(w)); + out.add(w); + } + + private int visibleRows() { + return Math.max(1, (sz.y - HEADER_H) / ROW_H); + } + + private int maxScroll() { + return Math.max(0, rows.size() - visibleRows()); + } + + private GroupRow rowAt(Coord c) { + if (c.y < HEADER_H) return null; + int idx = (c.y - HEADER_H) / ROW_H + scroll; + if (idx < 0 || idx >= rows.size()) return null; + return rows.get(idx); + } + + @Override + public void tick(double dt) { + super.tick(dt); + if (!expanded) { + return; + } + + long stamp = ExtInventory.this.inventoryStateStamp(); + if (stamp != lastStamp) { + dbg("rebuild panel: stamp changed old=%d new=%d", lastStamp, stamp); + rebuild(); + lastStamp = stamp; + } + } + + @Override + public void draw(GOut g) { + g.chcolor(BG); + g.frect(Coord.z, sz); + g.chcolor(); + + g.chcolor(HEADER); + g.frect(Coord.z, new Coord(sz.x, HEADER_H)); + g.chcolor(); + + if (headerTex == null) { + rebuildHeader(); + } + g.aimage(headerTex, new Coord(4, HEADER_H / 2), 0.0, 0.5); + + int y = HEADER_H; + int end = Math.min(rows.size(), scroll + visibleRows()); + for (int i = scroll; i < end; i++) { + GroupRow row = rows.get(i); + + g.chcolor(((i & 1) == 0) ? ROW_EVEN : ROW_ODD); + g.frect(new Coord(0, y), new Coord(sz.x, ROW_H)); + g.chcolor(); + + g.aimage(row.tex(), new Coord(4, y + (ROW_H / 2)), 0.0, 0.5); + + y += ROW_H; + } + + super.draw(g); + } + + @Override + public boolean mousewheel(MouseWheelEvent ev) { + int ns = scroll + ((ev.a > 0) ? 1 : -1); + scroll = Math.max(0, Math.min(ns, maxScroll())); + return true; + } + + @Override + public boolean mousedown(MouseDownEvent ev) { + if (ev.b != 1 && ev.b != 3) { + return false; + } + + if (ev.c.y < HEADER_H) { + grouping = grouping.next(); + rebuild(); + return true; + } + + GroupRow row = rowAt(ev.c); + if (row == null || row.items.isEmpty()) { + return false; + } + + if (ui.modshift) { + final String rowName = row.key.name; + final String rowResname = row.key.resname; + final Double rowQ = row.key.q; + final Grouping rowGrouping = grouping; + final boolean reverse = (ev.b == 3); + + dbg("shift-click row: %s", row.text()); + dbg(" row item count=%d pending=%s", row.items.size(), pendingUnresolvedItems); + + new Thread(() -> ExtInventory.this.transferRowWithRetries( + rowName, rowResname, rowQ, rowGrouping, reverse + )).start(); + + return true; + } + + if (ui.modctrl) { + List ordered = new ArrayList<>(row.items); + processGroup(ordered, ev.b == 3, "drop", sqsz.div(2)); + return true; + } + + WItem sample = (ev.b == 3) ? row.lowSample : row.highSample; + if (sample != null && sample.parent != null) { + sample.item.wdgmsg("take", sqsz.div(2)); + } + return true; + } + + @Override + public Object tooltip(Coord c, Widget prev) { + GroupRow row = rowAt(c); + if (row == null || row.highSample == null) { + return null; + } + return row.highSample.tooltip(Coord.z, (prev == this) ? row.highSample : prev); + } + + @Override + public boolean drop(DTarget.Drop ev) { + return false; + } + + @Override + public boolean iteminteract(DTarget.Interact ev) { + return false; + } + } +} \ No newline at end of file diff --git a/src/haven/GItem.java b/src/haven/GItem.java index bf2e13327..738e542d3 100644 --- a/src/haven/GItem.java +++ b/src/haven/GItem.java @@ -50,7 +50,7 @@ public class GItem extends AWidget implements ItemInfo.SpriteOwner, GSprite.Owne private Widget hovering; private boolean hoverset; private GSprite spr; - private ItemInfo.Raw rawinfo; + private ItemInfo.Raw rawinfo = ItemInfo.Raw.nil;; public List info = Collections.emptyList(); public boolean sendttupdate = false; public long meterUpdated = 0; // ND: last time meter was updated, ms @@ -210,22 +210,23 @@ public void tick(double dt) { hoverset = false; } - public List info() { - if(this.info == null) { - List info = ItemInfo.buildinfo(this, rawinfo); - addcontinfo(info); - Resource.Pagina pg = res.get().layer(Resource.pagina); - if(pg != null) - info.add(new ItemInfo.Pagina(this, pg.text)); - this.info = info; - try { - if (FoodService.isValidEndpoint() && !checkForHempBuff()) { - FoodService.checkFood(info, getres(), ui.gui.genus); - } - } catch (Exception ignored) {} + public List info() { + if(this.info == null) { + ItemInfo.Raw raw = (rawinfo != null) ? rawinfo : ItemInfo.Raw.nil; + List info = ItemInfo.buildinfo(this, raw); + addcontinfo(info); + Resource.Pagina pg = res.get().layer(Resource.pagina); + if(pg != null) + info.add(new ItemInfo.Pagina(this, pg.text)); + this.info = info; + try { + if (FoodService.isValidEndpoint() && !checkForHempBuff()) { + FoodService.checkFood(info, getres(), ui.gui.genus); + } + } catch (Exception ignored) {} + } + return(this.info); } - return(this.info); - } @@ -254,18 +255,24 @@ public void uimsg(String name, Object... args) { if(name == "num") { num = Utils.iv(args[0]); } else if(name == "chres") { - synchronized(this) { - res = ui.sess.getresv(args[0]); - sdt = (args.length > 1) ? new MessageBuf((byte[])args[1]) : MessageBuf.nil; - spr = null; - } + synchronized(this) { + res = ui.sess.getresv(args[0]); + sdt = (args.length > 1) ? new MessageBuf((byte[])args[1]) : MessageBuf.nil; + spr = null; + } + qBuff = null; + stackQualityTex = null; + info = null; + infoseq++; } else if(name == "tt") { - info = null; - rawinfo = new ItemInfo.Raw(args); + info = null; + qBuff = null; + stackQualityTex = null; + rawinfo = new ItemInfo.Raw(args); if (sendttupdate) { wdgmsg("ttupdate"); } - infoseq++; + infoseq++; meterUpdated = System.currentTimeMillis(); } else if(name == "meter") { meter = Utils.iv(args[0]); @@ -292,7 +299,11 @@ public void addchild(Widget child, Object... args) { contentsnm = (String)args[1]; contentsid = null; if(args.length > 2) - contentsid = args[2]; + contentsid = args[2]; + qBuff = null; + stackQualityTex = null; + info = null; + infoseq++; contentswnd = contparent().add(new ContentsWindow(this, contents)); if(this.parent instanceof Equipory){ Equipory equipory = (Equipory) this.parent; diff --git a/src/haven/GameUI.java b/src/haven/GameUI.java index a4e4c635b..02001ea0f 100644 --- a/src/haven/GameUI.java +++ b/src/haven/GameUI.java @@ -66,7 +66,8 @@ public class GameUI extends ConsoleHost implements Console.Directory, UI.Notice. private double msgtime; private Window invwnd, equwnd, /*makewnd,*/ srchwnd, iconwnd; public CraftWindow makewnd; - public Inventory maininv; + public Inventory maininv; + public ExtInventory maininvext; public CharWnd chrwdg; public MapWnd mapfile; public Widget qqview; @@ -1180,15 +1181,19 @@ public void addchild(Widget child, Object... args) { } else if(place == "fsess") { fs = add((Fightsess)child, Coord.z); } else if(place == "inv") { - invwnd = new Hidewnd(Coord.z, "Inventory") { - public void cresize(Widget ch) { - pack(); - } + invwnd = new Hidewnd(Coord.z, "Inventory") { + public void cresize(Widget ch) { + pack(); + } }; - invwnd.add(maininv = (Inventory)child, Coord.z); - invwnd.pack(); - invwnd.hide(); - add(invwnd, Utils.getprefc("wndc-inv", new Coord(100, 100))); + + maininv = (Inventory) child; + maininvext = new ExtInventory(maininv); + + invwnd.add(maininvext, Coord.z); + invwnd.pack(); + invwnd.hide(); + add(invwnd, Utils.getprefc("wndc-inv", new Coord(100, 100))); } else if(place == "equ") { equwnd = new Hidewnd(Coord.z, "Equipment"); equwnd.add(child, Coord.z); diff --git a/src/haven/Window.java b/src/haven/Window.java index 178a82262..8e2ff11aa 100644 --- a/src/haven/Window.java +++ b/src/haven/Window.java @@ -88,6 +88,10 @@ public class Window extends Widget { Resource.loadsimg("gfx/hud/wnd/lg/unstackbtnu"), Resource.loadsimg("gfx/hud/wnd/lg/unstackbtnd"), Resource.loadsimg("gfx/hud/wnd/lg/unstackbtnh")}; + private static final BufferedImage[] extlistbtni = new BufferedImage[] { + Resource.loadsimg("gfx/hud/wnd/lg/qlistbtnu"), + Resource.loadsimg("gfx/hud/wnd/lg/qlistbtnd"), + Resource.loadsimg("gfx/hud/wnd/lg/qlistbtnh")}; public Deco deco; public String cap; public TexRaw gbuf = null; @@ -148,6 +152,8 @@ protected void added() { if (cap != null && Arrays.stream(Config.EXCLUDED_INVENTORY_WINDOWS).noneMatch(cap::equals)) { if (((DefaultDeco) deco).stackbtn != null) ((DefaultDeco) deco).stackbtn.visible = true; + if (((DefaultDeco) deco).extlistbtn != null) + ((DefaultDeco) deco).extlistbtn.visible = ((DefaultDeco) deco).findExtInventory() != null;; if (((DefaultDeco) deco).unstackbtn != null) ((DefaultDeco) deco).unstackbtn.visible = true; } @@ -212,7 +218,7 @@ public static class DefaultDeco extends DragDeco { UI.rscale(0.75), UI.rscale(1.0), Color.BLACK); public final boolean lg; public final IButton cbtn; - public IButton stackbtn, unstackbtn; + public IButton extlistbtn, stackbtn, unstackbtn; public boolean dragsize, cfocus; public Area aa, ca; public Coord cptl = Coord.z, cpsz = Coord.z; @@ -239,14 +245,18 @@ public void iresize(Coord isz) { resize(wsz); ca = Area.sized(tlm, csz); aa = Area.sized(ca.ul.add(mrgn), asz); - cbtn.c = Coord.of(sz.x - cbtn.sz.x - UI.scale(9), - UI.scale(10)); // ND: UI Window close button location - if (stackbtn != null) - stackbtn.c = Coord.of(sz.x - cbtn.sz.x - UI.scale(40), - UI.scale(10)); - if (unstackbtn != null) - unstackbtn.c = Coord.of(sz.x - cbtn.sz.x - UI.scale(59), - UI.scale(10)); + int extra = inventoryExtraWidth(); + int anchor = sz.x - extra; + cbtn.c = Coord.of(anchor - cbtn.sz.x - UI.scale(9), -UI.scale(10)); + if (extlistbtn != null) + extlistbtn.c = Coord.of(anchor - cbtn.sz.x - UI.scale(78), -UI.scale(10)); + if (stackbtn != null) + stackbtn.c = Coord.of(anchor - cbtn.sz.x - UI.scale(59), -UI.scale(10)); + if (unstackbtn != null) + unstackbtn.c = Coord.of(anchor - cbtn.sz.x - UI.scale(40), -UI.scale(10)); cpsz = Coord.of((int)(wsz.x*0.95), cm.sz().y).sub(cptl); // ND: changed this to make the window top bar fully draggable WHEN RESIZED (for instance, buddy window) } - + public Area contarea() { return(aa); } @@ -360,29 +370,93 @@ public boolean checkhit(Coord c) { return(ca.contains(c) || (c.isect(cptl, cpsz) && (cm.back.getRaster().getSample(cpc.x % cm.back.getWidth(), cpc.y, 3) >= 128))); } + private Inventory findInventory() { + if (!(parent instanceof Window)) + return null; + + Window wnd = (Window) parent; + for (Widget w = wnd.child; w != null; w = w.next) { + if (w == wnd.deco) + continue; + if (w instanceof Inventory) + return (Inventory) w; + if (w instanceof ExtInventory) + return ((ExtInventory) w).inv; + } + return null; + } + + private ExtInventory findExtInventory() { + if (!(parent instanceof Window)) + return null; + + Window wnd = (Window) parent; + for (Widget w = wnd.child; w != null; w = w.next) { + if (w == wnd.deco) + continue; + if (w instanceof ExtInventory) + return (ExtInventory) w; + } + return null; + } + + private int inventoryExtraWidth() { + if (!(parent instanceof Window)) + return 0; + + Window wnd = (Window) parent; + for (Widget w = wnd.child; w != null; w = w.next) { + if (w == wnd.deco) + continue; + if (w instanceof ExtInventory) { + ExtInventory ext = (ExtInventory) w; + return Math.max(0, ext.sz.x - ext.inv.sz.x); + } + } + return 0; + } + + public void addExtListBtn() { + if (extlistbtn != null) + return; + + extlistbtn = add(new IButton(extlistbtni[0], extlistbtni[1], extlistbtni[2])).action(() -> { + ExtInventory ext = findExtInventory(); + if (ext != null) { + ext.togglePanel(); + } + }); + extlistbtn.settip("Quality List"); + extlistbtn.visible = false; + } + public void addStackBtn() { - stackbtn = add(new IButton(stackbtni[0], stackbtni[1], stackbtni[2])).action(() -> { - for (Widget wdg = this; wdg != null; wdg = wdg.next) { - if (wdg instanceof Inventory) { - new Thread(new StackAllItems(this.ui.gui, (Inventory) wdg)).start(); - } - } - }); - stackbtn.settip("Stack All"); - stackbtn.visible = false; - } + if (stackbtn != null) + return; + + stackbtn = add(new IButton(stackbtni[0], stackbtni[1], stackbtni[2])).action(() -> { + Inventory inv = findInventory(); + if (inv != null) { + new Thread(new StackAllItems(this.ui.gui, inv)).start(); + } + }); + stackbtn.settip("Stack All"); + stackbtn.visible = false; + } public void addUnstackBtn() { - unstackbtn = add(new IButton(unstackbtni[0], unstackbtni[1], unstackbtni[2])).action(() -> { - for (Widget wdg = this; wdg != null; wdg = wdg.next) { - if (wdg instanceof Inventory) { - new Thread(new UnstackAllItems(this.ui.gui, (Inventory) wdg)).start(); - } - } - }); - unstackbtn.settip("Unstack All"); - unstackbtn.visible = false; - } + if (unstackbtn != null) + return; + + unstackbtn = add(new IButton(unstackbtni[0], unstackbtni[1], unstackbtni[2])).action(() -> { + Inventory inv = findInventory(); + if (inv != null) { + new Thread(new UnstackAllItems(this.ui.gui, inv)).start(); + } + }); + unstackbtn.settip("Unstack All"); + unstackbtn.visible = false; + } } @@ -793,11 +867,27 @@ public T add(T child) { private void enhanceWidgets(T child) { try { - if (child instanceof Inventory) { + if (child instanceof Inventory || child instanceof ExtInventory) { if (deco instanceof DefaultDeco) { - ((DefaultDeco)deco).addStackBtn(); - ((DefaultDeco)deco).addUnstackBtn(); - } + try { + ((DefaultDeco) deco).addStackBtn(); + } catch (Exception e) { + e.printStackTrace(); + } + try { + ((DefaultDeco) deco).addUnstackBtn(); + } catch (Exception e) { + e.printStackTrace(); + } + if (child instanceof ExtInventory) { + try { + ((DefaultDeco) deco).addExtListBtn(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + } } } catch (Exception e) { e.printStackTrace();