diff --git a/AnySCT.toc b/AnySCT.toc index 3fa2518..920ec86 100644 --- a/AnySCT.toc +++ b/AnySCT.toc @@ -2,7 +2,7 @@ ## Title: AnySCT ## Notes: Blizzard-pet-style floating combat text for any units you pick: tanks, their pets and totems, mobs, enemy players. ## Author: ShempError -## Version: 0.3.0 +## Version: 0.3.1 ## X-Website: https://octowow.st/git/ShempError/AnySCT ## OptionalDeps: pfUI, CT_RaidAssist, oRA, TWThreat ## SavedVariables: AnySCTDB diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f8078f..35acb0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to AnySCT are documented here. +## 0.3.1 — 2026-10-15 + +### Fixed +- Lines in a tower no longer overlap: each line reserves its real height (crits at the peak of their size pop) plus the suffix line hanging below the line above it, and crits now rise with the rest of the stack instead of standing still, so newer lines can no longer rise into them. +- Crits of tiny procs (relative to the source's average hit) pop at up to half size. + ## 0.3.0 — 2026-10-15 ### Added diff --git a/README.md b/README.md index 62d4de2..e2be61f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,14 @@ are on the list, your own SCT addon stays the only one that shows your damage). *A listed tank hitting the training dummies in Orgrimmar, seen from another character: an orange melee "Miss", a gold spell "Resist", and the experimental WAIT label under the dummy being attacked.* +## What's new — 0.3.1 · 2026-10-15 + +- **The line tower no longer overlaps.** Every line reserves its real height, including the crit size pop + and the spell / name line under it, and crits now rise with the rest of the stack, so newer lines can + not run into them. +- **Crits of tiny procs pop smaller** (a crit "3" among Fire Strike ticks is half size), measured against + the source's average hit. + ## What's new — 0.3.0 · 2026-10-15 - **Identical hits are merged.** Fire Strike style tick storms become one line with the running sum @@ -45,6 +53,8 @@ are on the list, your own SCT addon stays the only one that shows your damage). **Version history** — details in [CHANGELOG.md](CHANGELOG.md): +- **0.3.1** (2026-10-15) — fix: the line tower no longer overlaps (real line heights, crits rise with the + stack), crits of tiny procs pop smaller. - **0.3.0** (2026-10-15) — merged identical hits, "Nameplate + anchor" placement, a demo that plays your own targetSCT's class burst and previews GO / WAIT, anchor drop fix. - **0.2.0** (2026-10-15) — targetSCT rename, name option, per-targetSCT colours, regrouped options with @@ -57,7 +67,7 @@ are on the list, your own SCT addon stays the only one that shows your damage). Everything floats over the **victim** of the event, never over the source: - **Melee in orange, special attacks in gold, heals in green.** Numbers use the client's own damage font, - no outline, a black shadow. Ordinary hits rise and fade; **crits pop** at a larger size and stay in place. + no outline, a black shadow. Ordinary hits rise and fade; **crits pop** at a larger size and rise with the stack. - **Words for what did not land**: Miss, Dodge, Parry, Block, Resist, Immune, ... in the same style. - **Every hit is shown**: up to 16 lines per mob and 60 on screen by default, so a fast burst is not recycled early (both are sliders on the Text tab). @@ -71,11 +81,18 @@ Everything floats over the **victim** of the event, never over the source: *A crit "366" popping over the Heroic Training Dummy, a "Block" in grey, and a GO label partly covered by the crit.* -Demo at the fixed anchor: a shaman burst with spell names, merged Fire Strike ticks, words and the WAIT label inside the anchor box - -*The Demo button at the fixed anchor (no target): a shaman burst with spell names on - a 783 Stormstrike -crit, a white hit, merged Fire Strike ticks (7), Miss / Dodge / Parry / Block, and the GO / WAIT preview -"WAIT 1200 / 1500" inside the anchor box.* + + + + + +
+ Demo at the fixed anchor early in the burst: a tower of numbers and words and WAIT 1350 / 1500 inside the anchor box
+ WAIT at the anchor — the Demo's shaman burst, no overlaps: a faded 783 Stormstrike on top, Dodge, 97, a merged 7 Fire Strike, Parry, Block, 100 Ancient Accord, a half-size crit 3 Fire Strike, and "WAIT 1350 / 1500" inside the anchor box +
+ Demo at the fixed anchor later in the burst: crits 216 and 760 in the tower and GO +1875 inside the anchor box
+ GO at the anchor — later in the burst: a fading Ancient Accord, 3 Fire Strike, Resist, 7 Fire Strike, a big crit 216, 194, a crit 760 Earth Shock, and "GO +1875" inside the box +
## GO / WAIT threat label (experimental) @@ -236,7 +253,7 @@ six sliders including the line limits) below.* | Direction | text rises (Up) or sinks (Down) | Up | | Number size | factor on the engine size, 0.5-3 | 1.00 | | Word size | same for Miss / Dodge / Parry ... | 1.00 | -| Rise (px) | how far a normal text rises, 0-300; crits stay in place | 120 | +| Rise (px) | how far a normal text rises, 0-300; crits rise with the stack | 120 | | Jitter (px) | random horizontal offset per line, 0-40 | 0 | | Max lines | lines on screen over all mobs, 3-100; the oldest is recycled at the limit | 60 | | Max lines per mob | lines over one mob, 1-30; lines live 1.5 s, so 16 shows every hit of a burst of about 10 per second | 16 | diff --git a/options.lua b/options.lua index 8eef1f9..aa51dd8 100644 --- a/options.lua +++ b/options.lua @@ -97,7 +97,7 @@ if CreateFrame then "Size factor of damage numbers. Size = 0.018333 x screen diagonal x factor (crits 0.0275): 23 px at 1024x768, 54 px at 2560x1440." }, { "wordFactor", "Word size", 0.5, 3, 0.05, "%.2f", "Size factor of Miss / Dodge / Parry / ..." }, { "riseHeight", "Rise (px)", 0, 300, 5, nil, - "How far a normal text rises (crits stay in place). The engine rises 2 yards; the screen distance depends on the camera." }, + "How far a normal text rises (crits rise with the stack). The engine rises 2 yards; the screen distance depends on the camera." }, { "mergeWindow", "Merge window (s)", 0.1, 1.5, 0.05, "%.2f", "How long a line keeps absorbing identical hits, counted from its first hit. Short: more, smaller lines. Long: fewer lines that update longer. Only used with Merge identical hits." }, { "jitter", "Jitter (px)", 0, 40, 1, nil, "Random horizontal offset per line. 0 like the engine; lines stack instead." }, diff --git a/screenshots/anchor-go.png b/screenshots/anchor-go.png new file mode 100644 index 0000000..b60a4d6 Binary files /dev/null and b/screenshots/anchor-go.png differ diff --git a/screenshots/anchor-wait.png b/screenshots/anchor-wait.png new file mode 100644 index 0000000..f63ca82 Binary files /dev/null and b/screenshots/anchor-wait.png differ diff --git a/screenshots/demo-anchor-tower.png b/screenshots/demo-anchor-tower.png deleted file mode 100644 index 2f675b0..0000000 Binary files a/screenshots/demo-anchor-tower.png and /dev/null differ diff --git a/sct.lua b/sct.lua index aedea27..c3a3ac0 100644 --- a/sct.lua +++ b/sct.lua @@ -24,7 +24,6 @@ AnySCT_STYLE = { fadeOutWord = 1.0, -- Miss / Dodge / Parry / Block ... fadeOutAbsorb = 0.09, -- "Absorb" is a quick flicker riseHeight = 120, -- px a normal text rises (engine: 2 world units; screen distance varies) - critRiseFactor = 0, -- crits do not rise, they pop in place sizeScale = 0.018333, -- text pixel size = scale x screen diagonal (1024x768 -> 23 px) critSizeScale = 0.0275, -- crits (1024x768 -> 35 px) shadow = 0.002, -- shadow offset, fraction of the viewport per axis (down-right) @@ -230,6 +229,43 @@ S.ANCHOR_W, S.ANCHOR_H, S.ANCHOR_LABEL_SIZE, S.PLATE_LABEL_SIZE = 200, 30, 12, 1 function S.EstTextWidth(text, size) return string.len(text) * size * 0.62 end -- Where anchor-mode damage lines start so they clear the box (and the list label above it). function S.AnchorLineOffset(showLabel) return showLabel and 16 or 2 end +-- ---- stack geometry ------------------------------------------------------------------------- +-- A line occupies [bottom - suffixH, bottom + numberH]: the number sits on its anchor point, the +-- suffix (spell / name) hangs below it. Crits are reserved at the peak of their size pop. A new line +-- is inserted at the bottom; every older line of the same mob moves up by the new line's height plus +-- the suffix height of the line directly above it, which is what makes the new line fit under it. +function S.CritPeak(kf) + local m = 1 + for i = 1, table.getn(kf) do if kf[i][2] > m then m = kf[i][2] end end + return m +end +-- Distance of a line's anchor point from its start: the same rise rate for every line of a tower, so +-- older lines (larger t) only ever move away from newer ones. +function S.LineOffset(t, dur, rise, push) return t / dur * rise + push end +function S.NumberHeight(base, isCrit, peak) return isCrit and base * peak or base end +function S.SuffixHeight(base, hasSuffix) + if not hasSuffix then return 0 end + local sz = math.floor(base * 0.3) + if sz < 10 then sz = 10 end + return sz + 2 +end +function S.StackAdvance(base, isCrit, peak, prevSuffixH) + return S.NumberHeight(base, isCrit, peak) + (prevSuffixH or 0) +end +-- A crit of a tiny proc must not pop at full crit size: scale by the amount against the source's +-- running average hit (0.5 .. 1). +function S.CritFactor(amount, ref) + if not ref or ref <= 0 then return 1 end + local f = (amount or 0) / ref + if f > 1 then return 1 end + if f < 0.5 then return 0.5 end + return f +end +function S.UpdateRef(ref, amount) + if not ref then return amount end + return ref + (amount - ref) * 0.1 +end + -- ---- merging identical attacks --------------------------------------------------------------- -- Is the result a word (Miss / Dodge / ...)? Same rule as S.Format. function S.IsWordResult(res, opt) @@ -370,6 +406,8 @@ if CreateFrame then local POOL_MAX = 100 -- slot tables only; FontStrings are created on first use (ensureFs) local critAnimated = S.CurveAnimated(STYLE.critKeyframes) local critActive = S.CurveActive(STYLE.critKeyframes) + local avgHit = {} -- per source: running average hit, for the crit pop scale + local critPeak = S.CritPeak(STYLE.critKeyframes) local GAP = 3 -- px between the plate top and the text local anchor, float, listLabel @@ -548,13 +586,14 @@ if CreateFrame then if t < STYLE.fadeIn then a = t / STYLE.fadeIn end if t > s.fadeOut then a = a * (s.dur - t) / (s.dur - s.fadeOut) end local rise = s.rise + -- crits rise with the tower like every other line: a line that stood still would + -- let the newer, rising lines below it run into it if s.crit then - rise = rise * STYLE.critRiseFactor if critAnimated then s.fs:SetTextHeight(s.base * S.KeyScale(STYLE.critKeyframes, t / s.dur)) end end - local off = (t / s.dur * rise + s.push) * s.dir + local off = S.LineOffset(t, s.dur, rise, s.push) * s.dir s.fs:ClearAllPoints() if s.plate then needScan = true @@ -632,9 +671,26 @@ if CreateFrame then local base = S.TextSize(scale, factor, sw, sh) s.base = base * mul - -- older lines of the same mob move one text height away + -- tiny crits (procs) pop smaller: scale by the amount against the source's running average hit + local refKey = res.source or "?" + if not isWord and res.kind ~= "heal" then + if s.crit then s.base = s.base * S.CritFactor(res.amount, avgHit[refKey]) end + avgHit[refKey] = S.UpdateRef(avgHit[refKey], res.amount or 0) + end + s.sfxH = S.SuffixHeight(s.base, suffix ~= nil) + + -- older lines of the same mob make room for this line's real height (crit peak included) plus + -- the suffix hanging below the line right above it + local prevSfx, prevT = 0, nil for j = 1, POOL_MAX do - if slots[j].active and slots[j].key == key then slots[j].push = slots[j].push + s.base end + local m = slots[j] + if m.active and m.key == key and j ~= i and (not prevT or m.t < prevT) then + prevT, prevSfx = m.t, m.sfxH or 0 + end + end + local adv = S.StackAdvance(s.base, s.crit, critPeak, prevSfx) + for j = 1, POOL_MAX do + if slots[j].active and slots[j].key == key and j ~= i then slots[j].push = slots[j].push + adv end end s.active = true