Skip to content

Commit 9104e47

Browse files
committed
Implemented Lesser Ghoul efficiency tracker
1 parent 25e0e2e commit 9104e47

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed

src/analysis/retail/deathknight/unholy/CombatLogParser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import RunicPowerTracker from './modules/core/RunicPowerTracker';
1414
import VirulentPlagueEfficiency from './modules/spells/VirulentPlagueEfficiency';
1515
import SoulReaper from '../shared/talents/SoulReaper';
1616
import CommanderOfTheDead from './modules/talents/CommanderOfTheDead';
17+
import LesserGhoul from './modules/features/LesserGhoul';
1718
import RunicPowerGraph from './modules/core/RunicPowerGraph';
1819
import RuneGraph from './modules/core/RuneGraph';
1920
import Guide from './modules/Guide';
@@ -30,6 +31,7 @@ class CombatLogParser extends CoreCombatLogParser {
3031
// Features
3132
virulentPlagueEfficiency: VirulentPlagueEfficiency,
3233
suddenDoom: SuddenDoom,
34+
lesserGhoul: LesserGhoul,
3335
unholyRuneForge: UnholyRuneForgeChecker,
3436

3537
// Talents

src/analysis/retail/deathknight/unholy/modules/Buffs.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ class Buffs extends CoreAuras {
2222
timelineHighlight: false,
2323
},
2424
{ spellId: SPELLS.SUDDEN_DOOM_BUFF.id, timelineHighlight: true },
25+
{
26+
spellId: SPELLS.LESSER_GHOUL_BUFF.id,
27+
triggeredBySpellId: SPELLS.FESTERING_STRIKE.id,
28+
timelineHighlight: true,
29+
},
2530
{
2631
spellId: SPELLS.ESSENCE_OF_THE_BLOOD_QUEEN_BUFF.id,
2732
enabled: combatant.hasTalent(TALENTS.GIFT_OF_THE_SANLAYN_TALENT),
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import SPELLS from 'common/SPELLS';
2+
import Analyzer, { Options, SELECTED_PLAYER } from 'parser/core/Analyzer';
3+
import Events, {
4+
ApplyBuffEvent,
5+
ApplyBuffStackEvent,
6+
RemoveBuffEvent,
7+
RemoveBuffStackEvent,
8+
} from 'parser/core/Events';
9+
import { formatPercentage } from 'common/format';
10+
import BoringSpellValueText from 'parser/ui/BoringSpellValueText';
11+
import Statistic from 'parser/ui/Statistic';
12+
import { STATISTIC_ORDER } from 'parser/ui/StatisticBox';
13+
14+
const BUFF_DURATION_MS = 30000;
15+
const EXPIRE_BUFFER_MS = 100;
16+
17+
class LesserGhoul extends Analyzer {
18+
stacksGained = 0;
19+
stacksConsumed = 0;
20+
21+
private currentStacks = 0;
22+
private buffAppliedAt: number | null = null;
23+
24+
constructor(options: Options) {
25+
super(options);
26+
27+
this.addEventListener(
28+
Events.applybuff.by(SELECTED_PLAYER).spell(SPELLS.LESSER_GHOUL_BUFF),
29+
this.onApplyBuff,
30+
);
31+
this.addEventListener(
32+
Events.applybuffstack.by(SELECTED_PLAYER).spell(SPELLS.LESSER_GHOUL_BUFF),
33+
this.onApplyBuffStack,
34+
);
35+
this.addEventListener(
36+
Events.removebuffstack.by(SELECTED_PLAYER).spell(SPELLS.LESSER_GHOUL_BUFF),
37+
this.onRemoveBuffStack,
38+
);
39+
this.addEventListener(
40+
Events.removebuff.by(SELECTED_PLAYER).spell(SPELLS.LESSER_GHOUL_BUFF),
41+
this.onRemoveBuff,
42+
);
43+
}
44+
45+
onApplyBuff(event: ApplyBuffEvent) {
46+
this.stacksGained += 1;
47+
this.currentStacks = 1;
48+
this.buffAppliedAt = event.timestamp;
49+
}
50+
51+
onApplyBuffStack(event: ApplyBuffStackEvent) {
52+
const newStacks = event.stack;
53+
const gained = newStacks - this.currentStacks;
54+
this.stacksGained += gained;
55+
this.currentStacks = newStacks;
56+
this.buffAppliedAt = event.timestamp;
57+
}
58+
59+
onRemoveBuffStack(event: RemoveBuffStackEvent) {
60+
const newStacks = event.stack;
61+
const consumed = this.currentStacks - newStacks;
62+
this.stacksConsumed += consumed;
63+
this.currentStacks = newStacks;
64+
}
65+
66+
onRemoveBuff(event: RemoveBuffEvent) {
67+
if (this.currentStacks === 0) {
68+
return;
69+
}
70+
71+
/* The last stack fires removebuff instead of removebuffstack, so we use
72+
timing to tell apart a Scourge Strike consumption from a natural expiry. */
73+
const expectedExpireAt = (this.buffAppliedAt ?? event.timestamp) + BUFF_DURATION_MS;
74+
const isExpiration =
75+
event.timestamp >= expectedExpireAt - EXPIRE_BUFFER_MS &&
76+
event.timestamp <= expectedExpireAt + EXPIRE_BUFFER_MS;
77+
78+
if (!isExpiration) {
79+
this.stacksConsumed += 1;
80+
}
81+
82+
this.currentStacks = 0;
83+
this.buffAppliedAt = null;
84+
}
85+
86+
get stacksExpired() {
87+
return this.stacksGained - this.stacksConsumed;
88+
}
89+
90+
get efficiency() {
91+
return this.stacksGained > 0 ? this.stacksConsumed / this.stacksGained : 1;
92+
}
93+
94+
statistic() {
95+
return (
96+
<Statistic
97+
position={STATISTIC_ORDER.CORE(13)}
98+
size="flexible"
99+
tooltip={
100+
<>
101+
<div>
102+
You consumed {this.stacksConsumed} out of {this.stacksGained} Lesser Ghoul stacks.
103+
</div>
104+
<div>{this.stacksExpired} stacks expired without being used.</div>
105+
</>
106+
}
107+
>
108+
<BoringSpellValueText spell={SPELLS.LESSER_GHOUL_BUFF}>
109+
<>
110+
{formatPercentage(this.efficiency)} % <small>stack efficiency</small>
111+
</>
112+
</BoringSpellValueText>
113+
</Statistic>
114+
);
115+
}
116+
}
117+
118+
export default LesserGhoul;

src/common/SPELLS/deathknight.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,12 @@ const spells = {
374374
name: 'Lesser Ghoul',
375375
icon: 'spell_deathknight_festering_strike',
376376
},
377+
// Tracks active Lesser Ghoul count
378+
LESSER_GHOUL_ACTIVE: {
379+
id: 1242998,
380+
name: 'Lesser Ghoul',
381+
icon: 'inv_ghoulnorthrend',
382+
},
377383

378384
DREAD_PLAGUE: {
379385
id: 1240996,

0 commit comments

Comments
 (0)