From 33be06d56e631a42e073e1fc80c4f51d09cc8d3a Mon Sep 17 00:00:00 2001 From: Vetyst Date: Thu, 3 Oct 2024 23:11:02 +0100 Subject: [PATCH] feature(TTW): Spymaster's Web Trinket (#7089) * feature(TTW): Spymaster's Web Trinket * Update personal contributor entry * Update personal contributor entry * feature(TTW): Spymaster's Web Trinket Review comments --------- Co-authored-by: Danny Janse Co-authored-by: J David Smith Co-authored-by: Putro <29204244+Pewtro@users.noreply.github.com> --- src/CHANGELOG.tsx | 1 + src/CONTRIBUTORS.ts | 10 +- src/common/ITEMS/thewarwithin/trinkets.ts | 5 + src/common/SPELLS/thewarwithin/trinkets.ts | 11 ++ src/parser/core/CASTS_THAT_ARENT_CASTS.ts | 1 + src/parser/core/CombatLogParser.tsx | 2 + .../thewarwithin/trinkets/SpymastersWeb.tsx | 126 ++++++++++++++++++ 7 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 src/parser/retail/modules/items/thewarwithin/trinkets/SpymastersWeb.tsx diff --git a/src/CHANGELOG.tsx b/src/CHANGELOG.tsx index 6ddd32a5774..00bcb92bad2 100644 --- a/src/CHANGELOG.tsx +++ b/src/CHANGELOG.tsx @@ -38,6 +38,7 @@ import SpellLink from 'interface/SpellLink'; // prettier-ignore export default [ + change(date(2024, 10, 4), <>Added simple statistics for ., Vetyst), change(date(2024, 10, 3), 'Fixed rendering of the Header on the character log browser.', Vetyst), change(date(2024, 9, 30), 'Add Classic Cata Firelands raid zone, headshots, and placeholder image', jazminite), change(date(2024, 9, 30), 'Enchant checker now detects Spellthreads', Sref), diff --git a/src/CONTRIBUTORS.ts b/src/CONTRIBUTORS.ts index 201bfd21e82..3d379aa9a14 100644 --- a/src/CONTRIBUTORS.ts +++ b/src/CONTRIBUTORS.ts @@ -564,14 +564,14 @@ export const HawkCorrigan: Contributor = { }; export const Vetyst: Contributor = { nickname: 'Vetyst', - github: 'djanse', - discord: 'vetyst#0001', + github: 'vetyst', + discord: 'vetyst', avatar: avatar('vetyst-avatar.png'), mains: [ { - name: 'Vetyst', - spec: SPECS.BALANCE_DRUID, - link: 'https://worldofwarcraft.com/en-gb/character/tarren-mill/vetyst', + name: 'Vetiest', + spec: SPECS.SHADOW_PRIEST, + link: 'https://worldofwarcraft.com/en-gb/character/ragnaros/vetiest', }, ], }; diff --git a/src/common/ITEMS/thewarwithin/trinkets.ts b/src/common/ITEMS/thewarwithin/trinkets.ts index f4505a9c15f..8f4ad6296bd 100644 --- a/src/common/ITEMS/thewarwithin/trinkets.ts +++ b/src/common/ITEMS/thewarwithin/trinkets.ts @@ -11,6 +11,11 @@ const trinkets = { name: 'Signet of the Priory', icon: 'inv_arathordungeon_signet_color1', }, + SPYMASTERS_WEB: { + id: 220202, + name: "Spymaster's Web", + icon: 'inv_11_0_raid_spymastersweb_purple', + }, } satisfies Record; export default trinkets; diff --git a/src/common/SPELLS/thewarwithin/trinkets.ts b/src/common/SPELLS/thewarwithin/trinkets.ts index 64507d9bd0e..18f60b493e0 100644 --- a/src/common/SPELLS/thewarwithin/trinkets.ts +++ b/src/common/SPELLS/thewarwithin/trinkets.ts @@ -7,6 +7,17 @@ const spells = { name: 'Bolstering Light', icon: 'inv_arathordungeon_signet_color1', }, + // Spymaster's Web + SPYMASTERS_WEB: { + id: 444959, + name: "Spymaster's Web", + icon: 'ability_spy', + }, + SPYMASTERS_REPORT: { + id: 451199, + name: "Spymaster's Report", + icon: 'inv_nerubianspiderling2_black', + }, } satisfies Record; export default spells; diff --git a/src/parser/core/CASTS_THAT_ARENT_CASTS.ts b/src/parser/core/CASTS_THAT_ARENT_CASTS.ts index 7efb14a91e4..ba6586c51d7 100644 --- a/src/parser/core/CASTS_THAT_ARENT_CASTS.ts +++ b/src/parser/core/CASTS_THAT_ARENT_CASTS.ts @@ -100,6 +100,7 @@ const spells: number[] = [ SPELLS.CLOUDBURST_TOTEM_RECALL.id, // Cloudburst reactivation TALENTS_SHAMAN.SPIRITWALKERS_GRACE_TALENT.id, SPELLS.FERAL_LUNGE_NOT_A_CAST.id, // duplicate event of regular Feral Lunge cast + SPELLS.HEALING_RAIN_TOTEMIC.id, //endregion //region warlock diff --git a/src/parser/core/CombatLogParser.tsx b/src/parser/core/CombatLogParser.tsx index 315b05cb4b6..41a63bf0ee5 100644 --- a/src/parser/core/CombatLogParser.tsx +++ b/src/parser/core/CombatLogParser.tsx @@ -101,6 +101,7 @@ import SporeTender from 'parser/retail/modules/items/dragonflight/enchants/Spore import WaftingDevotion from 'parser/retail/modules/items/dragonflight/enchants/WaftingDevotion'; import WaftingWrit from 'parser/retail/modules/items/dragonflight/enchants/WaftingWrit'; import SignetOfThePriory from 'parser/retail/modules/items/thewarwithin/trinkets/SignetOfThePriory'; +import SpymastersWeb from 'parser/retail/modules/items/thewarwithin/trinkets/SpymastersWeb'; import FriendlyCompatNormalizer from './FriendlyCompatNormalizer'; import { AuthorityOfRadiantPower, @@ -218,6 +219,7 @@ class CombatLogParser { stormridersFury: StormridersFury, authorityOfTheDepths: AuthorityOfTheDepths, signetOfThePriory: SignetOfThePriory, + spymastersWeb: SpymastersWeb, // Embellishments darkmoonSigilAscension: DarkmoonSigilAscension, diff --git a/src/parser/retail/modules/items/thewarwithin/trinkets/SpymastersWeb.tsx b/src/parser/retail/modules/items/thewarwithin/trinkets/SpymastersWeb.tsx new file mode 100644 index 00000000000..db376ee1c2a --- /dev/null +++ b/src/parser/retail/modules/items/thewarwithin/trinkets/SpymastersWeb.tsx @@ -0,0 +1,126 @@ +import ITEMS from 'common/ITEMS/thewarwithin/trinkets'; +import Analyzer, { Options, SELECTED_PLAYER } from 'parser/core/Analyzer'; +import Abilities from 'parser/core/modules/Abilities'; +import SPELLS from 'common/SPELLS/thewarwithin/trinkets'; +import SPELL_CATEGORY from 'parser/core/SPELL_CATEGORY'; +import Statistic from 'parser/ui/Statistic'; +import STATISTIC_ORDER from 'parser/ui/STATISTIC_ORDER'; +import Events, { ApplyBuffEvent, ApplyBuffStackEvent, CastEvent } from 'parser/core/Events'; +import BoringItemValueText from 'parser/ui/BoringItemValueText'; +import { formatDuration, formatNumber } from 'common/format'; +import STATISTIC_CATEGORY from 'parser/ui/STATISTIC_CATEGORY'; +import { calculatePrimaryStat } from 'parser/core/stats'; + +type SpymastersWebCast = { + timestamp: number; + stacks: number; +}; + +/** + * Based on the stats provided on wowhead. + * + * https://www.wowhead.com/item=220202/spymasters-web + */ +const SPYMASTERS_WEB_BASE_ILVL = 571; +const SPYMASTERS_WEB_BASE_GAIN = 515; + +export default class SpymastersWeb extends Analyzer.withDependencies({ + abilities: Abilities, +}) { + protected currentReportStackCount: number = 0; + protected primaryStatBonus: number = 0; + protected SpymastersWebCasts: SpymastersWebCast[] = []; + + constructor(options: Options) { + super(options); + + // Ensure the combatant has the trinket. + this.active = this.selectedCombatant.hasTrinket(ITEMS.SPYMASTERS_WEB.id); + if (!this.active) { + return; + } + + // Add the ability to the spellbook of the current combatant. + this.deps.abilities.add({ + spell: SPELLS.SPYMASTERS_WEB.id, + category: SPELL_CATEGORY.ITEMS, + cooldown: 20, + }); + + this.addEventListener( + Events.applybuff.by(SELECTED_PLAYER).spell(SPELLS.SPYMASTERS_REPORT), + this._onBuffApply, + ); + this.addEventListener( + Events.applybuffstack.by(SELECTED_PLAYER).spell(SPELLS.SPYMASTERS_REPORT), + this._onBuffApply, + ); + this.addEventListener( + Events.cast.by(SELECTED_PLAYER).spell(SPELLS.SPYMASTERS_WEB), + this._onCast, + ); + + // Calculate the primary stat bonus we'd receive per stack when the on use effect of the tricket is cast. + this.primaryStatBonus = calculatePrimaryStat( + SPYMASTERS_WEB_BASE_ILVL, + SPYMASTERS_WEB_BASE_GAIN, + this.selectedCombatant.getTrinket(ITEMS.SPYMASTERS_WEB.id)?.itemLevel, + ); + } + + _onBuffApply(event: ApplyBuffStackEvent | ApplyBuffEvent) { + // Increase the current stack report stack count. + this.currentReportStackCount += 1; + } + + _onCast(event: CastEvent) { + this.SpymastersWebCasts.push({ + timestamp: event.timestamp, + stacks: this.currentReportStackCount, + }); + + // Reset the stack count to zero. + this.currentReportStackCount = 0; + } + + statistic() { + const castRows = this.SpymastersWebCasts.map((cast: SpymastersWebCast, index: number) => { + const castTime = cast.timestamp - this.owner.fight.start_time; + const intellectGained = cast.stacks * this.primaryStatBonus; + + return ( + + {formatDuration(castTime)} + {cast.stacks} + {formatNumber(intellectGained)} + + ); + }); + + return ( + + + + + + + + + + {castRows} +
CastStacks on UseIntellect Gained
+ + } + > + + {this.SpymastersWebCasts.length} Casts + +
+ ); + } +}