Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76b3e1c58e | ||
|
|
1ea2afc333 | ||
|
|
4a0c325fd7 | ||
|
|
4600cc0719 |
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
## Title: AnySCT
|
## Title: AnySCT
|
||||||
## Notes: Blizzard-pet-style floating combat text for any units you pick: tanks, their pets and totems, mobs, enemy players.
|
## Notes: Blizzard-pet-style floating combat text for any units you pick: tanks, their pets and totems, mobs, enemy players.
|
||||||
## Author: ShempError
|
## Author: ShempError
|
||||||
## Version: 0.2.0
|
## Version: 0.3.1
|
||||||
## X-Website: https://octowow.st/git/ShempError/AnySCT
|
## X-Website: https://octowow.st/git/ShempError/AnySCT
|
||||||
## OptionalDeps: pfUI, CT_RaidAssist, oRA, TWThreat
|
## OptionalDeps: pfUI, CT_RaidAssist, oRA, TWThreat
|
||||||
## SavedVariables: AnySCTDB
|
## SavedVariables: AnySCTDB
|
||||||
|
|||||||
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
All notable changes to AnySCT are documented here.
|
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
|
## 0.2.0 — 2026-10-15
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -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
|
`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).
|
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
|
*The training dummies in Orgrimmar, seen from another character: a pet's orange melee "Miss" and gold spell
|
||||||
"Miss", a gold spell "Resist", and the experimental WAIT label under the dummy being attacked.*
|
"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
|
## 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):
|
**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
|
- **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.
|
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
|
- **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:
|
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,
|
- **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.
|
- **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
|
- **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).
|
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
|
- 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.
|
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
|
*A crit "366" popping over the Heroic Training Dummy, a "Block" in grey, and a GO label partly covered by
|
||||||
the crit.*
|
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)
|
## GO / WAIT threat label (experimental)
|
||||||
|
|
||||||
Off by default. Shows under the nameplate of **your own target** how much threat lead the targetSCT has
|
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>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="50%">
|
<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>
|
<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, a "Dodge" floats above</sub>
|
<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>
|
||||||
<td align="center" width="50%">
|
<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>
|
<sub><b>GO</b> — "GO +3225": the lead is large enough, opening is safe</sub>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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
|
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,
|
raise it to match your own burst); a *Threat* switch per list row (on for players, off for pets, totems,
|
||||||
mobs and hostile players).
|
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
|
- **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
|
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
|
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 |
|
| `list` | print the list |
|
||||||
| `clear` | empty the list |
|
| `clear` | empty the list |
|
||||||
| `colours` | give every listed unit a new automatic colour |
|
| `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) |
|
| `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 |
|
| `threat` | one line with the state of the GO / WAIT label |
|
||||||
| `debug` | toggle the event / drop trace in the chat frame |
|
| `debug` | toggle the event / drop trace in the chat frame |
|
||||||
| `mem` | memory self-test (idle and a demo burst) |
|
| `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 spell name | ability name under the number for special attacks | off |
|
||||||
| Show healer name | healer's name under heal numbers | on |
|
| Show healer name | healer's name under heal numbers | on |
|
||||||
| Mark glancing / crushing | add the word after the number | off |
|
| 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 |
|
| Font | Client (the damage font, honours pfUI) / FrizQT / Skurri / Arial Narrow | Client |
|
||||||
| Direction | text rises (Up) or sinks (Down) | Up |
|
| Direction | text rises (Up) or sinks (Down) | Up |
|
||||||
| Number size | factor on the engine size, 0.5-3 | 1.00 |
|
| Number size | factor on the engine size, 0.5-3 | 1.00 |
|
||||||
| Word size | same for Miss / Dodge / Parry ... | 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 |
|
| 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 | 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 |
|
| 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 |
|
| 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 |
|
| 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 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 |
|
| 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 |
|
| 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 | |
|
| Unlock anchor / Lock anchor | drag the anchor box; a demo stream plays while unlocked | |
|
||||||
| Reset anchor | back to screen centre, 120 px up | |
|
| 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 |
|
| 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 |
|
| 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
|
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,
|
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.
|
longer identifies the mob. If no plate addon or plate is found the text simply uses the fixed anchor.
|
||||||
|
|
||||||
## Colours and names
|
## Colours and names
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ AnySCT_DEFAULTS = {
|
|||||||
maxLinesPerTarget = 16,
|
maxLinesPerTarget = 16,
|
||||||
onlyWhileSourceFights = false, -- true: hide text while the listed source itself is not in combat (renamed from showOnlyInCombat, which gated on the PLAYER)
|
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, ...
|
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
|
markGlancing = false, -- true: add "glancing" / "crushing" after the number
|
||||||
guessTotemOwner = false, -- unknown totem owner = the only listed shaman (can misattribute enemy / own totems)
|
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)
|
classColours = false, -- numbers / words take the source's class colour (pets: the owner's)
|
||||||
@@ -49,7 +52,7 @@ AnySCT_DEFAULTS = {
|
|||||||
minimap = true,
|
minimap = true,
|
||||||
mmAngle = 200,
|
mmAngle = 200,
|
||||||
importMain = true,
|
importMain = true,
|
||||||
placement = "clamp", -- "clamp" | "anchorOff" | "anchor"
|
placement = "clamp", -- "clamp" | "anchorOff" | "anchor" | "both"
|
||||||
noPlate = "anchor", -- "anchor" | "hide"
|
noPlate = "anchor", -- "anchor" | "hide"
|
||||||
edgeMargin = 40,
|
edgeMargin = 40,
|
||||||
xOffset = 40, -- plate modes: shift right so targetSCT numbers sit beside the player's own
|
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
|
return p * 100
|
||||||
end
|
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)
|
function C.PosFromLead(v)
|
||||||
v = v or 0
|
v = v or 0
|
||||||
if v < 0 then v = 0 end
|
if v < 0 then v = 0 end
|
||||||
@@ -1346,6 +1366,8 @@ local function threatSrcName(name)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
C.ThreatSrcName = threatSrcName
|
||||||
|
|
||||||
local function threatHide()
|
local function threatHide()
|
||||||
if TH.shown then
|
if TH.shown then
|
||||||
TH.shown = false
|
TH.shown = false
|
||||||
@@ -1427,7 +1449,7 @@ local HELP = {
|
|||||||
" clear remove every targetSCT",
|
" clear remove every targetSCT",
|
||||||
" colours re-assign the targetSCT colours automatically",
|
" colours re-assign the targetSCT colours automatically",
|
||||||
" threat state of the experimental threat GO / WAIT label",
|
" 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",
|
" unlock | lock move the fixed anchor",
|
||||||
" demo show sample text",
|
" demo show sample text",
|
||||||
" mem measure memory use (10 s idle, then a demo burst)",
|
" mem measure memory use (10 s idle, then a demo burst)",
|
||||||
@@ -1449,7 +1471,7 @@ function C.Canon(n)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local PLACE = { clamp = "clamp", anchoroff = "anchorOff", anchor = "anchor" }
|
local PLACE = { clamp = "clamp", anchoroff = "anchorOff", anchor = "anchor", both = "both" }
|
||||||
|
|
||||||
function C.Slash(msg)
|
function C.Slash(msg)
|
||||||
msg = msg or ""
|
msg = msg or ""
|
||||||
@@ -1482,7 +1504,7 @@ function C.Slash(msg)
|
|||||||
C.Print("targetSCT colours re-assigned")
|
C.Print("targetSCT colours re-assigned")
|
||||||
elseif cmd == "place" then
|
elseif cmd == "place" then
|
||||||
local p = PLACE[string.lower(rest)]
|
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
|
elseif cmd == "unlock" or cmd == "lock" then
|
||||||
if AnySCT_SCT and AnySCT_SCT.SetMoveMode then AnySCT_SCT.SetMoveMode(cmd == "unlock") end
|
if AnySCT_SCT and AnySCT_SCT.SetMoveMode then AnySCT_SCT.SetMoveMode(cmd == "unlock") end
|
||||||
elseif cmd == "demo" then
|
elseif cmd == "demo" then
|
||||||
|
|||||||
+25
-9
@@ -36,6 +36,10 @@ if CreateFrame then
|
|||||||
"Print the healer's name under the heal number." },
|
"Print the healer's name under the heal number." },
|
||||||
{ "colourize", "Colour coding",
|
{ "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." },
|
"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",
|
{ "markGlancing", "Mark glancing / crushing",
|
||||||
"Add the word glancing or crushing after the number (Blizzard style). Off: shown as a plain number." },
|
"Add the word glancing or crushing after the number (Blizzard style). Off: shown as a plain number." },
|
||||||
{ "showSpells", "Show special attacks",
|
{ "showSpells", "Show special attacks",
|
||||||
@@ -68,8 +72,8 @@ if CreateFrame then
|
|||||||
local CYCLES = {
|
local CYCLES = {
|
||||||
{ "placement", "Placement",
|
{ "placement", "Placement",
|
||||||
{ { "clamp", "Nameplate, clamp" }, { "anchorOff", "Nameplate, else anchor" },
|
{ { "clamp", "Nameplate, clamp" }, { "anchorOff", "Nameplate, else anchor" },
|
||||||
{ "anchor", "Always anchor" } },
|
{ "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." },
|
"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",
|
{ "noPlate", "No nameplate",
|
||||||
{ { "anchor", "Anchor" }, { "hide", "Hide" } },
|
{ { "anchor", "Anchor" }, { "hide", "Hide" } },
|
||||||
"What to do when the targetSCT's target has no visible nameplate." },
|
"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." },
|
"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 / ..." },
|
{ "wordFactor", "Word size", 0.5, 3, 0.05, "%.2f", "Size factor of Miss / Dodge / Parry / ..." },
|
||||||
{ "riseHeight", "Rise (px)", 0, 300, 5, nil,
|
{ "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." },
|
{ "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." },
|
{ "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." },
|
{ "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)
|
local function getVal(key)
|
||||||
if key == "threatGoLead" then return C.PosFromLead(C.Opt(key) or 1500) end
|
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 == "posX" then return math.floor(((C.Opt("pos") or {}).x or 0) + 0.5) end
|
||||||
if key == "posY" then return (C.Opt("pos") or {}).y or 0 end
|
if key == "posY" then return math.floor(((C.Opt("pos") or {}).y or 0) + 0.5) end
|
||||||
return C.Opt(key) or 0
|
return C.Opt(key) or 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -202,6 +208,15 @@ if CreateFrame then
|
|||||||
local sl = sliders[i]
|
local sl = sliders[i]
|
||||||
local v = getVal(sl.asctKey)
|
local v = getVal(sl.asctKey)
|
||||||
sl.asctBusy = true
|
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:SetValue(v)
|
||||||
sl.asctBusy = false
|
sl.asctBusy = false
|
||||||
local txt = sl.asctFmt and string.format(sl.asctFmt, v) or tostring(v)
|
local txt = sl.asctFmt and string.format(sl.asctFmt, v) or tostring(v)
|
||||||
@@ -237,7 +252,7 @@ if CreateFrame then
|
|||||||
O.refresh = refresh
|
O.refresh = refresh
|
||||||
|
|
||||||
-- Fixed panel geometry (UI scale 1): 520 x 460, four tabs hanging below the bottom edge, 16 px margins.
|
-- 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 COLA, COLB = 20, 270 -- two content columns, 230-240 wide each
|
||||||
local TOP = -48 -- first content row, below the title
|
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
|
if d[8] then getglobal(name .. "High"):SetText("7500") end
|
||||||
sl:SetScript("OnValueChanged", function()
|
sl:SetScript("OnValueChanged", function()
|
||||||
if this.asctBusy then return end
|
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 v = math.floor(this:GetValue() / this.asctStep + 0.5) * this.asctStep
|
||||||
local txt = this.asctFmt and string.format(this.asctFmt, v) or tostring(v)
|
local txt = this.asctFmt and string.format(this.asctFmt, v) or tostring(v)
|
||||||
if this.asctFmt then v = tonumber(txt) end
|
if this.asctFmt then v = tonumber(txt) end
|
||||||
@@ -656,7 +672,7 @@ if CreateFrame then
|
|||||||
y2 = y2 - 30
|
y2 = y2 - 30
|
||||||
makeCycle(p2, "mobNameMode", COLA, y2)
|
makeCycle(p2, "mobNameMode", COLA, y2)
|
||||||
y2 = y2 - 30
|
y2 = y2 - 30
|
||||||
local CONTENT = { "showSpellName", "healShowHealer", "markGlancing" }
|
local CONTENT = { "showSpellName", "healShowHealer", "markGlancing", "mergeHits", "mergeCount" }
|
||||||
for i = 1, table.getn(CONTENT) do
|
for i = 1, table.getn(CONTENT) do
|
||||||
makeCheck(p2, CONTENT[i], COLA, y2)
|
makeCheck(p2, CONTENT[i], COLA, y2)
|
||||||
y2 = y2 - 21
|
y2 = y2 - 21
|
||||||
@@ -668,7 +684,7 @@ if CreateFrame then
|
|||||||
y2 = y2 - 30
|
y2 = y2 - 30
|
||||||
makeCycle(p2, "direction", COLA, y2)
|
makeCycle(p2, "direction", COLA, y2)
|
||||||
y2 = y2 - 48
|
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
|
for i = 1, table.getn(LOOK) do
|
||||||
local col = COLA + 10
|
local col = COLA + 10
|
||||||
if math.mod(i, 2) == 0 then col = COLB + 10 end
|
if math.mod(i, 2) == 0 then col = COLB + 10 end
|
||||||
@@ -738,7 +754,7 @@ if CreateFrame then
|
|||||||
end)
|
end)
|
||||||
ax = ax + bPos:GetWidth() + 6
|
ax = ax + bPos:GetWidth() + 6
|
||||||
makeButton(p4, "AnySCTOptionsDemo", "Demo", nil, ax, y4, "Demo",
|
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)
|
function() if AnySCT_SCT then AnySCT_SCT.Demo() end end)
|
||||||
y4 = y4 - 28
|
y4 = y4 - 28
|
||||||
makeCheck(p4, "showLabel", COLA, y4)
|
makeCheck(p4, "showLabel", COLA, y4)
|
||||||
|
|||||||
+13
-1
@@ -221,7 +221,7 @@ function P.PlatePosition(plate)
|
|||||||
return cx, cy, UIParent:GetWidth(), UIParent:GetHeight()
|
return cx, cy, UIParent:GetWidth(), UIParent:GetHeight()
|
||||||
end
|
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).
|
-- ("anchor" | "hide"), edgeMargin. Returns mode, x, y, screenW, screenH (x, y only for mode 1/2).
|
||||||
function P.Select(plate, opt)
|
function P.Select(plate, opt)
|
||||||
if opt.placement == "anchor" then return 3 end
|
if opt.placement == "anchor" then return 3 end
|
||||||
@@ -237,6 +237,18 @@ function P.Select(plate, opt)
|
|||||||
end
|
end
|
||||||
AnySCT_SelectMode = P.Select
|
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).
|
-- Does the plate still show the mob? Uses the last scan only (no per-frame GetName).
|
||||||
function P.PlateMatches(plate, guidNorm)
|
function P.PlateMatches(plate, guidNorm)
|
||||||
if not plate:IsVisible() then return false end
|
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 |
@@ -24,7 +24,6 @@ AnySCT_STYLE = {
|
|||||||
fadeOutWord = 1.0, -- Miss / Dodge / Parry / Block ...
|
fadeOutWord = 1.0, -- Miss / Dodge / Parry / Block ...
|
||||||
fadeOutAbsorb = 0.09, -- "Absorb" is a quick flicker
|
fadeOutAbsorb = 0.09, -- "Absorb" is a quick flicker
|
||||||
riseHeight = 120, -- px a normal text rises (engine: 2 world units; screen distance varies)
|
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)
|
sizeScale = 0.018333, -- text pixel size = scale x screen diagonal (1024x768 -> 23 px)
|
||||||
critSizeScale = 0.0275, -- crits (1024x768 -> 35 px)
|
critSizeScale = 0.0275, -- crits (1024x768 -> 35 px)
|
||||||
shadow = 0.002, -- shadow offset, fraction of the viewport per axis (down-right)
|
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
|
return text, colourKey, isWord, mul, suffix
|
||||||
end
|
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
|
if CreateFrame then
|
||||||
local W = AnySCT_W
|
local W = AnySCT_W
|
||||||
local C = AnySCT_Core
|
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 POOL_MAX = 100 -- slot tables only; FontStrings are created on first use (ensureFs)
|
||||||
local critAnimated = S.CurveAnimated(STYLE.critKeyframes)
|
local critAnimated = S.CurveAnimated(STYLE.critKeyframes)
|
||||||
local critActive = S.CurveActive(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 GAP = 3 -- px between the plate top and the text
|
||||||
|
|
||||||
local anchor, float, listLabel
|
local anchor, float, listLabel
|
||||||
@@ -258,6 +442,19 @@ if CreateFrame then
|
|||||||
end
|
end
|
||||||
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 function placeAnchor()
|
||||||
local pos = opt("pos") or AnySCT_DEFAULTS.pos
|
local pos = opt("pos") or AnySCT_DEFAULTS.pos
|
||||||
anchor:ClearAllPoints()
|
anchor:ClearAllPoints()
|
||||||
@@ -276,8 +473,8 @@ if CreateFrame then
|
|||||||
float:SetClampedToScreen(true)
|
float:SetClampedToScreen(true)
|
||||||
|
|
||||||
local a = CreateFrame("Frame", "AnySCTAnchor", UIParent)
|
local a = CreateFrame("Frame", "AnySCTAnchor", UIParent)
|
||||||
a:SetWidth(160)
|
a:SetWidth(S.ANCHOR_W)
|
||||||
a:SetHeight(30)
|
a:SetHeight(S.ANCHOR_H)
|
||||||
a:SetFrameStrata("HIGH")
|
a:SetFrameStrata("HIGH")
|
||||||
a:SetFrameLevel(10)
|
a:SetFrameLevel(10)
|
||||||
a:SetMovable(true)
|
a:SetMovable(true)
|
||||||
@@ -286,12 +483,18 @@ if CreateFrame then
|
|||||||
a:RegisterForDrag("LeftButton")
|
a:RegisterForDrag("LeftButton")
|
||||||
a:SetBackdrop(W.SOLID_BACKDROP)
|
a:SetBackdrop(W.SOLID_BACKDROP)
|
||||||
a:SetScript("OnDragStart", function()
|
a:SetScript("OnDragStart", function()
|
||||||
if moveMode then this:StartMoving() end
|
if moveMode then
|
||||||
|
dragging = true
|
||||||
|
this:StartMoving()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
a:SetScript("OnDragStop", function()
|
a:SetScript("OnDragStop", function()
|
||||||
this:StopMovingOrSizing()
|
this:StopMovingOrSizing()
|
||||||
|
dragging = false
|
||||||
local cx, cy = this:GetCenter()
|
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)
|
end)
|
||||||
anchor = a
|
anchor = a
|
||||||
|
|
||||||
@@ -383,13 +586,14 @@ if CreateFrame then
|
|||||||
if t < STYLE.fadeIn then a = t / STYLE.fadeIn end
|
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
|
if t > s.fadeOut then a = a * (s.dur - t) / (s.dur - s.fadeOut) end
|
||||||
local rise = s.rise
|
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
|
if s.crit then
|
||||||
rise = rise * STYLE.critRiseFactor
|
|
||||||
if critAnimated then
|
if critAnimated then
|
||||||
s.fs:SetTextHeight(s.base * S.KeyScale(STYLE.critKeyframes, t / s.dur))
|
s.fs:SetTextHeight(s.base * S.KeyScale(STYLE.critKeyframes, t / s.dur))
|
||||||
end
|
end
|
||||||
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()
|
s.fs:ClearAllPoints()
|
||||||
if s.plate then
|
if s.plate then
|
||||||
needScan = true
|
needScan = true
|
||||||
@@ -403,7 +607,11 @@ if CreateFrame then
|
|||||||
if y > s.lastH - margin then y = s.lastH - margin end
|
if y > s.lastH - margin then y = s.lastH - margin end
|
||||||
s.fs:SetPoint("BOTTOM", UIParent, "BOTTOMLEFT", x, y)
|
s.fs:SetPoint("BOTTOM", UIParent, "BOTTOMLEFT", x, y)
|
||||||
else
|
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
|
end
|
||||||
s.fs:SetAlpha(a)
|
s.fs:SetAlpha(a)
|
||||||
s.fs:SetShadowColor(0, 0, 0, a)
|
s.fs:SetShadowColor(0, 0, 0, a)
|
||||||
@@ -420,8 +628,36 @@ if CreateFrame then
|
|||||||
local o = C.Opts()
|
local o = C.Opts()
|
||||||
local key = res.mobGuid or res.mobName or "anchor"
|
local key = res.mobGuid or res.mobName or "anchor"
|
||||||
if mode == 3 then key = "anchor" end
|
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 i = acquire(key)
|
||||||
local s = slots[i]
|
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")
|
C.Ledger(res, s.active and "shown+recycled" or "shown")
|
||||||
s.key = key
|
s.key = key
|
||||||
ensureFs(i)
|
ensureFs(i)
|
||||||
@@ -435,9 +671,26 @@ if CreateFrame then
|
|||||||
local base = S.TextSize(scale, factor, sw, sh)
|
local base = S.TextSize(scale, factor, sw, sh)
|
||||||
s.base = base * mul
|
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
|
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
|
end
|
||||||
|
|
||||||
s.active = true
|
s.active = true
|
||||||
@@ -475,6 +728,7 @@ if CreateFrame then
|
|||||||
local cr, cg, cb = S.PickColour(res, o, ckey)
|
local cr, cg, cb = S.PickColour(res, o, ckey)
|
||||||
fs:SetTextColor(cr, cg, cb)
|
fs:SetTextColor(cr, cg, cb)
|
||||||
fs:SetText(text)
|
fs:SetText(text)
|
||||||
|
s.mText = text
|
||||||
fs:SetAlpha(0)
|
fs:SetAlpha(0)
|
||||||
fs:SetShadowColor(0, 0, 0, 0)
|
fs:SetShadowColor(0, 0, 0, 0)
|
||||||
fs:Show()
|
fs:Show()
|
||||||
@@ -503,7 +757,7 @@ if CreateFrame then
|
|||||||
if o.placement ~= "anchor" and (res.mobGuid or res.mobName) then
|
if o.placement ~= "anchor" and (res.mobGuid or res.mobName) then
|
||||||
plate = P.Find(res.mobGuid, res.mobName, res.mobUnit)
|
plate = P.Find(res.mobGuid, res.mobName, res.mobUnit)
|
||||||
end
|
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
|
if C.debug then
|
||||||
C.Dbg("place " .. tostring(res.mobName) .. ": " .. (plate and "plate found" or "no plate")
|
C.Dbg("place " .. tostring(res.mobName) .. ": " .. (plate and "plate found" or "no plate")
|
||||||
.. ", mode " .. tostring(mode) .. (mode == 0 and " (hidden by option)" or ""))
|
.. ", mode " .. tostring(mode) .. (mode == 0 and " (hidden by option)" or ""))
|
||||||
@@ -513,11 +767,12 @@ if CreateFrame then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
spawn(res, mode, plate, px, py, pw, ph)
|
spawn(res, mode, plate, px, py, pw, ph)
|
||||||
|
if also then spawn(res, 3) end -- "Nameplate + anchor": a second, separately capped stack
|
||||||
end
|
end
|
||||||
|
|
||||||
function S.OptionChanged(key)
|
function S.OptionChanged(key)
|
||||||
if not anchor then return end
|
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()
|
S.UpdateLabel()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -526,6 +781,7 @@ if CreateFrame then
|
|||||||
-- (C.ThreatDecide); this only draws. The follow OnUpdate is attached only while the label is shown.
|
-- (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,
|
local TL = { fs = nil, shown = false, plate = nil, status = nil, text = nil, lastText = nil,
|
||||||
lastMode = 0, lastName = nil, mobName = nil, guid = 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 followFrame
|
||||||
local findAcc = 0
|
local findAcc = 0
|
||||||
|
|
||||||
@@ -536,6 +792,7 @@ if CreateFrame then
|
|||||||
if TL.shown then
|
if TL.shown then
|
||||||
TL.shown = false
|
TL.shown = false
|
||||||
if TL.fs then TL.fs:Hide() end
|
if TL.fs then TL.fs:Hide() end
|
||||||
|
if TL2.fs then TL2.fs:Hide() end
|
||||||
end
|
end
|
||||||
if followFrame then followFrame:SetScript("OnUpdate", nil) end
|
if followFrame then followFrame:SetScript("OnUpdate", nil) end
|
||||||
return
|
return
|
||||||
@@ -552,6 +809,31 @@ if CreateFrame then
|
|||||||
followFrame:SetScript("OnUpdate", followUpdate)
|
followFrame:SetScript("OnUpdate", followUpdate)
|
||||||
end
|
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()
|
followUpdate = function()
|
||||||
if not TL.shown then return end
|
if not TL.shown then return end
|
||||||
findAcc = findAcc + arg1
|
findAcc = findAcc + arg1
|
||||||
@@ -561,56 +843,47 @@ if CreateFrame then
|
|||||||
TL.plate = nil
|
TL.plate = nil
|
||||||
TL.mobName = UnitName("target")
|
TL.mobName = UnitName("target")
|
||||||
TL.guid = C.ResolveGuid("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
|
end
|
||||||
local margin = o.edgeMargin or 40
|
local margin = o.edgeMargin or 40
|
||||||
local lift = S.TextSize(STYLE.sizeScale, o.sizeFactor or 1, GetScreenWidth(), GetScreenHeight()) + 6
|
local lift = S.TextSize(STYLE.sizeScale, o.sizeFactor or 1, GetScreenWidth(), GetScreenHeight()) + 6
|
||||||
local mode, px, py, pw, ph = P.Select(TL.plate, o)
|
local mode, px, py, pw, ph, also = P.Places(TL.plate, o)
|
||||||
if mode == 0 then TL.fs:Hide() return end
|
if mode == 0 then
|
||||||
local name = (mode == 3) and TL.mobName or nil
|
TL.fs:Hide()
|
||||||
if TL.text ~= TL.lastText or mode ~= TL.lastMode or name ~= TL.lastName then
|
if TL2.fs then TL2.fs:Hide() end
|
||||||
TL.lastText, TL.lastMode, TL.lastName = TL.text, mode, name
|
return
|
||||||
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
|
|
||||||
end
|
end
|
||||||
TL.fs:ClearAllPoints()
|
drawLabel(TL, TL.fs, mode, px, py, pw, ph, margin, lift)
|
||||||
if mode == 3 then
|
if also then
|
||||||
TL.fs:SetPoint("BOTTOM", anchor, "TOP", 0, 18)
|
if not TL2.fs then
|
||||||
else
|
TL2.fs = float:CreateFontString("AnySCTThreatLabel2", "OVERLAY")
|
||||||
local x, y = px, py + lift
|
TL2.fs:SetFont(S.FontPath(opt("font")), S.ANCHOR_LABEL_SIZE, "OUTLINE")
|
||||||
if mode == 2 then x, y = AnySCT_ClampToScreen(x, y, pw, ph, margin) end
|
end
|
||||||
TL.fs:SetPoint("BOTTOM", UIParent, "BOTTOMLEFT", x, y)
|
drawLabel(TL2, TL2.fs, 3, nil, nil, nil, nil, margin, lift)
|
||||||
|
elseif TL2.fs then
|
||||||
|
TL2.fs:Hide()
|
||||||
end
|
end
|
||||||
TL.fs:Show()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ---- demo / move / memory ------------------------------------------------------------------
|
-- ---- demo / move / memory ------------------------------------------------------------------
|
||||||
-- Fixed sequence for side-by-side comparison with the player's own real numbers.
|
-- Fixed sequence for side-by-side comparison with the player's own real numbers, then the burst.
|
||||||
local DEMO = {
|
local demoCache = {}
|
||||||
{ kind = "hit", amount = 72 }, { kind = "hit", amount = 66 }, { kind = "crit", amount = 141 },
|
local DEMO, DEMO_N, DEMO_END
|
||||||
{ kind = "miss", amount = 0 }, { kind = "dodge", amount = 0 }, { kind = "parry", amount = 0 },
|
local demoIdx, demoT, demoTicker, demoAtAnchor = 1, 0, nil, false
|
||||||
{ kind = "block", amount = 0 },
|
local dSrc, dClass, dRgb, dMulti, dVictim = nil, nil, nil, false, nil
|
||||||
}
|
|
||||||
local demoIdx, demoLeft, demoAcc, demoTicker, demoAtAnchor = 0, 0, 0, nil, false
|
|
||||||
|
|
||||||
-- /asct demo plays at the nameplate of the current target (the same placement rules as real
|
local function demoOnUpdate()
|
||||||
-- events); without a target, or with atAnchor, at the fixed anchor.
|
demoT = demoT + arg1
|
||||||
function S.Demo(atAnchor)
|
local showName = opt("showSpellName")
|
||||||
build()
|
while demoIdx <= DEMO_N and DEMO[demoIdx].t <= demoT do
|
||||||
demoLeft = table.getn(DEMO)
|
local e = DEMO[demoIdx]
|
||||||
demoIdx = 0
|
local res = e.res
|
||||||
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
|
|
||||||
demoIdx = demoIdx + 1
|
demoIdx = demoIdx + 1
|
||||||
local res = DEMO[demoIdx]
|
-- same fields a real event carries (C.Emit), so every display setting applies
|
||||||
res.mobGuid, res.mobName, res.mobUnit = nil, nil, nil
|
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
|
if not demoAtAnchor and UnitExists("target") then
|
||||||
res.mobGuid = C.ResolveGuid("target")
|
res.mobGuid = C.ResolveGuid("target")
|
||||||
res.mobName = UnitName("target")
|
res.mobName = UnitName("target")
|
||||||
@@ -619,7 +892,37 @@ if CreateFrame then
|
|||||||
else
|
else
|
||||||
spawn(res, 3)
|
spawn(res, 3)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
function S.FontStringCount()
|
function S.FontStringCount()
|
||||||
|
|||||||
Reference in New Issue
Block a user