4 Commits
Author SHA1 Message Date
ShempError 76b3e1c58e AnySCT 0.3.1: README captions 2026-10-15 15:00:15 +02:00
ShempError 1ea2afc333 AnySCT 0.3.1: README - set the threat buffer yourself 2026-10-15 14:55:38 +02:00
ShempError 4a0c325fd7 AnySCT 0.3.1 2026-10-15 14:55:12 +02:00
ShempError 4600cc0719 AnySCT 0.3.0 2026-10-15 14:49:24 +02:00
9 changed files with 504 additions and 81 deletions
+1 -1
View File
@@ -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.2.0
## Version: 0.3.1
## X-Website: https://octowow.st/git/ShempError/AnySCT
## OptionalDeps: pfUI, CT_RaidAssist, oRA, TWThreat
## SavedVariables: AnySCTDB
+21
View File
@@ -2,6 +2,27 @@
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
- **Merge identical hits** (Text tab, on by default): identical attacks (same source, victim and ability; normal hit or crit kept apart) within the **merge window** (0.1-1.5 s, default 0.4) become ONE line whose number is the running sum, updated in place. Misses and other words are not summed; identical words collapse. Optional **Show hit count** appends e.g. ` x5`.
- Placement **Nameplate + anchor** (`/asct place both`): every line and the GO / WAIT label appear over the victim's nameplate and at the fixed anchor, with separate line limits per place. Without a plate it shows once at the anchor.
- The **Demo** button and `/asct demo` now play a 2.5 s burst of the first listed targetSCT's class skills (a shaman demo tank without a list; amounts for the shaman set from recorded tanking fights, estimates for other classes) through the normal display path, so spell names, name prefixes, colours, limits and placement apply. Every result type (crit, Miss, Dodge, Parry, Block) appears, and a simulated GO / WAIT label previews the threat display (also with GO / WAIT switched off).
### Changed
- The GO / WAIT label at the fixed anchor sits inside the anchor box (now 200 x 30); damage lines in anchor mode start above its top edge.
- Anchor X / Y sliders span the real screen instead of fixed +-800 / +-500 and never clamp a stored position.
- The options window is 540 px high to fit the new Text-tab controls.
### Fixed
- The fixed anchor jumped to another place when dropped after a drag: the drop position was computed from `UIParent:GetWidth()` / `GetHeight()`, which are not in the coordinate system of frame rects on a non-default UI scale. It now uses the screen rect's centre and rounds to whole units; slider refreshes no longer write back while dragging.
## 0.2.0 — 2026-10-15
### Changed
+62 -13
View File
@@ -11,10 +11,32 @@ AnySCT shows only the events of the units on its list. It never touches Blizzard
`SHOW_COMBAT_TEXT` or another scrolling-combat-text addon, and it never shows your own events (if you
are on the list, your own SCT addon stays the only one that shows your damage).
<img src="screenshots/hero-miss-resist-wait.png" alt="Miss and Resist floating over training dummies, with a WAIT label under the dummy" width="100%">
<img src="screenshots/hero-miss-resist-wait.png" alt="A pet's Miss and Resist over the Expert Training Dummy, a WAIT label under the Heroic Training Dummy" width="100%">
*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.*
*The training dummies in Orgrimmar, seen from another character: a pet's orange melee "Miss" and gold spell
"Resist" over the Expert Training Dummy it attacks, and the experimental WAIT label under the Heroic Training
Dummy the listed tank is hitting.*
## 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
(same source, victim, ability and hit/crit within 0.4 s; the number updates in place, words are not
summed). Switch off or tune under Text: *Merge identical hits*, *Merge window*, *Show hit count*.
- **Placement "Nameplate + anchor"** shows every line, and the GO / WAIT label, over the plate and at the
fixed anchor at once, with separate line limits.
- **The Demo previews your own setup**: a 2.5 s burst of your first targetSCT's class skills (Stormstrike,
Earth Shock and friends for a shaman) with all your display settings, plus the GO / WAIT label inside
the anchor box, to judge the line tower without a second character.
- **Fixed: the anchor no longer jumps when you drop it.** The dropped position is now exact, and the
Anchor X / Y sliders cover the whole screen.
## What's new — 0.2.0 · 2026-10-15
@@ -32,6 +54,10 @@ 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
label tooltips, higher line limits, experimental threat-based GO / WAIT.
- **0.1.0** (2026-10-14) — first release: list, victim-routed text in the client's own style, nameplate
@@ -42,10 +68,12 @@ 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).
- **Identical hits are merged**: the same attack from the same source on the same mob within 0.4 s shows
as one line with the summed value (tiny procs like Fire Strike no longer flood the tower; Text tab).
- Pets and totems of a listed unit are shown under their owner; Fire Nova, traps and other area damage
land on the mob they hit.
@@ -54,6 +82,19 @@ 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.*
<table>
<tr>
<td align="center" width="50%">
<a href="screenshots/anchor-wait.png"><img src="screenshots/anchor-wait.png" width="300" alt="Demo at the fixed anchor early in the burst: a tower of numbers and words and WAIT 1350 / 1500 inside the anchor box"></a><br>
<sub><b>WAIT at the anchor</b> — 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</sub>
</td>
<td align="center" width="50%">
<a href="screenshots/anchor-go.png"><img src="screenshots/anchor-go.png" width="300" alt="Demo at the fixed anchor later in the burst: crits 216 and 760 in the tower and GO +1875 inside the anchor box"></a><br>
<sub><b>GO at the anchor</b> — 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</sub>
</td>
</tr>
</table>
## GO / WAIT threat label (experimental)
Off by default. Shows under the nameplate of **your own target** how much threat lead the targetSCT has
@@ -63,11 +104,11 @@ over you, so you can see when you can open without pulling aggro: `GO +4210` (gr
<table>
<tr>
<td align="center" width="50%">
<a href="screenshots/wait.png"><img src="screenshots/wait.png" width="420" alt="WAIT label under the Heroic Training Dummy, a Dodge over the Expert Training Dummy"></a><br>
<sub><b>WAIT</b> — "WAIT 1211 / 1500": the buffer is still below the required 1500, a "Dodge" floats above</sub>
<a href="screenshots/wait.png"><img src="screenshots/wait.png" width="420" alt="WAIT label under the Heroic Training Dummy, a pet's Dodge over the Expert Training Dummy"></a><br>
<sub><b>WAIT</b> — "WAIT 1211 / 1500": the buffer is still below the required 1500 (the "Dodge" is a pet's, on the dummy next to it)</sub>
</td>
<td align="center" width="50%">
<a href="screenshots/go.png"><img src="screenshots/go.png" width="420" alt="GO label under the Heroic Training Dummy, a Dodge over the Expert Training Dummy"></a><br>
<a href="screenshots/go.png"><img src="screenshots/go.png" width="420" alt="GO label under the Heroic Training Dummy, a pet's Dodge over the Expert Training Dummy"></a><br>
<sub><b>GO</b> — "GO +3225": the lead is large enough, opening is safe</sub>
</td>
</tr>
@@ -88,6 +129,11 @@ over you, so you can see when you can open without pulling aggro: `GO +4210` (gr
threshold; a typical raid burst over 3 s is a few thousand threat, Execute crits can exceed 10000, so
raise it to match your own burst); a *Threat* switch per list row (on for players, off for pets, totems,
mobs and hostile players).
- **Set the required buffer yourself.** AnySCT does not work it out for you: how much threat you can put out
in one burst depends on your class, spec, gear and buffs (an enhancement shaman's Windfury triple crit, a
warrior's Execute crit), and it changes as your gear improves. The default of 1500 suits a fresh level 60
or a levelling character. Watch how far your own biggest burst moves the number and set the slider at
least that high; GO only means "the tank's lead is bigger than the buffer you chose".
- **Experimental:** the packet format is taken from the TWThreat addon and has not been confirmed on every
server build. If it does not work for you, switch on *Record threat trace* (Placement tab, Debug) and
send `<WoW>\imports\anysct_threat.txt` with your bug report (needs SuperWoW). The trace is off by default
@@ -151,9 +197,9 @@ character.
| `list` | print the list |
| `clear` | empty the list |
| `colours` | give every listed unit a new automatic colour |
| `place clamp/anchoroff/anchor` | where the text appears |
| `place clamp/anchoroff/anchor/both` | where the text appears |
| `unlock` / `lock` | move the fixed anchor (drag the box; a demo stream plays) |
| `demo` | sample text at your target's nameplate, to compare with your own numbers |
| `demo` | sample text and a 2.5 s damage burst at your target's nameplate, to compare with your own numbers and judge the line tower |
| `threat` | one line with the state of the GO / WAIT label |
| `debug` | toggle the event / drop trace in the chat frame |
| `mem` | memory self-test (idle and a demo burst) |
@@ -206,11 +252,14 @@ six sliders including the line limits) below.*
| Show spell name | ability name under the number for special attacks | off |
| Show healer name | healer's name under heal numbers | on |
| Mark glancing / crushing | add the word after the number | off |
| Merge identical hits | identical attacks (same source, victim, ability; normal hit or crit) within the merge window become one line with the running sum, updated in place; words are not summed | on |
| Show hit count | append the number of merged hits, e.g. `12 x5` | off |
| Merge window | seconds a line keeps absorbing identical hits, counted from its first hit (0.1-1.5) | 0.4 |
| Font | Client (the damage font, honours pfUI) / FrizQT / Skurri / Arial Narrow | Client |
| 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 |
@@ -243,7 +292,7 @@ list label, and the Debug section.*
| control | what it does | default |
|---|---|---|
| Placement | Nameplate, clamp (over the victim's plate, held at the screen edge) / Nameplate, else anchor / Always anchor | Nameplate, clamp |
| Placement | Nameplate, clamp (over the victim's plate, held at the screen edge) / Nameplate, else anchor / Always anchor / Nameplate + anchor (both at once, separate line limits) | Nameplate, clamp |
| No nameplate | when the victim has no visible plate: Anchor / Hide | Anchor |
| Plate X offset | horizontal shift from the plate centre, -150..150 | 40 |
| Plate Y offset | vertical shift from the plate's top edge, -100..100 | 0 |
@@ -252,7 +301,7 @@ list label, and the Debug section.*
| Anchor X / Anchor Y | position of the fixed anchor from the screen centre, -800..800 / -500..500 | 0 / 120 |
| Unlock anchor / Lock anchor | drag the anchor box; a demo stream plays while unlocked | |
| Reset anchor | back to screen centre, 120 px up | |
| Demo | hit, hit, crit, miss, dodge, parry, block at your target's plate (anchor without a target) | |
| Demo | starts at once with a 2.5 s burst of your first targetSCT's class skills (a shaman demo tank without one) on one victim, using all your display settings (Stormstrike, Earth Shock, tiny Fire Strike ticks, melee, crits, extra attacks, and every result type: crit, miss, dodge, parry, block, resist) that fills the per-mob line tower, plus a simulated GO / WAIT threat label (works with Threat GO off); at your target's plate (anchor without a target); click again to restart | |
| Show targetSCT list label | a `targetSCTs: ...` line above the anchor | off |
| Record threat trace | writes the received threat data to `<WoW>\imports\anysct_threat.txt` (needs SuperWoW), for bug reports | off |
@@ -262,7 +311,7 @@ Enemy nameplates must be on (V key) for text over the mob.
The text always floats over the **victim** of the event, not over the source. Placement modes: over the
nameplate clamped to the screen edge (default), over the nameplate with the fixed anchor as fallback,
or always the fixed anchor. At the fixed anchor the victim's name is always added because the position no
always the fixed anchor, or both the plate and the fixed anchor at once (the GO / WAIT label too; with no plate it shows once at the anchor). At the fixed anchor the victim's name is always added because the position no
longer identifies the mob. If no plate addon or plate is found the text simply uses the fixed anchor.
## Colours and names
+26 -4
View File
@@ -28,6 +28,9 @@ AnySCT_DEFAULTS = {
maxLinesPerTarget = 16,
onlyWhileSourceFights = false, -- true: hide text while the listed source itself is not in combat (renamed from showOnlyInCombat, which gated on the PLAYER)
colourize = false, -- false: everything orange like the pet text; true: crit yellow, miss red, block blue, ...
mergeHits = true, -- identical attacks (same source, victim, attack, hit/crit) within a short window become ONE line with the running sum
mergeWindow = 0.4, -- seconds a line keeps absorbing identical hits
mergeCount = false, -- append " x5" to a merged line
markGlancing = false, -- true: add "glancing" / "crushing" after the number
guessTotemOwner = false, -- unknown totem owner = the only listed shaman (can misattribute enemy / own totems)
classColours = false, -- numbers / words take the source's class colour (pets: the owner's)
@@ -49,7 +52,7 @@ AnySCT_DEFAULTS = {
minimap = true,
mmAngle = 200,
importMain = true,
placement = "clamp", -- "clamp" | "anchorOff" | "anchor"
placement = "clamp", -- "clamp" | "anchorOff" | "anchor" | "both"
noPlate = "anchor", -- "anchor" | "hide"
edgeMargin = 40,
xOffset = 40, -- plate modes: shift right so targetSCT numbers sit beside the player's own
@@ -1242,6 +1245,23 @@ function C.LeadFromPos(p)
return p * 100
end
-- Fixed-anchor geometry. Frame rects (GetCenter/GetLeft/...) and SetPoint offsets live in one
-- coordinate system; UIParent:GetWidth()/GetHeight() do NOT (1834x768 against a 2580x1080 rect on a
-- UI scale of 0.711), so the screen centre must come from UIParent's own rect, never from its size.
function C.AnchorPosFromCenter(cx, cy, ux, uy)
return { point = "CENTER", x = math.floor(cx - ux + 0.5), y = math.floor(cy - uy + 0.5) }
end
-- Half extents of the screen as slider limits: rounded up to a multiple of 5, at least 100.
function C.ScreenHalfRange(left, right, bottom, top)
local function half(a, b)
local h = math.ceil(((b or 0) - (a or 0)) / 2 / 5) * 5
if h < 100 then h = 100 end
return h
end
return half(left, right), half(bottom, top)
end
function C.PosFromLead(v)
v = v or 0
if v < 0 then v = 0 end
@@ -1346,6 +1366,8 @@ local function threatSrcName(name)
return nil
end
C.ThreatSrcName = threatSrcName
local function threatHide()
if TH.shown then
TH.shown = false
@@ -1427,7 +1449,7 @@ local HELP = {
" clear remove every targetSCT",
" colours re-assign the targetSCT colours automatically",
" threat state of the experimental threat GO / WAIT label",
" place clamp|anchoroff|anchor where the text appears",
" place clamp|anchoroff|anchor|both where the text appears",
" unlock | lock move the fixed anchor",
" demo show sample text",
" mem measure memory use (10 s idle, then a demo burst)",
@@ -1449,7 +1471,7 @@ function C.Canon(n)
return nil
end
local PLACE = { clamp = "clamp", anchoroff = "anchorOff", anchor = "anchor" }
local PLACE = { clamp = "clamp", anchoroff = "anchorOff", anchor = "anchor", both = "both" }
function C.Slash(msg)
msg = msg or ""
@@ -1482,7 +1504,7 @@ function C.Slash(msg)
C.Print("targetSCT colours re-assigned")
elseif cmd == "place" then
local p = PLACE[string.lower(rest)]
if p then C.SetOpt("placement", p) C.Print("placement: " .. p) else C.Print("usage: /anysct place clamp|anchoroff|anchor") end
if p then C.SetOpt("placement", p) C.Print("placement: " .. p) else C.Print("usage: /anysct place clamp|anchoroff|anchor|both") end
elseif cmd == "unlock" or cmd == "lock" then
if AnySCT_SCT and AnySCT_SCT.SetMoveMode then AnySCT_SCT.SetMoveMode(cmd == "unlock") end
elseif cmd == "demo" then
+25 -9
View File
@@ -36,6 +36,10 @@ if CreateFrame then
"Print the healer's name under the heal number." },
{ "colourize", "Colour coding",
"Off: everything is orange like Blizzard's pet text. On: crit yellow, miss / dodge / parry red, block blue, absorb / resist / immune purple. A per-targetSCT colour (Colours tab) overrides this." },
{ "mergeHits", "Merge identical hits",
"Identical attacks (same source, victim and ability, normal hit or crit) that arrive within the merge window become ONE line whose number is the running sum, updated in place. Tames Fire Strike style tick storms. Misses, dodges and the like are not summed; identical words collapse to one line. On by default." },
{ "mergeCount", "Show hit count",
"Append the number of merged hits to a merged line, e.g. 12 x5. Off by default." },
{ "markGlancing", "Mark glancing / crushing",
"Add the word glancing or crushing after the number (Blizzard style). Off: shown as a plain number." },
{ "showSpells", "Show special attacks",
@@ -68,8 +72,8 @@ if CreateFrame then
local CYCLES = {
{ "placement", "Placement",
{ { "clamp", "Nameplate, clamp" }, { "anchorOff", "Nameplate, else anchor" },
{ "anchor", "Always anchor" } },
"Where the text appears. Over the nameplate needs enemy nameplates on (V). Clamp keeps text along the screen edge when the plate is off screen." },
{ "anchor", "Always anchor" }, { "both", "Nameplate + anchor" } },
"Where the text appears. Over the nameplate needs enemy nameplates on (V). Clamp keeps text along the screen edge when the plate is off screen. 'Nameplate + anchor' shows every line and the GO / WAIT label over the plate AND at the fixed anchor (separate line limits); without a plate it shows once at the anchor." },
{ "noPlate", "No nameplate",
{ { "anchor", "Anchor" }, { "hide", "Hide" } },
"What to do when the targetSCT's target has no visible nameplate." },
@@ -93,7 +97,9 @@ 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." },
{ "maxLines", "Max lines", 3, 100, 1, nil, "Maximum number of lines on screen over all mobs; when it is reached the oldest line is recycled." },
{ "maxLinesPerTarget", "Max lines per mob", 1, 30, 1, nil, "Maximum number of lines over one mob. Lines live 1.5 s, so 16 shows every hit of a burst of about 10 per second; lower values recycle the oldest line early." },
@@ -114,8 +120,8 @@ if CreateFrame then
local function getVal(key)
if key == "threatGoLead" then return C.PosFromLead(C.Opt(key) or 1500) end
if key == "posX" then return (C.Opt("pos") or {}).x or 0 end
if key == "posY" then return (C.Opt("pos") or {}).y or 0 end
if key == "posX" then return math.floor(((C.Opt("pos") or {}).x or 0) + 0.5) end
if key == "posY" then return math.floor(((C.Opt("pos") or {}).y or 0) + 0.5) end
return C.Opt(key) or 0
end
@@ -202,6 +208,15 @@ if CreateFrame then
local sl = sliders[i]
local v = getVal(sl.asctKey)
sl.asctBusy = true
if (sl.asctKey == "posX" or sl.asctKey == "posY") and AnySCT_SCT and AnySCT_SCT.ScreenRect then
local hw, hh = C.ScreenHalfRange(AnySCT_SCT.ScreenRect())
local lim = sl.asctKey == "posX" and hw or hh
-- never clamp a stored position: widen the range to hold it
if math.abs(v) > lim then lim = math.ceil(math.abs(v) / 5) * 5 end
sl:SetMinMaxValues(-lim, lim)
getglobal(sl:GetName() .. "Low"):SetText(tostring(-lim))
getglobal(sl:GetName() .. "High"):SetText(tostring(lim))
end
sl:SetValue(v)
sl.asctBusy = false
local txt = sl.asctFmt and string.format(sl.asctFmt, v) or tostring(v)
@@ -237,7 +252,7 @@ if CreateFrame then
O.refresh = refresh
-- Fixed panel geometry (UI scale 1): 520 x 460, four tabs hanging below the bottom edge, 16 px margins.
local PANEL_W, PANEL_H = 520, 460
local PANEL_W, PANEL_H = 520, 540
local COLA, COLB = 20, 270 -- two content columns, 230-240 wide each
local TOP = -48 -- first content row, below the title
@@ -424,6 +439,7 @@ if CreateFrame then
if d[8] then getglobal(name .. "High"):SetText("7500") end
sl:SetScript("OnValueChanged", function()
if this.asctBusy then return end
if AnySCT_SCT and AnySCT_SCT.IsDragging and AnySCT_SCT.IsDragging() then return end
local v = math.floor(this:GetValue() / this.asctStep + 0.5) * this.asctStep
local txt = this.asctFmt and string.format(this.asctFmt, v) or tostring(v)
if this.asctFmt then v = tonumber(txt) end
@@ -656,7 +672,7 @@ if CreateFrame then
y2 = y2 - 30
makeCycle(p2, "mobNameMode", COLA, y2)
y2 = y2 - 30
local CONTENT = { "showSpellName", "healShowHealer", "markGlancing" }
local CONTENT = { "showSpellName", "healShowHealer", "markGlancing", "mergeHits", "mergeCount" }
for i = 1, table.getn(CONTENT) do
makeCheck(p2, CONTENT[i], COLA, y2)
y2 = y2 - 21
@@ -668,7 +684,7 @@ if CreateFrame then
y2 = y2 - 30
makeCycle(p2, "direction", COLA, y2)
y2 = y2 - 48
local LOOK = { "sizeFactor", "wordFactor", "riseHeight", "jitter", "maxLines", "maxLinesPerTarget" }
local LOOK = { "sizeFactor", "wordFactor", "riseHeight", "jitter", "maxLines", "maxLinesPerTarget", "mergeWindow" }
for i = 1, table.getn(LOOK) do
local col = COLA + 10
if math.mod(i, 2) == 0 then col = COLB + 10 end
@@ -738,7 +754,7 @@ if CreateFrame then
end)
ax = ax + bPos:GetWidth() + 6
makeButton(p4, "AnySCTOptionsDemo", "Demo", nil, ax, y4, "Demo",
"Play hit, hit, crit, miss, dodge, parry, block at your target's nameplate (fixed anchor without a target) to compare with your own numbers.",
"Plays a 2.5 s burst of your first targetSCT's class skills (a shaman demo tank without one) on your target's nameplate (fixed anchor without a target), using all your display settings. Fills the per-mob tower and previews the GO / WAIT threat label; click again to restart.",
function() if AnySCT_SCT then AnySCT_SCT.Demo() end end)
y4 = y4 - 28
makeCheck(p4, "showLabel", COLA, y4)
+13 -1
View File
@@ -221,7 +221,7 @@ function P.PlatePosition(plate)
return cx, cy, UIParent:GetWidth(), UIParent:GetHeight()
end
-- Decide the display mode. opt needs: placement ("clamp" | "anchorOff" | "anchor"), noPlate
-- Decide the display mode. opt needs: placement ("clamp" | "anchorOff" | "anchor" | "both"), noPlate
-- ("anchor" | "hide"), edgeMargin. Returns mode, x, y, screenW, screenH (x, y only for mode 1/2).
function P.Select(plate, opt)
if opt.placement == "anchor" then return 3 end
@@ -237,6 +237,18 @@ function P.Select(plate, opt)
end
AnySCT_SelectMode = P.Select
-- Placement "both": the plate rules of "clamp" (modes 1/2) plus a copy at the fixed anchor. Without a
-- usable plate (none found, off screen with hide, or hidden) the anchor shows it once, whatever the
-- no-plate setting says. Returns mode, x, y, screenW, screenH, alsoAnchor.
function P.Places(plate, opt)
local mode, x, y, w, h = P.Select(plate, opt)
if opt.placement == "both" then
if mode == 1 or mode == 2 then return mode, x, y, w, h, true end
return 3
end
return mode, x, y, w, h, false
end
-- Does the plate still show the mob? Uses the last scan only (no per-frame GetName).
function P.PlateMatches(plate, guidNorm)
if not plate:IsVisible() then return false end
Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

+356 -53
View File
@@ -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)
@@ -216,6 +215,189 @@ function S.Format(res, opt, mode)
return text, colourKey, isWord, mul, suffix
end
-- Demo threat preview: the buffer climbs from ~300 to 1.3x the required lead over the burst (f 0..1).
function S.DemoThreat(f, need)
need = need or 1500
local start = (need > 300) and 300 or need * 0.2
if f < 0 then f = 0 elseif f > 1 then f = 1 end
local b = start + (need * 1.3 - start) * f
return (b >= need) and "GO" or "WAIT", b, need
end
-- Demo timeline: see S.BuildDemo. Event tables are reused on every run (no allocation while it plays).
-- Fixed-anchor box and the GO / WAIT label sitting inside it (rough glyph width: 0.62 x size per char).
S.ANCHOR_W, S.ANCHOR_H, S.ANCHOR_LABEL_SIZE, S.PLATE_LABEL_SIZE = 200, 30, 12, 14
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)
return (S.WORDS[res.kind] ~= nil) and (((res.amount or 0) <= 0) or (opt.colourize and true or false))
end
-- Result class that must match to merge: normal hits (glancing / crushing too) and crits stay apart.
function S.MergeClass(res)
local k = res.kind
if k == "crit" or res.crit then return "crit" end
if k == "hit" or k == "glancing" or k == "crushing" then return "hit" end
return k
end
-- Does line slot s (fields mKey, mWord, mCls, mSrc, mSpell, mHealer, mPet, t) absorb this result?
-- key = the place/victim key of the line (guid, name or "anchor"); words ignore the spell.
function S.MergeMatches(s, res, key, isWord, window)
if not s.active or s.mKey ~= key or s.mWord ~= isWord or s.t > window then return false end
if s.mCls ~= S.MergeClass(res) or s.mSrc ~= res.source or s.mPet ~= res.srcPet then return false end
if isWord then return true end
return s.mSpell == (res.spellId or res.spell) and s.mHealer == res.healer
end
function S.MergeLabel(text, count, showCount)
if showCount and count > 1 then return text .. " x" .. count end
return text
end
S.DEMO_BURST_LEN = 2.5
-- Demo source: the first listed targetSCT (name, class, own colour); without one a generic shaman tank.
S.DEMO_FALLBACK_NAME = "Shemp" -- leak-gate: allow
S.DEMO_VICTIM = "Training Dummy"
function S.DemoSource(st)
local e = st and st.order and st.byName and st.byName[st.order[1]]
if e then
return e.name, e.class, e.rgb, table.getn(st.order) > 1
end
return S.DEMO_FALLBACK_NAME, "SHAMAN", { 0, 0.44, 0.87 }, false
end
-- Burst skill sets per class. Entry: { spell (nil = white hit, "@kind" = miss word), weight, typical
-- amount, upper typical amount, typical crit amount, crit % , windfury-style extra hits }.
-- SHAMAN: shares and amounts from recorded tanking fights in dungeons and raids, last 30 days
-- (Stormstrike and Earth Shock are boosted so the preview shows them; crit rates are raised to
-- make a few crits visible). Every other class: plausible level-60 tank numbers, ESTIMATES.
S.DEMO_SETS = {
SHAMAN = {
{ "Fire Strike", 51, 2, 2, 3, 10 }, { "Lightning Strike", 15, 3, 3, 5, 12 },
{ "Tidal Wave", 10, 29, 31, 43, 10 }, { nil, 9, 97, 118, 216, 22, 2 },
{ "Engulfing Shadows", 6, 100, 100, 100, 0 }, { "Ancient Accord", 3, 100, 110, 150, 6 },
{ "Thundercall", 2, 39, 56, 56, 0 }, { "Earth Shock", 8, 666, 760, 992, 15 },
{ "Stormstrike", 10, 117, 140, 241, 30 }, { "Flame Shock", 2, 85, 100, 458, 5 },
{ "@miss", 1 }, { "@dodge", 1 }, { "@parry", 1 }, { "@block", 1 }, { "@resist", 1 },
},
WARRIOR = {
{ nil, 28, 150, 190, 320, 25 }, { "Heroic Strike", 20, 280, 340, 560, 25 },
{ "Revenge", 14, 350, 420, 700, 25 }, { "Shield Slam", 14, 480, 560, 900, 25 },
{ "Sunder Armor", 10, 60, 60, 60, 0 }, { "@miss", 1 }, { "@dodge", 1 }, { "@parry", 1 }, { "@block", 1 },
},
DRUID = {
{ nil, 24, 120, 150, 260, 25 }, { "Maul", 22, 260, 320, 520, 25 }, { "Swipe", 30, 90, 110, 190, 25 },
{ "Lacerate", 12, 70, 70, 70, 0 }, { "@miss", 1 }, { "@dodge", 1 }, { "@parry", 1 }, { "@block", 1 },
},
PALADIN = {
{ nil, 28, 140, 175, 300, 25 }, { "Holy Shield", 18, 100, 120, 200, 0 },
{ "Consecration", 16, 70, 70, 70, 0 }, { "Seal of Righteousness", 22, 90, 110, 190, 25 },
{ "Hammer of the Righteous", 8, 320, 380, 640, 25 }, { "@miss", 1 }, { "@dodge", 1 }, { "@parry", 1 }, { "@block", 1 },
},
}
function S.DemoSet(class)
return S.DEMO_SETS[class or ""] or S.DEMO_SETS.WARRIOR
end
-- Timeline: a 2.5 s burst starting at once, of ~9 slots/s picked from the class set
-- by weight (deterministic credit round robin, so rare skills still show up). Each entry: t, f (0..1
-- through the burst, nil before it), sn (spell name or nil), res (event table, reused every run).
function S.BuildDemo(class)
local set = S.DemoSet(class)
local list, n = {}, 0
local t0
local function add(t, kind, amount, sn)
n = n + 1
list[n] = { t = t, sn = sn, res = { kind = kind, amount = amount } }
if t0 then list[n].f = (t - t0) / S.DEMO_BURST_LEN end
end
t0 = 0
local total = 0
local credit, uses, critAcc = {}, {}, {}
for i = 1, table.getn(set) do
total = total + set[i][2]
credit[i], uses[i], critAcc[i] = 0, 0, 60
end
local count = 22
-- every word of the set is guaranteed to appear once, spread over the burst
local forced, words = {}, {}
for i = 1, table.getn(set) do
if set[i][1] and string.sub(set[i][1], 1, 1) == "@" then table.insert(words, i) end
end
local nw = table.getn(words)
for j = 1, nw do forced[3 + math.floor((j - 1) * (count - 4) / nw)] = words[j] end
for slot = 1, count do
local t = t0 + (slot - 1) * S.DEMO_BURST_LEN / count
local best = 1
for i = 1, table.getn(set) do
credit[i] = credit[i] + set[i][2]
if credit[i] > credit[best] then best = i end
end
credit[best] = credit[best] - total
if forced[slot] then best = forced[slot] end
local d = set[best]
uses[best] = uses[best] + 1
local word = d[1] and string.sub(d[1], 1, 1) == "@" and string.sub(d[1], 2)
if word then
add(t, word, 0)
else
local crit = false
local rate = d[6] or 0
if rate > 0 then
critAcc[best] = critAcc[best] + rate
if critAcc[best] >= 100 then critAcc[best] = critAcc[best] - 100; crit = true end
end
local amount = crit and d[5] or ((math.mod(uses[best], 2) == 1) and d[3] or d[4])
add(t, crit and "crit" or "hit", amount, d[1])
if not d[1] and d[7] and uses[best] == 2 then -- an extra attack in the same frame
for _ = 1, d[7] do add(t, "hit", d[3], nil) end
end
end
end
return list, n, list[n].t + 1.5
end
if CreateFrame then
local W = AnySCT_W
local C = AnySCT_Core
@@ -224,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
@@ -258,6 +442,19 @@ if CreateFrame then
end
end
local dragging = false
-- UIParent's rect in the same coordinate system as the anchor's (see C.AnchorPosFromCenter).
function S.ScreenRect()
local l, b, r, t = UIParent:GetLeft(), UIParent:GetBottom(), UIParent:GetRight(), UIParent:GetTop()
if not (l and b and r and t) then
l, b, r, t = 0, 0, UIParent:GetWidth(), UIParent:GetHeight()
end
return l, b, r, t
end
function S.IsDragging() return dragging end
local function placeAnchor()
local pos = opt("pos") or AnySCT_DEFAULTS.pos
anchor:ClearAllPoints()
@@ -276,8 +473,8 @@ if CreateFrame then
float:SetClampedToScreen(true)
local a = CreateFrame("Frame", "AnySCTAnchor", UIParent)
a:SetWidth(160)
a:SetHeight(30)
a:SetWidth(S.ANCHOR_W)
a:SetHeight(S.ANCHOR_H)
a:SetFrameStrata("HIGH")
a:SetFrameLevel(10)
a:SetMovable(true)
@@ -286,12 +483,18 @@ if CreateFrame then
a:RegisterForDrag("LeftButton")
a:SetBackdrop(W.SOLID_BACKDROP)
a:SetScript("OnDragStart", function()
if moveMode then this:StartMoving() end
if moveMode then
dragging = true
this:StartMoving()
end
end)
a:SetScript("OnDragStop", function()
this:StopMovingOrSizing()
dragging = false
local cx, cy = this:GetCenter()
C.SetOpt("pos", { point = "CENTER", x = cx - UIParent:GetWidth() / 2, y = cy - UIParent:GetHeight() / 2 })
if not cx then return end
local l, b, r, t = S.ScreenRect()
C.SetOpt("pos", C.AnchorPosFromCenter(cx, cy, (l + r) / 2, (b + t) / 2))
end)
anchor = a
@@ -383,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
@@ -403,7 +607,11 @@ if CreateFrame then
if y > s.lastH - margin then y = s.lastH - margin end
s.fs:SetPoint("BOTTOM", UIParent, "BOTTOMLEFT", x, y)
else
s.fs:SetPoint("BOTTOM", anchor, "CENTER", s.dx, off)
if s.dir < 0 then
s.fs:SetPoint("TOP", anchor, "BOTTOM", s.dx, off - 2)
else
s.fs:SetPoint("BOTTOM", anchor, "TOP", s.dx, off + S.AnchorLineOffset(opt("showLabel")))
end
end
s.fs:SetAlpha(a)
s.fs:SetShadowColor(0, 0, 0, a)
@@ -420,8 +628,36 @@ if CreateFrame then
local o = C.Opts()
local key = res.mobGuid or res.mobName or "anchor"
if mode == 3 then key = "anchor" end
local isWord0 = S.IsWordResult(res, o)
if o.mergeHits ~= false then
local window = o.mergeWindow or 0.4
for j = 1, POOL_MAX do
local m = slots[j]
if m.active and S.MergeMatches(m, res, key, isWord0, window) then
-- fold into the live line: same position and fade, only the text changes
if not isWord0 then
m.mSum = m.mSum + (res.amount or 0)
m.mCount = m.mCount + 1
local a0 = res.amount
res.amount = m.mSum
local text = S.Format(res, o, mode)
res.amount = a0
text = S.MergeLabel(text, m.mCount, o.mergeCount)
if text ~= m.mText then
m.mText = text
m.fs:SetText(text)
end
end
C.Ledger(res, "merged")
return
end
end
end
local i = acquire(key)
local s = slots[i]
s.mKey, s.mWord, s.mCls, s.mSrc, s.mPet = key, isWord0, S.MergeClass(res), res.source, res.srcPet
s.mSpell, s.mHealer = res.spellId or res.spell, res.healer
s.mSum, s.mCount = res.amount or 0, 1
C.Ledger(res, s.active and "shown+recycled" or "shown")
s.key = key
ensureFs(i)
@@ -435,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
@@ -475,6 +728,7 @@ if CreateFrame then
local cr, cg, cb = S.PickColour(res, o, ckey)
fs:SetTextColor(cr, cg, cb)
fs:SetText(text)
s.mText = text
fs:SetAlpha(0)
fs:SetShadowColor(0, 0, 0, 0)
fs:Show()
@@ -503,7 +757,7 @@ if CreateFrame then
if o.placement ~= "anchor" and (res.mobGuid or res.mobName) then
plate = P.Find(res.mobGuid, res.mobName, res.mobUnit)
end
local mode, px, py, pw, ph = P.Select(plate, o)
local mode, px, py, pw, ph, also = P.Places(plate, o)
if C.debug then
C.Dbg("place " .. tostring(res.mobName) .. ": " .. (plate and "plate found" or "no plate")
.. ", mode " .. tostring(mode) .. (mode == 0 and " (hidden by option)" or ""))
@@ -513,11 +767,12 @@ if CreateFrame then
return
end
spawn(res, mode, plate, px, py, pw, ph)
if also then spawn(res, 3) end -- "Nameplate + anchor": a second, separately capped stack
end
function S.OptionChanged(key)
if not anchor then return end
if key == "all" or key == "pos" then placeAnchor() end
if (key == "all" or key == "pos") and not dragging then placeAnchor() end
S.UpdateLabel()
end
@@ -526,6 +781,7 @@ if CreateFrame then
-- (C.ThreatDecide); this only draws. The follow OnUpdate is attached only while the label is shown.
local TL = { fs = nil, shown = false, plate = nil, status = nil, text = nil, lastText = nil,
lastMode = 0, lastName = nil, mobName = nil, guid = nil }
local TL2 = { fs = nil, lastText = nil, lastMode = 0, lastName = nil } -- anchor copy ("both")
local followFrame
local findAcc = 0
@@ -536,6 +792,7 @@ if CreateFrame then
if TL.shown then
TL.shown = false
if TL.fs then TL.fs:Hide() end
if TL2.fs then TL2.fs:Hide() end
end
if followFrame then followFrame:SetScript("OnUpdate", nil) end
return
@@ -552,6 +809,31 @@ if CreateFrame then
followFrame:SetScript("OnUpdate", followUpdate)
end
-- Draw the label fs (state record L) for a placement mode; text and font change only on change.
local function drawLabel(L, fs, mode, px, py, pw, ph, margin, lift)
local size = (mode == 3) and S.ANCHOR_LABEL_SIZE or S.PLATE_LABEL_SIZE
if size ~= L.size then
L.size = size
fs:SetFont(S.FontPath(opt("font")), size, "OUTLINE")
L.lastText = nil
end
local name = (mode == 3) and TL.mobName or nil
if TL.text ~= L.lastText or mode ~= L.lastMode or name ~= L.lastName then
L.lastText, L.lastMode, L.lastName = TL.text, mode, name
fs:SetText(name and (name .. ": " .. TL.text) or TL.text)
if TL.status == "GO" then fs:SetTextColor(0.2, 1, 0.2) else fs:SetTextColor(1, 0.6, 0.1) end
end
fs:ClearAllPoints()
if mode == 3 then
fs:SetPoint("CENTER", anchor, "CENTER", 0, 0)
else
local x, y = px, py + lift
if mode == 2 then x, y = AnySCT_ClampToScreen(x, y, pw, ph, margin) end
fs:SetPoint("BOTTOM", UIParent, "BOTTOMLEFT", x, y)
end
fs:Show()
end
followUpdate = function()
if not TL.shown then return end
findAcc = findAcc + arg1
@@ -561,56 +843,47 @@ if CreateFrame then
TL.plate = nil
TL.mobName = UnitName("target")
TL.guid = C.ResolveGuid("target")
if o.placement ~= "anchor" then TL.plate = P.Find(TL.guid, TL.mobName, "target") end
if o.placement ~= "anchor" and not TL.forceAnchor then TL.plate = P.Find(TL.guid, TL.mobName, "target") end
end
local margin = o.edgeMargin or 40
local lift = S.TextSize(STYLE.sizeScale, o.sizeFactor or 1, GetScreenWidth(), GetScreenHeight()) + 6
local mode, px, py, pw, ph = P.Select(TL.plate, o)
if mode == 0 then TL.fs:Hide() return end
local name = (mode == 3) and TL.mobName or nil
if TL.text ~= TL.lastText or mode ~= TL.lastMode or name ~= TL.lastName then
TL.lastText, TL.lastMode, TL.lastName = TL.text, mode, name
TL.fs:SetText(name and (name .. ": " .. TL.text) or TL.text)
if TL.status == "GO" then TL.fs:SetTextColor(0.2, 1, 0.2) else TL.fs:SetTextColor(1, 0.6, 0.1) end
local mode, px, py, pw, ph, also = P.Places(TL.plate, o)
if mode == 0 then
TL.fs:Hide()
if TL2.fs then TL2.fs:Hide() end
return
end
TL.fs:ClearAllPoints()
if mode == 3 then
TL.fs:SetPoint("BOTTOM", anchor, "TOP", 0, 18)
else
local x, y = px, py + lift
if mode == 2 then x, y = AnySCT_ClampToScreen(x, y, pw, ph, margin) end
TL.fs:SetPoint("BOTTOM", UIParent, "BOTTOMLEFT", x, y)
drawLabel(TL, TL.fs, mode, px, py, pw, ph, margin, lift)
if also then
if not TL2.fs then
TL2.fs = float:CreateFontString("AnySCTThreatLabel2", "OVERLAY")
TL2.fs:SetFont(S.FontPath(opt("font")), S.ANCHOR_LABEL_SIZE, "OUTLINE")
end
drawLabel(TL2, TL2.fs, 3, nil, nil, nil, nil, margin, lift)
elseif TL2.fs then
TL2.fs:Hide()
end
TL.fs:Show()
end
-- ---- demo / move / memory ------------------------------------------------------------------
-- Fixed sequence for side-by-side comparison with the player's own real numbers.
local DEMO = {
{ kind = "hit", amount = 72 }, { kind = "hit", amount = 66 }, { kind = "crit", amount = 141 },
{ kind = "miss", amount = 0 }, { kind = "dodge", amount = 0 }, { kind = "parry", amount = 0 },
{ kind = "block", amount = 0 },
}
local demoIdx, demoLeft, demoAcc, demoTicker, demoAtAnchor = 0, 0, 0, nil, false
-- Fixed sequence for side-by-side comparison with the player's own real numbers, then the burst.
local demoCache = {}
local DEMO, DEMO_N, DEMO_END
local demoIdx, demoT, demoTicker, demoAtAnchor = 1, 0, nil, false
local dSrc, dClass, dRgb, dMulti, dVictim = nil, nil, nil, false, nil
-- /asct demo plays at the nameplate of the current target (the same placement rules as real
-- events); without a target, or with atAnchor, at the fixed anchor.
function S.Demo(atAnchor)
build()
demoLeft = table.getn(DEMO)
demoIdx = 0
demoAcc = 0
demoAtAnchor = atAnchor and true or false
if not demoTicker then demoTicker = CreateFrame("Frame", "AnySCTDemoFrame", UIParent) end
demoTicker:SetScript("OnUpdate", function()
demoAcc = demoAcc + arg1
if demoAcc < 0.45 then return end
demoAcc = 0
if demoLeft <= 0 then this:SetScript("OnUpdate", nil) return end
demoLeft = demoLeft - 1
local function demoOnUpdate()
demoT = demoT + arg1
local showName = opt("showSpellName")
while demoIdx <= DEMO_N and DEMO[demoIdx].t <= demoT do
local e = DEMO[demoIdx]
local res = e.res
demoIdx = demoIdx + 1
local res = DEMO[demoIdx]
res.mobGuid, res.mobName, res.mobUnit = nil, nil, nil
-- same fields a real event carries (C.Emit), so every display setting applies
res.source, res.srcRgb, res.srcMulti, res.class = dSrc, dRgb, dMulti, dClass
res.spell = (e.sn and showName) and e.sn or nil
res.spellId = e.sn and 1 or nil
res.mobGuid, res.mobName, res.mobUnit = nil, dVictim, nil
if not demoAtAnchor and UnitExists("target") then
res.mobGuid = C.ResolveGuid("target")
res.mobName = UnitName("target")
@@ -619,7 +892,37 @@ if CreateFrame then
else
spawn(res, 3)
end
end)
if e.f then
-- the GO / WAIT preview rides the burst (real label code path, even with threatGo off)
local status, buf, need = S.DemoThreat(e.f, opt("threatGoLead"))
S.SetThreat(status, buf, need, C.ThreatSrcName and C.ThreatSrcName(dSrc))
end
end
if demoT >= DEMO_END then
TL.forceAnchor = false
S.SetThreat(nil)
this:SetScript("OnUpdate", nil)
end
end
-- /asct demo plays at the nameplate of the current target (the same placement rules as real
-- events); without a target, or with atAnchor, at the fixed anchor. A second call restarts it.
function S.Demo(atAnchor)
build()
demoIdx, demoT = 1, 0
demoAtAnchor = atAnchor and true or false
dSrc, dClass, dRgb, dMulti = S.DemoSource(C.state)
dVictim = UnitExists("target") and UnitName("target") or S.DEMO_VICTIM
local key = S.DEMO_SETS[dClass or ""] and dClass or "WARRIOR"
if not demoCache[key] then
local l, n, e = S.BuildDemo(key)
demoCache[key] = { l, n, e }
end
DEMO, DEMO_N, DEMO_END = demoCache[key][1], demoCache[key][2], demoCache[key][3]
TL.forceAnchor = demoAtAnchor
S.SetThreat(nil)
if not demoTicker then demoTicker = CreateFrame("Frame", "AnySCTDemoFrame", UIParent) end
demoTicker:SetScript("OnUpdate", demoOnUpdate)
end
function S.FontStringCount()