Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dragonflight' of https://github.com/WoWAnalyzer/WoWAnal…
Browse files Browse the repository at this point in the history
…yzer into Defensives
Krealle committed Mar 22, 2024
2 parents d4670fc + 14e371d commit ad8269b
Showing 40 changed files with 735 additions and 200 deletions.
6 changes: 5 additions & 1 deletion src/CHANGELOG.tsx
Original file line number Diff line number Diff line change
@@ -27,13 +27,17 @@ import {
Arlie,
LucasLevyOB,
dub,
Zyer,
Zyer, Earosselot,
} from 'CONTRIBUTORS';
import { ItemLink } from 'interface';
import SpellLink from 'interface/SpellLink';

// prettier-ignore
export default [
change(date(2024, 3, 14), 'Correct getBuffStacks method to return the stacks at the given timestamp', Earosselot),
change(date(2024, 3, 14), 'Fix overflow on cooldown bars while using the phase selector.', ToppleTheNun),
change(date(2024, 3, 14), 'Bump opacity on phase selector to 75% from 40%.', ToppleTheNun),
change(date(2024, 3, 13), 'Bump opacity on muted text to 75% from 47%.', ToppleTheNun),
change(date(2024, 3, 2), 'Correct an issue with the Power Word: Radiance icon.', emallson),
change(date(2024, 3, 2), 'Correct incorrect tertiary stat scaling above 25% raw and 19% character sheet rating', Putro),
change(date(2024, 2, 26), <>Added checklist support for <ItemLink id={ITEMS.IRIDAL_THE_EARTHS_MASTER.id}/>, <ItemLink id={ITEMS.DREAMBINDER_LOOM_OF_THE_GREAT_CYCLE.id}/>, <ItemLink id={ITEMS.BELORRELOS_THE_SUNCALLER.id}/>, <ItemLink id={ITEMS.NYMUES_UNRAVELING_SPINDLE.id}/></>, Zyer),
7 changes: 7 additions & 0 deletions src/CONTRIBUTORS.ts
Original file line number Diff line number Diff line change
@@ -1808,6 +1808,13 @@ export const Trevor: Contributor = {
avatar: avatar('Trevor-avatar.png'),
};

export const Harrek: Contributor = {
nickname: 'Harrek',
discord: 'harrek',
github: 'Harreks',
avatar: avatar('Harrek-avatar.png'),
};

export const Jeff: Contributor = {
nickname: 'Jeff',
discord: 'muhnameizjeff#8143',
1 change: 1 addition & 0 deletions src/analysis/retail/evoker/augmentation/CHANGELOG.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import SPELLS from 'common/SPELLS/evoker';
import RESOURCE_TYPES from 'game/RESOURCE_TYPES';

export default [
change(date(2024, 3, 7), <>Fix an issue with <SpellLink spell={TALENTS.POTENT_MANA_TALENT} /> module when no <SpellLink spell={TALENTS.SOURCE_OF_MAGIC_TALENT} /> was active during the fight.</>, Vollmer),
change(date(2024, 2, 10), <>Fix crash in <SpellLink spell={TALENTS.SOURCE_OF_MAGIC_TALENT} /> module.</>, Trevor),
change(date(2024, 2, 3), <>Implement <SpellLink spell={TALENTS.SOURCE_OF_MAGIC_TALENT} /> and <SpellLink spell={TALENTS.POTENT_MANA_TALENT} /> modules.</>, Vollmer),
change(date(2024, 1, 26), <>Show 4 default targets instead of 2 for Buff Helper MRT note.</>, Vollmer),
1 change: 1 addition & 0 deletions src/analysis/retail/evoker/devastation/CHANGELOG.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import SPELLS from 'common/SPELLS/evoker';
import RESOURCE_TYPES from 'game/RESOURCE_TYPES';

export default [
change(date(2024, 3, 7), <>Fix an issue with <SpellLink spell={TALENTS.POTENT_MANA_TALENT} /> module when no <SpellLink spell={TALENTS.SOURCE_OF_MAGIC_TALENT} /> was active during the fight.</>, Vollmer),
change(date(2024, 3, 6), <>Make it more apparent that you can mouseover points in the <SpellLink spell={SPELLS.DISINTEGRATE} /> graph.</>, Vollmer),
change(date(2024, 2, 10), <>Fix crash in <SpellLink spell={TALENTS.SOURCE_OF_MAGIC_TALENT} /> module.</>, Trevor),
change(date(2024, 2, 3), <>Implement <SpellLink spell={TALENTS.SOURCE_OF_MAGIC_TALENT} /> and <SpellLink spell={TALENTS.POTENT_MANA_TALENT} /> modules.</>, Vollmer),
4 changes: 3 additions & 1 deletion src/analysis/retail/evoker/preservation/CHANGELOG.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { change, date } from 'common/changelog';
import SPELLS from 'common/SPELLS';
import { TALENTS_EVOKER } from 'common/TALENTS';
import { ToppleTheNun, Trevor, Tyndi, Vohrr, Vollmer } from 'CONTRIBUTORS';
import { ToppleTheNun, Trevor, Tyndi, Vohrr, Vollmer, Harrek } from 'CONTRIBUTORS';
import RESOURCE_TYPES from 'game/RESOURCE_TYPES';
import { ResourceLink, SpellLink } from 'interface';

export default [
change(date(2024, 3, 12), <>Implement <SpellLink spell={TALENTS_EVOKER.TIME_OF_NEED_TALENT} /> module.</>, Harrek),
change(date(2024, 3, 7), <>Fix an issue with <SpellLink spell={TALENTS_EVOKER.POTENT_MANA_TALENT} /> module when no <SpellLink spell={TALENTS_EVOKER.SOURCE_OF_MAGIC_TALENT} /> was active during the fight.</>, Vollmer),
change(date(2024, 2, 18), <>Add spell cast time to <SpellLink spell={TALENTS_EVOKER.STASIS_TALENT}/> module</>, Trevor),
change(date(2024, 2, 10), <>Fix crash in <SpellLink spell={TALENTS_EVOKER.SOURCE_OF_MAGIC_TALENT} /> module.</>, Trevor),
change(date(2024, 2, 3), <>Implement <SpellLink spell={TALENTS_EVOKER.SOURCE_OF_MAGIC_TALENT} /> and <SpellLink spell={TALENTS_EVOKER.POTENT_MANA_TALENT} /> modules.</>, Vollmer),
2 changes: 2 additions & 0 deletions src/analysis/retail/evoker/preservation/CombatLogParser.ts
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ import FieldOfDreams from './modules/talents/FieldOfDreams';
import DreamFlight from './modules/talents/DreamFlight';
import ExhilBurst from './modules/talents/ExhilBurst';
import Stasis from './modules/talents/Stasis';
import TimeOfNeed from './modules/talents/TimeOfNeed';
import Lifebind from './modules/talents/Lifebind';
import EnergyLoop from './modules/talents/EnergyLoop';
import AlwaysBeCasting from './modules/core/AlwaysBeCasting';
@@ -116,6 +117,7 @@ class CombatLogParser extends CoreCombatLogParser {
fieldOfDreams: FieldOfDreams,
exhilBurst: ExhilBurst,
stasis: Stasis,
timeOfNeed: TimeOfNeed,
lifebind: Lifebind,
energyLoop: EnergyLoop,
fontOfMagic: FontOfMagic,
236 changes: 236 additions & 0 deletions src/analysis/retail/evoker/preservation/modules/talents/TimeOfNeed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import Analyzer, { Options, SELECTED_PLAYER } from 'parser/core/Analyzer';
import { getTimeOfNeedHealing } from '../../normalizers/CastLinkNormalizer';
import { TALENTS_EVOKER } from 'common/TALENTS';
import Events, { HealEvent, SummonEvent, DamageEvent } from 'parser/core/Events';
import SPELLS from 'common/SPELLS';
import LazyLoadStatisticBox from 'parser/ui/LazyLoadStatisticBox';
import STATISTIC_ORDER from 'parser/ui/STATISTIC_ORDER';
import STATISTIC_CATEGORY from 'parser/ui/STATISTIC_CATEGORY';
import { SpellIcon, SpellLink } from 'interface';
import { formatNumber } from 'common/format';
import fetchWcl from 'common/fetchWclApi';
import { WCLEventsResponse } from 'common/WCL_TYPES';
import Combatants from 'parser/shared/modules/Combatants';

//All possible results from a Time of Need proc
const Result = {
Proc: (
<>
Saved by <SpellLink spell={TALENTS_EVOKER.VERDANT_EMBRACE_TALENT} />
</>
),
Long: 'Saved by all the healing',
Not: 'Was not in danger of death',
Dead: 'Died anyway',
Bug: 'Time of Need fizzled out',
} as const;
type Results = typeof Result[keyof typeof Result];

interface TonEvent {
summon: SummonEvent;
verdantEmbrace: HealEvent | null;
livingFlames: HealEvent[];
livingFlameTotalHeal: number;
livingFlameTotalOverheal: number;
target: number;
result: Results;
}

class TimeOfNeed extends Analyzer {
static dependencies = {
combatants: Combatants,
};

protected combatants!: Combatants;
spawns: TonEvent[] = [];

constructor(options: Options) {
super(options);

this.active = this.selectedCombatant.hasTalent(TALENTS_EVOKER.TIME_OF_NEED_TALENT);

this.addEventListener(
Events.summon.by(SELECTED_PLAYER).spell(SPELLS.TIME_OF_NEED_SUMMON),
this.onSpawn,
);
}

onSpawn(event: SummonEvent) {
const healEvents = getTimeOfNeedHealing(event);

const currentEvent: TonEvent = {
summon: event,
verdantEmbrace: null,
livingFlames: [],
livingFlameTotalHeal: 0,
livingFlameTotalOverheal: 0,
target: 0,
result: Result.Not,
};

healEvents.forEach((heal) => {
if (heal.ability.guid === SPELLS.VERDANT_EMBRACE_HEAL.id) {
currentEvent.verdantEmbrace = heal;
currentEvent.target = heal.targetID;
} else if (heal.ability.guid === SPELLS.TIME_OF_NEED_LIVING_FLAME.id) {
currentEvent.livingFlames.push(heal);
currentEvent.livingFlameTotalHeal += heal.amount + (heal.absorbed || 0);
currentEvent.livingFlameTotalOverheal += heal.overheal ?? 0;
}
});

this.spawns.push(currentEvent);
}

parseDamageTaken(tonSpawn: TonEvent, eventList: WCLEventsResponse) {
//Check that ToN actually did any healing or if it bugged out
if (tonSpawn.verdantEmbrace === null && !tonSpawn.livingFlames.length) {
return Result.Bug;
}
//If there is no damage events, then Time of Need didn't save the player from anything
if (!eventList.events.length) {
return Result.Not;
}
const hits = eventList.events as DamageEvent[];
//Iterate all the damage taken events during the time window
for (const hit of hits) {
//Check that the info from the damage event is valid
if (hit.hitPoints !== undefined) {
//If the hitpoints after the hit are equal to 0, the player died anyway
if (!hit.hitPoints) {
return Result.Dead;
}
//Initialize the healing from Time of Need with the VE
let tonHealingAtThisPoint = tonSpawn.verdantEmbrace?.amount || 0;
//If there is no living flames casted, or this hit was before the first living flame landed
if (!tonSpawn.livingFlames.length || hit.timestamp < tonSpawn.livingFlames[0].timestamp) {
//Check if the player would've died from this hit without the Verdant Embrace healing
if (hit.hitPoints - tonHealingAtThisPoint <= 0) {
return Result.Proc; //Person was saved by the VE
}
} else {
//Add up all the healing done by the living flames that happened before this hit
tonSpawn.livingFlames.forEach((livingFlame) => {
if (livingFlame.timestamp < hit.timestamp) {
tonHealingAtThisPoint += livingFlame.amount;
}
});
//If the hit points after this hit minus the total ToN healing done to this point is equal or less than 0, then he was saved
if (hit.hitPoints > 0 && hit.hitPoints - tonHealingAtThisPoint <= 0) {
return Result.Long; //Mark as long save
}
}
}
}
return Result.Not;
}

async load() {
//Run every ToN spawn through parseDamageTaken()
for (const spawn of this.spawns) {
const target = this.combatants?.players[spawn.target];
const filter = target && target?.name ? `target.name = "${target.name}"` : '';
const damageTakenEvents = await fetchWcl<WCLEventsResponse>(
`report/events/damage-taken/${this.owner.report.code}`,
{
start: spawn.summon.timestamp,
end: spawn.summon.timestamp + 8000,
filter,
},
);
spawn.result = this.parseDamageTaken(spawn, damageTakenEvents);
}
}

statistic() {
return (
<LazyLoadStatisticBox
category={STATISTIC_CATEGORY.TALENTS}
position={STATISTIC_ORDER.OPTIONAL(60)}
loader={this.load.bind(this)}
label={
<>
<SpellLink spell={TALENTS_EVOKER.TIME_OF_NEED_TALENT} /> events
</>
}
tooltip={
<div>
<SpellLink spell={TALENTS_EVOKER.TIME_OF_NEED_TALENT} /> will cast one{' '}
<SpellLink spell={TALENTS_EVOKER.VERDANT_EMBRACE_TALENT} /> and several
<SpellLink spell={SPELLS.LIVING_FLAME_HEAL} />s on the player that caused the proc.
Depending on the amount of damage that player took during the 8 seconds
<SpellLink spell={TALENTS_EVOKER.TIME_OF_NEED_TALENT} /> is active the event is
classified as one of the following:
<ul>
<li>
<b>
Saved by <SpellLink spell={TALENTS_EVOKER.VERDANT_EMBRACE_TALENT} />:
</b>{' '}
If the player would've died from damage after the{' '}
<SpellLink spell={TALENTS_EVOKER.VERDANT_EMBRACE_TALENT} /> but before any{' '}
<SpellLink spell={SPELLS.LIVING_FLAME_HEAL} />
s.
</li>
<li>
<b>Saved by all the healing:</b> If the player was saved by a combination of the{' '}
<SpellLink spell={TALENTS_EVOKER.VERDANT_EMBRACE_TALENT} /> and some{' '}
<SpellLink spell={SPELLS.LIVING_FLAME_HEAL} /> healing.
</li>
<li>
<b>Was not in danger of death:</b> If there wasn't any damage event big enough to
kill the player even without the{' '}
<SpellLink spell={TALENTS_EVOKER.TIME_OF_NEED_TALENT} /> healing.
</li>
<li>
<b>Died anyway:</b> If they died anyway regardless of the{' '}
<SpellLink spell={TALENTS_EVOKER.TIME_OF_NEED_TALENT} /> healing.
</li>
<li>
<b>Time of Need fizzled out:</b> The player might have died before{' '}
<SpellLink spell={TALENTS_EVOKER.TIME_OF_NEED_TALENT} /> had time to land the{' '}
<SpellLink spell={TALENTS_EVOKER.VERDANT_EMBRACE_TALENT} />, and thus did no healing
at all.
</li>
</ul>
</div>
}
value={<>Total of {this.spawns.length} events</>}
>
<table className="table table-condensed">
<thead>
<tr>
<th>Event Time</th>
<th>Healing (Overhealing)</th>
<th>Result</th>
</tr>
</thead>
<tbody>
{this.spawns.map((info, index) => (
<tr key={index}>
<td>{this.owner.formatTimestamp(info.summon.timestamp)}</td>
<td>
<div>
{formatNumber(
info.verdantEmbrace?.amount || 0 + (info.verdantEmbrace?.absorbed || 0),
)}{' '}
({formatNumber(info.verdantEmbrace?.overheal || 0)}){' '}
<SpellIcon spell={TALENTS_EVOKER.VERDANT_EMBRACE_TALENT} />
</div>
<div>
{formatNumber(info.livingFlameTotalHeal)} (
{formatNumber(info.livingFlameTotalOverheal)}){' '}
<SpellIcon spell={SPELLS.TIME_OF_NEED_LIVING_FLAME} />
</div>
<div>on {info.livingFlames.length} casts</div>
</td>
<td>{info.result}</td>
</tr>
))}
</tbody>
</table>
</LazyLoadStatisticBox>
);
}
}

export default TimeOfNeed;
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import {
RefreshBuffEvent,
RemoveBuffEvent,
RemoveBuffStackEvent,
SummonEvent,
} from 'parser/core/Events';
import { DUPLICATION_SPELLS, STASIS_CAST_IDS } from '../constants';
import { TIERS } from 'game/TIERS';
@@ -58,6 +59,7 @@ export const STASIS_FOR_RAMP = 'ForRamp';
export const ESSENCE_RUSH = 'EssenceRush';
export const T31_2PC = 'T31LFProc';
export const EB_REVERSION = 'EssenceBurstReversion';
export const TIME_OF_NEED_HEALING = 'TimeOfNeedHealing';

export enum ECHO_TYPE {
NONE,
@@ -76,6 +78,7 @@ const MAX_ESSENCE_BURST_DURATION = 32000; // 15s duration can refresh to 30s wit
const TA_BUFFER_MS = 6000 + CAST_BUFFER_MS; //TA pulses over 6s at 0% haste
const STASIS_BUFFER = 1000;
const T31_LF_AMOUNT = 3;
const TIME_OF_NEED_DURATION = 8000;

/*
This file is for attributing echo applications to hard casts or to temporal anomaly.
@@ -823,6 +826,19 @@ const EVENT_LINKS: EventLink[] = [
);
},
},
{
linkRelation: TIME_OF_NEED_HEALING,
linkingEventId: SPELLS.TIME_OF_NEED_SUMMON.id,
linkingEventType: EventType.Summon,
referencedEventId: [SPELLS.VERDANT_EMBRACE_HEAL.id, SPELLS.TIME_OF_NEED_LIVING_FLAME.id],
referencedEventType: EventType.Heal,
forwardBufferMs: TIME_OF_NEED_DURATION,
anySource: true,
anyTarget: true,
additionalCondition(linkingEvent, referencedEvent) {
return (linkingEvent as SummonEvent).targetID === (referencedEvent as HealEvent).sourceID;
},
},
];

/**
@@ -1062,4 +1078,8 @@ export function isEbFromReversion(
return HasRelatedEvent(event, EB_REVERSION);
}

export function getTimeOfNeedHealing(event: SummonEvent) {
return GetRelatedEvents<HealEvent>(event, TIME_OF_NEED_HEALING) ?? null;
}

export default CastLinkNormalizer;
Original file line number Diff line number Diff line change
@@ -51,6 +51,10 @@ class PotentMana extends SourceOfMagic {
}

async load() {
if (!this.sourceOfMagicWindows.length) {
return;
}

const fetchPromises = {
healingTable: await fetchWcl<WCLHealingTableResponse>(
`report/tables/healing/${this.owner.report.code}`,
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ class SourceOfMagic extends Analyzer {
}

statistic() {
if (!this.active || !this.sourceOfMagicWindows.length) {
if (!this.active) {
return null;
}

1 change: 1 addition & 0 deletions src/analysis/retail/mage/fire/CHANGELOG.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { Sharrq, ToppleTheNun } from 'CONTRIBUTORS';

// prettier-ignore
export default [
change(date(2024, 3, 10), <>Added a check to filter out <SpellLink spell={TALENTS.FIRE_BLAST_TALENT} /> without <SpellLink spell={SPELLS.HEATING_UP} /> if it is within a second of <SpellLink spell={TALENTS.COMBUSTION_TALENT} /> starting.</>, Sharrq),
change(date(2024, 1, 20), <>Fixed an issue with <SpellLink spell={TALENTS.METEOR_TALENT} /> that was counting <SpellLink spell={TALENTS.FIREFALL_TALENT} /> Meteors as mistakes for not landing in <SpellLink spell={TALENTS.COMBUSTION_TALENT} />.</>, Sharrq),
change(date(2024, 1, 17), 'Bump to 10.2.5 support.', ToppleTheNun),
change(date(2024, 1, 3), <>Updated <SpellLink spell={SPELLS.FIREBALL} /> during <SpellLink spell={TALENTS.COMBUSTION_TALENT} /> to disregard casts where the player had a <SpellLink spell={TALENTS.FLAME_ACCELERANT_TALENT} /> proc. Reworded the suggestion to include Double Lust and Flame Accelerant.</>, Sharrq),
Loading

0 comments on commit ad8269b

Please sign in to comment.