Repository navigation
Loot filter: PoE-style ground item visuals (label background, frame, size, beams, automap shapes) - #99
Open
errolgr wants to merge 15 commits into
Open
Loot filter: PoE-style ground item visuals (label background, frame, size, beams, automap shapes)#99errolgr wants to merge 15 commits into
errolgr wants to merge 15 commits into
Conversation
Parse %BG-XX%, %OPACITY-25|50|75|100%, %FRAME-XX%, %SIZE-S|M|L%, %BEAM-XX%, %FLASH-XX% and %ICON-<shape>% in BuildAction next to the map colour keywords and strip them from the label text. Malformed values stay in the text as typed, like other unknown keywords. Palette colours take 1-2 hex digits (8-bit palette). GetGroundStyle resolves an item's style over all matching rules with %CONTINUE% semantics (a later rule overrides each field it sets) and caches it per item like the name and map caches; ResetCaches clears it on filter reload and filter level change. Filters without the new tokens skip the lookup entirely. CreateUnitItemInfo moves to ItemDisplay.cpp so GetGroundStyle can build the item info from a unit outside Item.cpp.
Ground labels are drawn in D2 font 1 (observed in game), not 13, so %SIZE-S|M|L% map to fonts 1 / 2 / 3 and S keeps today's look.
Ground item labels are drawn from an array the label pass fills (vanilla D2Client 0x58FB0; PD2 redirects that call to its own rewrite in ProjectDiablo.dll, same entry layout): each label is measured with D2Win #10177 and drawn with D2Win #10013 (box + text). BH hooks both entries and recognises a label call by its arguments: the text is the name of an entry inside D2Client's or ProjectDiablo.dll's image and the other arguments are that entry's fields. For an item with a GroundStyle the measurement uses the style's font (so the engine sizes, stacks and hit-tests the box for it) and the draw gets the background colour, the opacity (25/50/75/100 = D2 draw modes TRANS25/TRANS50/TRANS75/NORMAL), the font and a 1 px frame on the box edge; a framed label reserves one more row so the label stacked above it does not cover the frame. The hovered label keeps the engine's highlight box. Every other call, and labels of items without a style, reach the original functions with the original arguments. The entry hooks are written as one locked 8-byte swap. An entry already detoured by another module (D2GL: jmp rel32 + int3) is accepted when the rest of the prologue matches; the trampoline then continues at that module's hook. 1.13c only; if a prologue differs the styles stay off and BH says so once per session. GroundLabels::SetPreLabelCallback lets item beams draw below the labels.
%BEAM-XX% draws a light column above a ground item while it lies there, %FLASH-XX% one that fades out over 2 s after the item drops (fresh drops only: the client marks them ITEM_NEW; items already lying in a room that comes into view do not flash). A beam is a pulsing translucent glow with a half-opaque core and a light pool on the ground, drawn with D2GFX_DrawRectangle in the TRANS25/TRANS50 draw modes. At most 16 beams, the ones nearest the player. ItemBeams::Draw runs once per frame from BH's draw hook; a hook that runs before the ground labels may call it first. %ICON-<shape>% draws the item's automap marker layers (%BORDER%/%MAP%/%DOT%/%PX%, same sizes and colours) as a circle, diamond, star, triangle or cross instead of a square. A lone %DOT%/%PX% shape grows to 6 px so the shape stays readable. Squares take the unchanged drawing path.
Beams were drawn over the character, inventory and waypoint panels: BH's draw hook runs after the panels, and so does the pre-label hook (the engine draws the panels before the ground labels and keeps labels off them by position). Beams now follow the engine's label rule: with a left panel open (D2Client ScreenCovered 2) only the right half of the screen shows the world, with a right panel open (1) only the left half, with both (3) nothing; beam rectangles are clipped to that span at any screen width, and only beams in it count for the 16-beam cap. While the game menu, hotkey config, help, NPC dialog, skill picker or gold dialog is open no beam is drawn (items are still tracked, so flashes keep their drop time).
D2GL's hd_text replaces D2Win #10013 with its own label box (black or its hover blue, the colour argument unused) and draws it in a layer over the game frame. A restyled label is now drawn with the calls D2GL honours when D2GL renders with hd_text: the box and frame with D2Gfx #10014 (palette colour and draw mode in the game frame; black in D2GL's layer) and the text with D2Win #10150. hd_text off and the other renderers keep the #10013 path.
errolgr
force-pushed
the
feature/filter-visuals
branch
from
October 2, 2026 04:34
dcb7889 to
70a563d
Compare
With one side panel open (inventory, character, ...) D2Client shifts the world view by a quarter of the screen width (D2Client+0x11C418: -width/4 for a right panel, +width/4 for a left one, set with ScreenCovered at 0x3FF90) and places ground labels with that shift (0x5912D). Beams were placed without it, so they stayed where the item was before the panel opened. Beams now use the same transform as the labels.
The beam is now built from 1 px columns in three stacked translucent layers of the beam colour (outer glow, inner glow, core), each column cut to its own height on a shallow parabola across the beam and ending in a checker-dithered tail, so the sides fall off and the top fades without bands. A white-hot centre line, a white flare and a flickering light pool mark the ground, and two short sparks rise slowly through the core. (The 8-bit DrawRectangle has no additive mode on DirectDraw: modes 0/1/2 blend 25/50/75 %, 7/8 are opaque, 3 draws nothing, 4/6 black.) The foot of the beam (40 px) is now drawn with the world, right after the floor and before the ground items and units (the call to the floor draw at D2Client 0x8B235 makes the original call and then draws the feet), so the item and anyone standing in front of it cover it; the rest of the beam is drawn over the world as before. The site is byte-checked (a call); if it cannot be hooked, beams are drawn whole as before and BH says so once.
The beam is back to the narrow needle of the first version (13 px at the foot, tapering to 1 px), kept as 1 px columns with dithered tails so it stays smooth; the core, a faint white centre line and the rising sparks are scaled to it. The light pool and flare are gone. The beam no longer reaches down to the item: it starts in the middle of the item's ground label, so the label covers its foot and no beam shows below the label. While the labels are drawn the label's actual box is used (GroundLabels::LabelBoxOf: the entries measured in this label pass, a label the engine moved up to make room is followed); without labels the beam starts where the label would sit (14 px above the ground point), so it does not move when Alt is pressed. With the item no longer under the beam, the world-draw hook that put the beam's foot under ground items is removed.
…ise higher The thin needle gets back the foot of the first beams, scaled to it: a small flickering light pool (rows -3 .. +2 around the foot, a little wider than the needle) and a white flare. The beam is a quarter taller (190 px). The foot is one fixed point, whether labels are shown or not, so the beam never moves when Alt is pressed: 14 px above the item's ground point, the middle of the box the engine draws an unstacked ground label in (box 22 .. 6 px above the ground point), so a shown label covers the foot, pool and flare. A label the engine stacks elsewhere does not cover its beam's foot. The per-frame label box lookup (GroundLabels::LabelBoxOf) is gone.
PD2's D2GL renderer (motion_prediction, on by default) draws the world between the game's 25 fps ticks at the player's predicted position: every world sprite is drawn at its position minus the prediction offset, and ProjectDiablo.dll's ground label pass subtracts the same offset from the labels (d2glGetGlobalXOffset/d2glGetGlobalYOffset while d2glIsReady). The rectangles BH draws are not moved by D2GL, so beams stood at the tick position and wobbled against their item while the player walked. Beams now subtract that offset too, read from the same D2GL exports; it is 0 without D2GL, when D2GL does not render and with motion_prediction off.
The engine marks the label under the mouse (and the item the player walks to) by drawing it with its opaque blue box (draw mode 5). A restyled label took that box instead of its %BG%/%OPACITY% background, so hovering it, with Alt held or "Always Show Items" on, turned it into the stock hover look. Now a hovered restyled label keeps its background colour, opacity, frame and font and gets a 1 px white ring inside its frame (on the box's edge without one), on the default renderers and under D2GL's hd_text alike. Labels without a style keep the engine's hover look; measurement and hit-testing are unchanged.
…nd labels New loot filter keywords set how a ground item's label looks while it is hovered (the label under the mouse with Alt held or "Always Show Items" on): %HOVERBG-XX% (palette colour), %HOVEROPACITY-25|50|75|100%, %HOVERFRAME-XX% and %HOVERTEXT-<colour>% with BH's colour words (WHITE RED GREEN BLUE GOLD GRAY BLACK TAN ORANGE YELLOW PURPLE DARK_GREEN CORAL SAGE TEAL LIGHT_GRAY), which draws the whole label text in that colour. They are parsed like the other visual keywords: removed from the label text, case-insensitive, shown as typed when malformed, usable in aliases, and with %CONTINUE% a later rule only overrides the ones it sets. Each falls back to %BG% / %OPACITY% / %FRAME% (then the engine's plain box) while hovered; a rule with hover keywords only leaves the label to the engine until it is hovered. A hovered restyled label without hover keywords no longer gets a white ring: it is lightened by white at 25 % opacity over its box, inside its frame, on the default renderers and under D2GL's hd_text (where a hovered black box, drawn in D2GL's own layer, also gets a lighter alpha). Labels without a style keep the engine's hover look; measurement and hit-testing are unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Path of Exile style ground item visuals to the loot filter: a filter rule can now give an item's ground label a background colour, opacity, frame and size, put a light beam on it (permanent, or a short flash when it drops), and choose the shape of its automap marker. Everything is opt-in through new
%KEYWORDS%; filters that don't use them look exactly like today.D2GL, HD text on. Ber: red text on a white box with a red frame and a red beam; Jah: white on red; Lo/Sur: white on orange-red / black; mid runes: coloured frames on see-through boxes; Shako (unique) and Sigon's Visor (set) keep their name colour with a matching frame; the key and potion are today's plain labels.
Problem
A filter can change a label's text and colour and mark items on the automap, but every ground label is the same small text on the same black, half-transparent box. A Ber and a Chipped Ruby look alike on a busy screen until you read them. Players coming from PoE (NeverSink / FilterBlade filters) expect labels that stand out by tier, and PD2 filter authors already borrow PoE conventions (the PoE alert sounds 4714-4729 are in PD2's sounds.txt and used by filters).
Changes
New filter keywords
XXis a 2-digit hex palette index, the same as%MAP-XX%.%BG-XX%%OPACITY-25|50|75|100%%FRAME-XX%%SIZE-S|M|L%%BEAM-XX%%FLASH-XX%%ICON-SQUARE|CIRCLE|DIAMOND|STAR|TRIANGLE|CROSS%%MAP%/%BORDER%/%DOT%/%PX%keep their size and colour)%HOVERBG-XX%%BG%%HOVEROPACITY-25|50|75|100%%OPACITY%%HOVERFRAME-XX%%FRAME%%HOVERTEXT-<colour>%WHITE,RED,GOLD, ...)They work like the existing keywords: stripped from the label text, case-insensitive, combine with
%CONTINUE%(a later matching rule overrides only the fields it sets), and can live in anAlias, so a whole colour theme is a few alias lines:A malformed value (
%BG-ZZ%,%OPACITY-30%,%ICON-HEXAGON%) is shown as typed and sets nothing, like any unknown keyword today. A rule that only sets visual keywords does not become a notification rule (no drop message or sound).Hover. A hovered styled label (Alt held, or BH's Always Show Items) keeps its style. If the matching rules set no
%HOVER...%keyword it gets lighter: a 25 % white layer over the box, frame unchanged, so every styled label has a hover cue without any filter change. The%HOVER...%keywords replace that with an explicit hovered look, and a rule with only hover keywords keeps the stock label until it is hovered. Unstyled labels keep the game's own hover box exactly. Hit-testing and pick-up are unchanged.Aliasblock differsHow it works
ItemDisplay.h/.cppActionfields, parsed inBuildAction.GetGroundStyle(item)resolves an item's style across the rules (with%CONTINUE%), cached like the name/map caches and reset on filter reload / filter level change. Filters without visual keywords skip the lookup entirely (a flag set when the filter loads).GroundLabels.cpp/.h(new),Item.cppjmpdetour like D2GL's is accepted), and if anything differs the styles stay off with a one-time chat note.GroundLabels.cpphd_texton (PD2's default), D2GL's #10013 ignores the box colour. When D2GL is actually rendering with HD text, a styled label draws its own box (#10014), frame and centred text (#10150); unstyled labels and every other path are unchanged.GroundLabels.cpp,ItemDisplay.h/.cpp%HOVER...%fields or the 25 % white layer on top. The text recolour for%HOVERTEXT%works on a copy of the string, so measuring, stacking and hit-testing are untouched.ItemBeams.cpp/.h(new),MapNotify.cpp,D2Ptrs.hD2Client+0x11C418) and, under D2GL withmotion_predictionon (PD2's default), D2GL's per-frame prediction offset (d2glGetGlobalXOffset/YOffset, the same exports ProjectDiablo.dll uses for the labels), so beams stay on their item while the player walks, clipped to the visible half when a side panel is open (ScreenCovered), hidden under full-screen menus.MarkerShapes.cpp/.h(new),MapNotify.cppBH.vcxproj,.filters,CMakeLists.txtTesting
%HOVER...%keywords,%CONTINUE%overrides, aliases, notification-rule separation, cache reset; label hook trampolines on the real D2Win prologues (incl. D2GL's detour); beam geometry, fade, flash eligibility, 16-beam cap, panel clipping at 800 and 1068 px; marker shape pixels at every size; hover look fallbacks, the default lighter layer and%HOVERTEXT%recolouring of multi-colour text%HOVER...%keyword, hover-only rules, an unstyled label keeps the game's hover exactly, call audit with hover args, clicking a hovered styled label still picks the item up; with Alt held and with Always Show Items on*symbols, padding, e.g.PICK **** [ETH] Archon Plate **** UP) with a style: box and frame wrap the whole line, every colour keptKnown differences under D2GL HD text: styled boxes have square ends (D2GL's own labels have faded ends), black boxes use D2GL's opacity steps (40/60/80/100 %), and HD text's glyph outline makes red/blue text on a pure white box look lighter.
Filter used for the top screenshot:
Not tested: online play (single player only), and the token names are a proposal; happy to rename them to whatever fits PD2's filter docs and the filter authors.
Side findings (not changed here)
permShowItems3/4patches go into D2Client 0x58FB0, which PD2 never calls, so they have no effect on PD2.BH::CheckForD2GLonly checks that D2GL's exports exist, so BH thinks D2GL is active under-ddrawtoo (PD2 loads glide3x.dll in both modes;d2glIsReady()tells them apart).