From d28b1d6874b740ad3a0cfb874f234a162fc144cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Hillerstr=C3=B6m?= Date: Tue, 31 Dec 2024 20:31:48 +0100 Subject: [PATCH 01/15] [UI] Port APL drag and drop improvements from cata repo --- package.json | 6 + .../individual_sim_ui/apl_values.ts | 2 +- ui/core/components/list_picker.tsx | 277 ++++++++++++++---- ui/scss/core/components/_list_picker.scss | 24 +- .../_apl_rotation_picker.scss | 1 + ui/scss/shared/_global.scss | 2 +- 6 files changed, 250 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index 1e224d0e04..c3c58c14a1 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,12 @@ "name": "sod", "version": "0.1.0", "private": true, + "engines": { + "node": ">=20" + }, + "volta": { + "node": "20.11.1" + }, "scripts": { "build": "bazel build //...", "test": "bazel test //...", diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index e6cdc94462..5e8c058e77 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -447,7 +447,7 @@ export function valueListFieldConfig(field: string): AplHelpers.APLPickerBuilder index: number, config: ListItemPickerConfig, APLValue | undefined>, ) => new APLValuePicker(parent, player, config), - allowedActions: ['create', 'delete'], + allowedActions: ['copy', 'create', 'delete', 'move'], actions: { create: { useIcon: true, diff --git a/ui/core/components/list_picker.tsx b/ui/core/components/list_picker.tsx index dcf223d831..7a8eeaf857 100644 --- a/ui/core/components/list_picker.tsx +++ b/ui/core/components/list_picker.tsx @@ -159,6 +159,24 @@ export class ListPicker extends Input { + button.classList.add('hover'); + }, + { signal: this.signal }, + ); + + button.addEventListener( + 'mouseleave', + () => { + button.classList.remove('hover'); + }, + { signal: this.signal }, + ); + } + private addNewPicker() { const index = this.itemPickerPairs.length; const itemContainer = document.createElement('div'); @@ -174,6 +192,12 @@ export class ListPicker extends Input extends Input = { elem: itemContainer, picker: itemPicker, idx: index }; + if (this.actionEnabled('delete')) { + if (!this.config.minimumItems || index + 1 > this.config.minimumItems) { + hasActions = true; + const deleteButton = ListPicker.makeActionElem('list-picker-item-delete', 'fa-times'); + deleteButton.classList.add('link-danger'); + popover.appendChild(deleteButton); + + const deleteButtonTooltip = tippy(deleteButton, { + allowHTML: false, + content: `Delete ${this.config.itemLabel}`, + }); + + deleteButton.addEventListener( + 'click', + () => { + const newList = this.config.getValue(this.modObject); + newList.splice(index, 1); + this.config.setValue(TypedEvent.nextEventID(), this.modObject, newList); + deleteButtonTooltip.hide(); + }, + { signal: this.signal }, + ); + this.addOnDisposeCallback(() => deleteButtonTooltip?.destroy()); + this.addHoverListeners(deleteButton); + } + } + + if (this.actionEnabled('copy')) { + hasActions = true; + const copyButton = ListPicker.makeActionElem('list-picker-item-copy', 'fa-copy'); + popover.appendChild(copyButton); + const copyButtonTooltip = tippy(copyButton, { + allowHTML: false, + content: `Copy to New ${this.config.itemLabel}`, + }); + + copyButton.addEventListener( + 'click', + () => { + const newList = this.config.getValue(this.modObject).slice(); + newList.splice(index, 0, this.config.copyItem(newList[index])); + this.config.setValue(TypedEvent.nextEventID(), this.modObject, newList); + copyButtonTooltip.hide(); + }, + { signal: this.signal }, + ); + this.addOnDisposeCallback(() => copyButtonTooltip?.destroy()); + this.addHoverListeners(copyButton); + } + if (this.actionEnabled('move')) { + hasActions = true; + itemContainer.classList.add('draggable'); + if (this.config.itemLabel) { + itemContainer.classList.add(this.config.itemLabel.toLowerCase().replace(' ', '-')); + } + const moveButton = ListPicker.makeActionElem('list-picker-item-move', 'fa-arrows-up-down'); - itemHeader.appendChild(moveButton); + popover.appendChild(moveButton); const moveButtonTooltip = tippy(moveButton, { allowHTML: false, @@ -220,11 +300,32 @@ export class ListPicker extends Input { + moveButton.setAttribute('draggable', 'true'); + itemContainer.setAttribute('draggable', 'true'); + }, + { signal: this.signal }, + ); + + moveButton.addEventListener( + 'mouseup', + () => { + moveButton.removeAttribute('draggable'); + itemContainer.removeAttribute('draggable'); + }, + { signal: this.signal }, + ); + moveButton.addEventListener( 'dragstart', event => { if (event.target == moveButton) { + const popoverRect = popover.getBoundingClientRect(); + event.dataTransfer!.setDragImage(itemContainer, 0, popoverRect.height / 2); event.dataTransfer!.dropEffect = 'move'; event.dataTransfer!.effectAllowed = 'move'; itemContainer.classList.add('dragfrom'); @@ -237,14 +338,42 @@ export class ListPicker extends Input curDragData && this.config.itemLabel === 'Action' && curDragData.listPicker !== this; + const targetIsSelf = () => curDragData && curDragData.listPicker === this && curDragData.item.idx === index; + const targetIsChild = () => curDragData && curDragData.item.elem.contains(itemContainer); + + const invalidDropTarget = (checkSelf = true) => { + // Only allow dropping on the same type of list, Value -> Value, Action -> Action + if (!curDragData || curDragData.listPicker.config.itemLabel !== this.config.itemLabel) { + return true; + } + + // Only allow dropping Actions within the same list + if (droppingActionOnOtherList()) { + return true; + } + + // Just skip trying to drop on itself? + if (checkSelf && targetIsSelf()) { + return true; + } + + // Can't drop within itself + if (checkSelf && targetIsChild()) { + return true; + } + + return false; + }; + let dragEnterCounter = 0; itemContainer.addEventListener( 'dragenter', event => { - if (!curDragData || curDragData.listPicker != this) { + if (invalidDropTarget()) { return; } - event.preventDefault(); + event.stopPropagation(); dragEnterCounter++; itemContainer.classList.add('dragto'); }, @@ -254,7 +383,7 @@ export class ListPicker extends Input { - if (!curDragData || curDragData.listPicker != this) { + if (invalidDropTarget()) { return; } event.preventDefault(); @@ -269,30 +398,80 @@ export class ListPicker extends Input { - if (!curDragData || curDragData.listPicker != this) { + if (invalidDropTarget()) { + if (droppingActionOnOtherList() || targetIsSelf()) { + event.dataTransfer!.dropEffect = 'none'; + } + return; } + event.dataTransfer!.dropEffect = 'move'; + event.stopPropagation(); event.preventDefault(); }, { signal: this.signal }, ); + const cleanupAfterDrag = () => { + if (!curDragData) { + return; + } + moveButton.removeAttribute('draggable'); + itemContainer.removeAttribute('draggable'); + curDragData.item.elem.removeAttribute('draggable'); + [...document.querySelectorAll('.dragfrom,.dragto')].forEach(elem => { + elem.classList.remove('dragfrom'); + elem.classList.remove('dragto'); + }); + }; + + itemContainer.addEventListener( + 'dragend', + event => { + if (invalidDropTarget(false)) { + return; + } + event.stopPropagation(); + cleanupAfterDrag(); + curDragData = null; + }, + { signal: this.signal }, + ); + itemContainer.addEventListener( 'drop', event => { - if (!curDragData || curDragData.listPicker != this) { + if (!curDragData || invalidDropTarget()) { + if (targetIsSelf()) { + event.stopPropagation(); + cleanupAfterDrag(); + } return; } - event.preventDefault(); - dragEnterCounter = 0; - itemContainer.classList.remove('dragto'); - curDragData.item.elem.classList.remove('dragfrom'); + event.stopPropagation(); + cleanupAfterDrag(); const srcIdx = curDragData.item.idx; - const dstIdx = index; + let dstIdx = index; + + const targetRect = itemContainer.getBoundingClientRect(); + if (event.clientY > targetRect.top + targetRect.height / 2) { + dstIdx++; + } + const newList = this.config.getValue(this.modObject); - const arrElem = newList[srcIdx]; - newList.splice(srcIdx, 1); + let arrElem; + + if (curDragData.listPicker !== this) { + const oldList = curDragData.listPicker.config.getValue(curDragData.listPicker.modObject); + arrElem = oldList[srcIdx]; + oldList.splice(srcIdx, 1); + curDragData.listPicker.config.setValue(TypedEvent.nextEventID(), curDragData.listPicker.modObject, oldList); + } else { + arrElem = newList[srcIdx]; + newList.splice(srcIdx, 1); + } + newList.splice(dstIdx, 0, arrElem); this.config.setValue(TypedEvent.nextEventID(), this.modObject, newList); @@ -302,61 +481,41 @@ export class ListPicker extends Input { - const newList = this.config.getValue(this.modObject).slice(); - newList.splice(index, 0, this.config.copyItem(newList[index])); - this.config.setValue(TypedEvent.nextEventID(), this.modObject, newList); - copyButtonTooltip.hide(); + popover.showPopover(); + const actionsButtonRect = actionsButton.getBoundingClientRect(); + const popoverRect = popover.getBoundingClientRect(); + const diff = (popoverRect.height - actionsButtonRect.height) / 2; + popover.style.top = actionsButtonRect.top - diff + 'px'; + popover.style.left = actionsButtonRect.right - popoverRect.width + 10 + 'px'; + popover.classList.add('hover'); + }, + { signal: this.signal }, + ); + popover.addEventListener( + 'mouseleave', + () => { + popover.classList.remove('hover'); + popover.hidePopover(); }, { signal: this.signal }, ); - this.addOnDisposeCallback(() => copyButtonTooltip?.destroy()); - } - - if (this.actionEnabled('delete')) { - if (!this.config.minimumItems || index + 1 > this.config.minimumItems) { - const deleteButton = ListPicker.makeActionElem('list-picker-item-delete', 'fa-times'); - deleteButton.classList.add('link-danger'); - itemHeader.appendChild(deleteButton); - - const deleteButtonTooltip = tippy(deleteButton, { - allowHTML: false, - content: `Delete ${this.config.itemLabel}`, - }); - - deleteButton.addEventListener( - 'click', - () => { - const newList = this.config.getValue(this.modObject); - newList.splice(index, 1); - this.config.setValue(TypedEvent.nextEventID(), this.modObject, newList); - deleteButtonTooltip.hide(); - }, - { signal: this.signal }, - ); - this.addOnDisposeCallback(() => deleteButtonTooltip?.destroy()); - } } this.itemPickerPairs.push(item); } - static makeActionElem(cssClass: string, iconCssClass: string): HTMLAnchorElement { + static makeActionElem(cssClass: string, iconCssClass: string): HTMLButtonElement { return ( - - - - ) as HTMLAnchorElement; + + ) as HTMLButtonElement; } static getItemHeaderElem(itemPicker: Input): HTMLElement { diff --git a/ui/scss/core/components/_list_picker.scss b/ui/scss/core/components/_list_picker.scss index 496ccdb028..c24c71abff 100644 --- a/ui/scss/core/components/_list_picker.scss +++ b/ui/scss/core/components/_list_picker.scss @@ -4,6 +4,16 @@ flex-direction: column; align-items: center; + &.dragfrom { + background-color: color-mix(in srgb, var(--bs-body-bg) 80%, transparent); + filter: opacity(0.5); + cursor: move; + } + + &.draggable:has(> .list-picker-item-header .list-picker-item-popover.hover) { + background-color: color-mix(in srgb, var(--bs-primary) 5%, transparent); + } + &:not(:last-child) { margin-bottom: calc(2 * var(--spacer-3)); } @@ -32,7 +42,6 @@ padding: 0; border: 0; margin: 0; - flex: 0; } } @@ -61,7 +70,20 @@ .list-picker-item-action { margin-left: var(--spacer-2); + + &.list-picker-item-move { + cursor: move; + } } + + .list-picker-item-popover:popover-open { + inset: unset; + position: relative; + background-color: color-mix(in srgb, var(--bs-body-bg) 80%, transparent); + border: 1px solid black; + border-radius: 5px; + padding: 5px 10px; + } } .target-input-picker-root { diff --git a/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss b/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss index e2b56ce450..a9c9914d9d 100644 --- a/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss +++ b/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss @@ -26,6 +26,7 @@ .list-picker-item-header { align-items: flex-start; + justify-content: flex-end; } & > .list-picker-item { diff --git a/ui/scss/shared/_global.scss b/ui/scss/shared/_global.scss index 9f51283c2e..09f7832afd 100644 --- a/ui/scss/shared/_global.scss +++ b/ui/scss/shared/_global.scss @@ -123,7 +123,7 @@ kbd { } .dragto:not(.dragfrom) { - filter: brightness(0.75); + filter: brightness(0.5); } .interactive { From f4470530cb1c63f6fe1fd5402de3a19e85ec1899 Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Thu, 2 Jan 2025 19:08:57 -0700 Subject: [PATCH 02/15] fixed typo --- sim/rogue/evasion.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim/rogue/evasion.go b/sim/rogue/evasion.go index d46e38e035..bd540a533f 100644 --- a/sim/rogue/evasion.go +++ b/sim/rogue/evasion.go @@ -33,7 +33,7 @@ func (rogue *Rogue) RegisterEvasionSpell() { DefaultCast: core.Cast{}, CD: core.Cooldown{ Timer: rogue.NewTimer(), - Duration: []time.Duration{time.Minute * 5, time.Minute*5 - time.Second*45, time.Second*5 - time.Second*90}[rogue.Talents.Elusiveness], + Duration: []time.Duration{time.Minute * 5, time.Minute*5 - time.Second*45, time.Minute*5 - time.Second*90}[rogue.Talents.Elusiveness], }, IgnoreHaste: true, }, From 5ecaf4d7944cec1f94d9b90f59b350fdaf0c9f4d Mon Sep 17 00:00:00 2001 From: sanguinerarogue Date: Thu, 2 Jan 2025 19:27:18 -0700 Subject: [PATCH 03/15] evasion talent change --- sim/rogue/evasion.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim/rogue/evasion.go b/sim/rogue/evasion.go index bd540a533f..5e523c9a80 100644 --- a/sim/rogue/evasion.go +++ b/sim/rogue/evasion.go @@ -33,7 +33,7 @@ func (rogue *Rogue) RegisterEvasionSpell() { DefaultCast: core.Cast{}, CD: core.Cooldown{ Timer: rogue.NewTimer(), - Duration: []time.Duration{time.Minute * 5, time.Minute*5 - time.Second*45, time.Minute*5 - time.Second*90}[rogue.Talents.Elusiveness], + Duration: []time.Duration{time.Minute * 5, time.Minute*5 - time.Second*45, time.Minute*5 - time.Second*90}[rogue.Talents.Endurance], }, IgnoreHaste: true, }, From f283731cb2493d09435fc060613e5f2538e870cf Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 11 Jan 2025 21:05:12 -0500 Subject: [PATCH 04/15] add more thorns effects, display total thorns damage, fix timeworn EP calculation --- proto/common.proto | 11 +- sim/common/sod/enchant_effects.go | 30 +++-- sim/common/sod/item_effects/phase_6.go | 138 +++++++++++++++++++++- sim/common/sod/item_effects/phase_7.go | 48 ++++++++ sim/core/buffs.go | 2 + sim/core/character.go | 10 +- sim/core/stats/stats.go | 2 + ui/core/components/character_stats.tsx | 104 ++++++++-------- ui/core/components/stat_weights_action.ts | 3 +- ui/core/constants/other.ts | 6 +- ui/core/individual_sim_ui.tsx | 4 +- ui/core/player.ts | 6 +- ui/core/proto_utils/names.ts | 5 +- ui/elemental_shaman/sim.ts | 6 +- ui/enhancement_shaman/sim.ts | 26 ++-- ui/feral_druid/sim.ts | 4 +- ui/retribution_paladin/sim.ts | 2 +- ui/tank_warrior/sim.ts | 5 +- 18 files changed, 314 insertions(+), 98 deletions(-) create mode 100644 sim/common/sod/item_effects/phase_7.go diff --git a/proto/common.proto b/proto/common.proto index fe09e49ae0..d5eb57e3c6 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -127,15 +127,15 @@ enum Stat { // between the UI and backend. // // It's also OK to include things here which aren't in the PseudoStats struct. -// NextIndex: 32 +// NextIndex: 33 enum PseudoStat { PseudoStatMainHandDps = 0; PseudoStatOffHandDps = 1; PseudoStatRangedDps = 2; PseudoStatBlockValueMultiplier = 3; - PseudoStatDodge = 4; - PseudoStatParry = 5; - BonusPhysicalDamage = 31; + PseudoStatDodge = 4 [deprecated = true]; + PseudoStatParry = 5 [deprecated = true]; + PseudoStatBonusPhysicalDamage = 31; // Melee Weapon Skill PseudoStatUnarmedSkill = 6; @@ -170,7 +170,8 @@ enum PseudoStat { PseudoStatBlockValuePerStrength = 29; // Special Pseudostats - TimewornBonus = 30; + PseudoStatTimewornBonus = 30; + PseudoStatThornsDamage = 32; } message UnitStats { diff --git a/sim/common/sod/enchant_effects.go b/sim/common/sod/enchant_effects.go index 7bf520de57..9290b7d1cc 100644 --- a/sim/common/sod/enchant_effects.go +++ b/sim/common/sod/enchant_effects.go @@ -88,9 +88,21 @@ func init() { }) // Sharpened Chitin Armor Kit + // Permanently cause an item worn on the chest, legs, hands or feet to cause 20 Nature damage to the attacker when struck in combat. + // Only usable on items level 45 and above. core.NewEnchantEffect(7649, func(agent core.Agent) { character := agent.GetCharacter() - actionID := core.ActionID{SpellID: 1213833} + actionID := core.ActionID{ItemID: 233803} + + damage := 20.0 + numEnchants := 0 + for _, item := range character.Equipment { + if item.Enchant.EffectID == 7649 { + numEnchants++ + } + } + + character.PseudoStats.ThornsDamage += damage * float64(numEnchants) procSpell := character.RegisterSpell(core.SpellConfig{ ActionID: actionID, @@ -102,22 +114,20 @@ func init() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealDamage(sim, target, 20, spell.OutcomeMagicHit) + spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHit) }, }) - character.GetOrRegisterAura(core.Aura{ - Label: "Thorns +20", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: "Thorns +20", OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { - procSpell.Cast(sim, spell.Unit) + for i := 0; i < numEnchants; i++ { + procSpell.Cast(sim, spell.Unit) + } } }, - }) + })) }) // Obsidian Scope diff --git a/sim/common/sod/item_effects/phase_6.go b/sim/common/sod/item_effects/phase_6.go index 43e699d3f5..3a4c4307a2 100644 --- a/sim/common/sod/item_effects/phase_6.go +++ b/sim/common/sod/item_effects/phase_6.go @@ -12,9 +12,13 @@ import ( ) const ( - LeggingsOfImmersion = 233505 - RingOfSwarmingThought = 233507 - RobesOfTheBattleguard = 233575 + RazorspikeBattleplate = 233492 + LeggingsOfImmersion = 233505 + RingOfSwarmingThought = 233507 + RobesOfTheBattleguard = 233575 + RazorspikeShoulderplates = 233793 + RazorspikeHeadcage = 233795 + LodestoneOfRetaliation = 233992 // Obsidian Weapons ObsidianChampion = 233490 @@ -167,6 +171,41 @@ func init() { ObsidianEdgedAura(ObsidianStormhammer, agent) }) + /////////////////////////////////////////////////////////////////////////// + // Trinkets + /////////////////////////////////////////////////////////////////////////// + + // https://www.wowhead.com/classic/item=233992/lodestone-of-retaliation + // When struck in combat inflicts 80 Nature damage to the attacker. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(LodestoneOfRetaliation, func(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += 80 + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: LodestoneOfRetaliation}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 2, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, 80, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: "Damage Shield Dmg +80 (Lodestone of Retaliation)", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) + }) + /////////////////////////////////////////////////////////////////////////// // Rings /////////////////////////////////////////////////////////////////////////// @@ -291,6 +330,99 @@ func init() { }) }) + // https://www.wowhead.com/classic/item=233492/razorspike-battleplate + // When struck in combat inflicts 100 Nature damage to the attacker. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(RazorspikeBattleplate, func(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += 100 + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: RazorspikeBattleplate}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 2, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, 100, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: "Damage Shield Dmg +100 (Razorspike Battleplate)", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) + }) + + // https://www.wowhead.com/classic/item=233795/razorspike-headcage + // When struck in combat inflicts 100 Nature damage to the attacker. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(RazorspikeHeadcage, func(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += 100 + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: RazorspikeHeadcage}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 2, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, 100, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: "Damage Shield Dmg +100 (Razorspike Headcage)", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) + }) + + // https://www.wowhead.com/classic/item=233793/razorspike-shoulderplates + // When struck in combat inflicts 80 Nature damage to the attacker. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(RazorspikeShoulderplates, func(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += 80 + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: RazorspikeShoulderplates}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 2, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, 80, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: "Damage Shield Dmg +80 (Razorspike Shoulderplates)", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) + }) + // https://www.wowhead.com/classic/item=233575/robes-of-the-battleguard // Your damaging non-periodic spells increase your spell damage by 20 for 15 sec. // If the target is player controlled, gain 120 spell penetration for 15 sec instead. diff --git a/sim/common/sod/item_effects/phase_7.go b/sim/common/sod/item_effects/phase_7.go new file mode 100644 index 0000000000..c6844e2953 --- /dev/null +++ b/sim/common/sod/item_effects/phase_7.go @@ -0,0 +1,48 @@ +package item_effects + +import "github.com/wowsims/sod/sim/core" + +const ( + BulwarkOfIre = 235868 +) + +func init() { + core.AddEffectsToTest = false + + /////////////////////////////////////////////////////////////////////////// + // Other + /////////////////////////////////////////////////////////////////////////// + + // https://www.wowhead.com/classic/item=235868/bulwark-of-ire + // Deal 100 Shadow damage to melee attackers. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(BulwarkOfIre, func(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += 100 + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: BulwarkOfIre}, + SpellSchool: core.SpellSchoolShadow, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 2, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, 100, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: "Splintered Shieldd", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) + }) + + core.AddEffectsToTest = true +} diff --git a/sim/core/buffs.go b/sim/core/buffs.go index c70d57e862..3730cbf10e 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -1179,6 +1179,8 @@ func ThornsAura(character *Character, points int32) *Aura { actionID := ActionID{SpellID: spellID} damage := float64(baseDamage) * (1 + 0.25*float64(points)) + character.PseudoStats.ThornsDamage += damage + procSpell := character.RegisterSpell(SpellConfig{ ActionID: actionID, SpellSchool: SpellSchoolNature, diff --git a/sim/core/character.go b/sim/core/character.go index 7104d13bbf..7eb8828e60 100644 --- a/sim/core/character.go +++ b/sim/core/character.go @@ -189,8 +189,8 @@ func NewCharacter(party *Party, partyIndex int, player *proto.Player) Character character.PseudoStats.BowsSkill += ps[proto.PseudoStat_PseudoStatBowsSkill] character.PseudoStats.CrossbowsSkill += ps[proto.PseudoStat_PseudoStatCrossbowsSkill] character.PseudoStats.GunsSkill += ps[proto.PseudoStat_PseudoStatGunsSkill] - character.PseudoStats.BonusPhysicalDamage += ps[proto.PseudoStat_BonusPhysicalDamage] - character.PseudoStats.TimewornBonus += int32(ps[proto.PseudoStat_TimewornBonus]) + character.PseudoStats.BonusPhysicalDamage += ps[proto.PseudoStat_PseudoStatBonusPhysicalDamage] + character.PseudoStats.TimewornBonus += int32(ps[proto.PseudoStat_PseudoStatTimewornBonus]) } } @@ -271,7 +271,6 @@ func (character *Character) applyEquipment() { character.PseudoStats.BonusPhysicalDamage += item.BonusPhysicalDamage } - } func (character *Character) addUniversalStatDependencies() { @@ -612,6 +611,7 @@ func (character *Character) GetPseudoStatsProto() []float64 { proto.PseudoStat_PseudoStatMainHandDps: character.AutoAttacks.MH().DPS(), proto.PseudoStat_PseudoStatOffHandDps: character.AutoAttacks.OH().DPS(), proto.PseudoStat_PseudoStatRangedDps: character.AutoAttacks.Ranged().DPS(), + proto.PseudoStat_PseudoStatBonusPhysicalDamage: float64(character.PseudoStats.BonusPhysicalDamage), proto.PseudoStat_PseudoStatBlockValueMultiplier: character.PseudoStats.BlockValueMultiplier, proto.PseudoStat_PseudoStatAxesSkill: float64(character.PseudoStats.AxesSkill), proto.PseudoStat_PseudoStatSwordsSkill: float64(character.PseudoStats.SwordsSkill), @@ -640,8 +640,8 @@ func (character *Character) GetPseudoStatsProto() []float64 { proto.PseudoStat_PseudoStatRangedSpeedMultiplier: float64(character.PseudoStats.RangedSpeedMultiplier), proto.PseudoStat_PseudoStatBlockValuePerStrength: float64(character.PseudoStats.BlockValuePerStrength), - proto.PseudoStat_TimewornBonus: float64(character.PseudoStats.TimewornBonus), - proto.PseudoStat_BonusPhysicalDamage: float64(character.PseudoStats.BonusPhysicalDamage), + proto.PseudoStat_PseudoStatTimewornBonus: float64(character.PseudoStats.TimewornBonus), + proto.PseudoStat_PseudoStatThornsDamage: character.PseudoStats.ThornsDamage, } } diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index 2ff1a7cdef..8019d84b65 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -471,6 +471,8 @@ type PseudoStats struct { // Tracks the number of Timeworn items equipped for Bronze Signet bonuses TimewornBonus int32 + // Tracks the cumulative amount of Thorns damage from various effects purely for UI display + ThornsDamage float64 /////////////////////////////////////////////////// // Effects that apply when this unit is the target. diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index 798abbdaa9..b64affa537 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx'; import tippy from 'tippy.js'; import { ref } from 'tsx-vanilla'; @@ -10,82 +11,87 @@ import { Component } from './component.js'; import { NumberPicker } from './number_picker'; export type StatMods = { talents?: Stats; buffs?: Stats }; +export type DisplayStat = { + stat: UnitStat, + notEditable?: boolean +} -const statGroups = new Map>([ +const statGroups = new Map>([ [ 'Primary', [ - UnitStat.fromStat(Stat.StatHealth), - UnitStat.fromStat(Stat.StatMana), + {stat: UnitStat.fromStat(Stat.StatHealth)}, + {stat: UnitStat.fromStat(Stat.StatMana)}, ], ], [ 'Attributes', [ - UnitStat.fromStat(Stat.StatStrength), - UnitStat.fromStat(Stat.StatAgility), - UnitStat.fromStat(Stat.StatStamina), - UnitStat.fromStat(Stat.StatIntellect), - UnitStat.fromStat(Stat.StatSpirit), + {stat: UnitStat.fromStat(Stat.StatStrength)}, + {stat: UnitStat.fromStat(Stat.StatAgility)}, + {stat: UnitStat.fromStat(Stat.StatStamina)}, + {stat: UnitStat.fromStat(Stat.StatIntellect)}, + {stat: UnitStat.fromStat(Stat.StatSpirit)}, ] ], [ 'Physical', [ - UnitStat.fromStat(Stat.StatAttackPower), - UnitStat.fromStat(Stat.StatFeralAttackPower), - UnitStat.fromStat(Stat.StatRangedAttackPower), - UnitStat.fromStat(Stat.StatMeleeHit), - UnitStat.fromStat(Stat.StatExpertise), - UnitStat.fromStat(Stat.StatMeleeCrit), - UnitStat.fromStat(Stat.StatMeleeHaste), - UnitStat.fromPseudoStat(PseudoStat.BonusPhysicalDamage), + {stat: UnitStat.fromStat(Stat.StatAttackPower)}, + {stat: UnitStat.fromStat(Stat.StatFeralAttackPower)}, + {stat: UnitStat.fromStat(Stat.StatRangedAttackPower)}, + {stat: UnitStat.fromStat(Stat.StatMeleeHit)}, + {stat: UnitStat.fromStat(Stat.StatExpertise)}, + {stat: UnitStat.fromStat(Stat.StatMeleeCrit)}, + {stat: UnitStat.fromStat(Stat.StatMeleeHaste)}, + {stat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatBonusPhysicalDamage)}, ] ], [ 'Spell', [ - UnitStat.fromStat(Stat.StatSpellPower), - UnitStat.fromStat(Stat.StatSpellDamage), - UnitStat.fromStat(Stat.StatArcanePower), - UnitStat.fromStat(Stat.StatFirePower), - UnitStat.fromStat(Stat.StatFrostPower), - UnitStat.fromStat(Stat.StatHolyPower), - UnitStat.fromStat(Stat.StatNaturePower), - UnitStat.fromStat(Stat.StatShadowPower), - UnitStat.fromStat(Stat.StatSpellHit), - UnitStat.fromStat(Stat.StatSpellCrit), - UnitStat.fromStat(Stat.StatSpellHaste), - UnitStat.fromStat(Stat.StatSpellPenetration), - UnitStat.fromStat(Stat.StatMP5), + {stat: UnitStat.fromStat(Stat.StatSpellPower)}, + {stat: UnitStat.fromStat(Stat.StatSpellDamage)}, + {stat: UnitStat.fromStat(Stat.StatArcanePower)}, + {stat: UnitStat.fromStat(Stat.StatFirePower)}, + {stat: UnitStat.fromStat(Stat.StatFrostPower)}, + {stat: UnitStat.fromStat(Stat.StatHolyPower)}, + {stat: UnitStat.fromStat(Stat.StatNaturePower)}, + {stat: UnitStat.fromStat(Stat.StatShadowPower)}, + {stat: UnitStat.fromStat(Stat.StatSpellHit)}, + {stat: UnitStat.fromStat(Stat.StatSpellCrit)}, + {stat: UnitStat.fromStat(Stat.StatSpellHaste)}, + {stat: UnitStat.fromStat(Stat.StatSpellPenetration)}, + {stat: UnitStat.fromStat(Stat.StatMP5)}, ] ], [ 'Defense', [ - UnitStat.fromStat(Stat.StatArmor), - UnitStat.fromStat(Stat.StatBonusArmor), - UnitStat.fromStat(Stat.StatDefense), - UnitStat.fromStat(Stat.StatDodge), - UnitStat.fromStat(Stat.StatParry), - UnitStat.fromStat(Stat.StatBlock), - UnitStat.fromStat(Stat.StatBlockValue), + {stat: UnitStat.fromStat(Stat.StatArmor)}, + {stat: UnitStat.fromStat(Stat.StatBonusArmor)}, + {stat: UnitStat.fromStat(Stat.StatDefense)}, + {stat: UnitStat.fromStat(Stat.StatDodge)}, + {stat: UnitStat.fromStat(Stat.StatParry)}, + {stat: UnitStat.fromStat(Stat.StatBlock)}, + {stat: UnitStat.fromStat(Stat.StatBlockValue)}, ] ], [ 'Resistance', [ - UnitStat.fromStat(Stat.StatArcaneResistance), - UnitStat.fromStat(Stat.StatFireResistance), - UnitStat.fromStat(Stat.StatFrostResistance), - UnitStat.fromStat(Stat.StatNatureResistance), - UnitStat.fromStat(Stat.StatShadowResistance), + {stat: UnitStat.fromStat(Stat.StatArcaneResistance)}, + {stat: UnitStat.fromStat(Stat.StatFireResistance)}, + {stat: UnitStat.fromStat(Stat.StatFrostResistance)}, + {stat: UnitStat.fromStat(Stat.StatNatureResistance)}, + {stat: UnitStat.fromStat(Stat.StatShadowResistance)}, ] ], [ 'Misc', [ - UnitStat.fromPseudoStat(PseudoStat.TimewornBonus), + {stat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatThornsDamage), notEditable: true}, + {stat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatTimewornBonus), notEditable: true}, ] ], ]) @@ -121,12 +127,13 @@ export class CharacterStats extends Component { this.valueElems = []; statGroups.forEach((groupedStats, _) => { - const filteredStats = groupedStats.filter(stat => displayStats.find(displayStat => displayStat.equals(stat))); + const filteredStats = groupedStats.filter(stat => displayStats.find(displayStat => displayStat.equals(stat.stat))); if (!filteredStats.length) return; const body = ; - filteredStats.forEach(stat => { + filteredStats.forEach(displayStat => { + const { stat } = displayStat this.stats.push(stat); const statName = stat.getName(player.getClass()); @@ -134,7 +141,7 @@ export class CharacterStats extends Component { const row = ( {statName} - {this.bonusStatsLink(stat)} + {this.bonusStatsLink(displayStat)} ); body.appendChild(row); @@ -487,8 +494,9 @@ export class CharacterStats extends Component { return debuffStats; } - private bonusStatsLink(stat: UnitStat): HTMLElement { - const statName = stat.getName(this.player.getClass()); + private bonusStatsLink(displayStat: DisplayStat): HTMLElement { + const { stat, notEditable } = displayStat; + const statName = displayStat.stat.getName(this.player.getClass()); const linkRef = ref(); const iconRef = ref(); @@ -496,7 +504,7 @@ export class CharacterStats extends Component { diff --git a/ui/core/components/stat_weights_action.ts b/ui/core/components/stat_weights_action.ts index 41d9162640..3f28a4746f 100644 --- a/ui/core/components/stat_weights_action.ts +++ b/ui/core/components/stat_weights_action.ts @@ -753,7 +753,8 @@ class EpWeightsMenu extends BaseModal { PseudoStat.PseudoStatMainHandDps, PseudoStat.PseudoStatOffHandDps, PseudoStat.PseudoStatRangedDps, - PseudoStat.BonusPhysicalDamage + PseudoStat.PseudoStatBonusPhysicalDamage, + PseudoStat.PseudoStatTimewornBonus, ].includes(stat.getPseudoStat()); } }); diff --git a/ui/core/constants/other.ts b/ui/core/constants/other.ts index 2c0343f992..e5058b29f3 100644 --- a/ui/core/constants/other.ts +++ b/ui/core/constants/other.ts @@ -40,7 +40,7 @@ export const GLOBAL_DISPLAY_STATS = [ ]; export const GLOBAL_DISPLAY_PSEUDO_STATS = [ - PseudoStat.TimewornBonus, + PseudoStat.PseudoStatTimewornBonus, ]; export const GLOBAL_EP_STATS = [ @@ -49,6 +49,10 @@ export const GLOBAL_EP_STATS = [ Stat.StatNatureResistance, ]; +export const GLOBAL_EP_PSEUDOSTATS = [ + PseudoStat.PseudoStatTimewornBonus, +]; + export enum SortDirection { ASC, DESC, diff --git a/ui/core/individual_sim_ui.tsx b/ui/core/individual_sim_ui.tsx index 708207a80e..217566c731 100644 --- a/ui/core/individual_sim_ui.tsx +++ b/ui/core/individual_sim_ui.tsx @@ -15,7 +15,7 @@ import * as InputHelpers from './components/input_helpers'; import { addRaidSimAction, RaidSimResultsManager } from './components/raid_sim_action'; import { SavedDataConfig } from './components/saved_data_manager'; import { addStatWeightsAction } from './components/stat_weights_action'; -import { GLOBAL_DISPLAY_PSEUDO_STATS, GLOBAL_DISPLAY_STATS, GLOBAL_EP_STATS, LEVEL_THRESHOLDS } from './constants/other'; +import { GLOBAL_DISPLAY_PSEUDO_STATS, GLOBAL_DISPLAY_STATS, GLOBAL_EP_PSEUDOSTATS, GLOBAL_EP_STATS, LEVEL_THRESHOLDS } from './constants/other'; import * as Tooltips from './constants/tooltips'; import { simLaunchStatuses } from './launched_sims'; import { Player, PlayerConfig, registerSpecConfig as registerPlayerConfig } from './player'; @@ -330,7 +330,7 @@ export abstract class IndividualSimUI extends SimUI { private addSidebarComponents() { this.raidSimResultsManager = addRaidSimAction(this); - addStatWeightsAction(this, this.individualConfig.epStats.concat(GLOBAL_EP_STATS), this.individualConfig.epPseudoStats, this.individualConfig.epReferenceStat); + addStatWeightsAction(this, this.individualConfig.epStats.concat(GLOBAL_EP_STATS), GLOBAL_EP_PSEUDOSTATS.concat(this.individualConfig.epPseudoStats ?? []), this.individualConfig.epReferenceStat); const displayStats: UnitStat[] = []; diff --git a/ui/core/player.ts b/ui/core/player.ts index 3e44c3eead..43779fb882 100644 --- a/ui/core/player.ts +++ b/ui/core/player.ts @@ -1077,7 +1077,7 @@ export class Player { } // Add pseudo stats that should be included in item EP. - itemStats = itemStats.addPseudoStat(PseudoStat.BonusPhysicalDamage, item.bonusPhysicalDamage); + itemStats = itemStats.addPseudoStat(PseudoStat.PseudoStatBonusPhysicalDamage, item.bonusPhysicalDamage); // For random suffix items, use the suffix option with the highest EP for the purposes of ranking items in the picker. let maxSuffixEP = 0; @@ -1094,6 +1094,10 @@ export class Player { ep -= 0.01; } + if (item.timeworn) { + ep += this.epWeights.getPseudoStat(PseudoStat.PseudoStatTimewornBonus) + } + this.itemEPCache[slot].set(item.id, ep); return ep; } diff --git a/ui/core/proto_utils/names.ts b/ui/core/proto_utils/names.ts index 1f5e5fd31b..c5e35110ae 100644 --- a/ui/core/proto_utils/names.ts +++ b/ui/core/proto_utils/names.ts @@ -207,8 +207,9 @@ export const pseudoStatNames: Map = new Map([ [PseudoStat.PseudoStatOffHandDps, 'Off Hand DPS'], [PseudoStat.PseudoStatRangedDps, 'Ranged DPS'], [PseudoStat.PseudoStatBlockValueMultiplier, 'Block Value Multiplier'], - [PseudoStat.TimewornBonus, 'Timeworn Pieces'], - [PseudoStat.BonusPhysicalDamage, 'Bonus Weapon Damage'], + [PseudoStat.PseudoStatTimewornBonus, 'Timeworn Pieces'], + [PseudoStat.PseudoStatBonusPhysicalDamage, 'Bonus Weapon Damage'], + [PseudoStat.PseudoStatThornsDamage, 'Total Thorns Damage'], ]); export function getClassStatName(stat: Stat, playerClass: Class): string { diff --git a/ui/elemental_shaman/sim.ts b/ui/elemental_shaman/sim.ts index bedf2402d9..247068438d 100644 --- a/ui/elemental_shaman/sim.ts +++ b/ui/elemental_shaman/sim.ts @@ -30,9 +30,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { Stat.StatSpellHaste, Stat.StatMP5, ], - epPseudoStats: [ - PseudoStat.TimewornBonus, - ], + epPseudoStats: [], // Reference stat against which to calculate EP. I think all classes use either spell power or attack power. epReferenceStat: Stat.StatSpellPower, // Which stats to display in the Character Stats section, at the bottom of the left-hand sidebar. @@ -72,6 +70,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecElementalShaman, { [Stat.StatStrength]: 0.01, [Stat.StatAttackPower]: 0.01, [Stat.StatFireResistance]: 0.5, + }, { + [PseudoStat.PseudoStatTimewornBonus]: 35.74, }), // Default consumes settings. consumes: Presets.DefaultConsumes, diff --git a/ui/enhancement_shaman/sim.ts b/ui/enhancement_shaman/sim.ts index 3e96971f2f..52bd59d147 100644 --- a/ui/enhancement_shaman/sim.ts +++ b/ui/enhancement_shaman/sim.ts @@ -75,26 +75,28 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { // Default EP weights for sorting gear in the gear picker. epWeights: Stats.fromMap( { - [Stat.StatIntellect]: 0.02, - [Stat.StatAgility]: 1.12, - [Stat.StatStrength]: 2.29, - [Stat.StatSpellPower]: 1.15, - [Stat.StatSpellDamage]: 1.15, - [Stat.StatFirePower]: 0.63, - [Stat.StatNaturePower]: 0.48, + [Stat.StatIntellect]: 2.01, + [Stat.StatAgility]: 1.88, + [Stat.StatStrength]: 2.66, + [Stat.StatSpellPower]: 0.68, + [Stat.StatSpellDamage]: 0.68, + [Stat.StatFirePower]: 0.31, + [Stat.StatNaturePower]: 0.37, [Stat.StatSpellHit]: 0.03, //default EP assumes cap - [Stat.StatSpellCrit]: 1.94, + [Stat.StatSpellCrit]: 7.54, [Stat.StatSpellHaste]: 2.97, [Stat.StatMP5]: 0.01, [Stat.StatAttackPower]: 1.0, - [Stat.StatMeleeHit]: 9.62, - [Stat.StatMeleeCrit]: 14.8, + [Stat.StatMeleeHit]: 22.53, + [Stat.StatMeleeCrit]: 25.21, + [Stat.StatExpertise]: 50.82, [Stat.StatFireResistance]: 0.5, }, { - [PseudoStat.PseudoStatMainHandDps]: 8.15, - [PseudoStat.PseudoStatOffHandDps]: 5.81, + [PseudoStat.PseudoStatMainHandDps]: 4.86, + [PseudoStat.PseudoStatOffHandDps]: 5.55, [PseudoStat.PseudoStatMeleeSpeedMultiplier]: 5.81, + [PseudoStat.PseudoStatTimewornBonus]: 9.00, }, ), // Default consumes settings. diff --git a/ui/feral_druid/sim.ts b/ui/feral_druid/sim.ts index 1323f299de..5b611c27f6 100644 --- a/ui/feral_druid/sim.ts +++ b/ui/feral_druid/sim.ts @@ -39,7 +39,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralDruid, { Stat.StatMP5, ], epPseudoStats: [ - PseudoStat.BonusPhysicalDamage, + PseudoStat.PseudoStatBonusPhysicalDamage, ], // Reference stat against which to calculate EP. I think all classes use either spell power or attack power. epReferenceStat: Stat.StatAttackPower, @@ -62,7 +62,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralDruid, { Stat.StatMP5, ], displayPseudoStats: [ - PseudoStat.BonusPhysicalDamage, + PseudoStat.PseudoStatBonusPhysicalDamage, ], defaults: { diff --git a/ui/retribution_paladin/sim.ts b/ui/retribution_paladin/sim.ts index d56c533c02..25bd1f3ac3 100644 --- a/ui/retribution_paladin/sim.ts +++ b/ui/retribution_paladin/sim.ts @@ -57,7 +57,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecRetributionPaladin, { epPseudoStats: [ PseudoStat.PseudoStatMainHandDps, PseudoStat.PseudoStatMeleeSpeedMultiplier, - PseudoStat.TimewornBonus + PseudoStat.PseudoStatTimewornBonus, ], // Reference stat against which to calculate EP. I think all classes use either spell power or attack power. epReferenceStat: Stat.StatAttackPower, diff --git a/ui/tank_warrior/sim.ts b/ui/tank_warrior/sim.ts index 35a670d52f..f5b90ce0bf 100644 --- a/ui/tank_warrior/sim.ts +++ b/ui/tank_warrior/sim.ts @@ -64,8 +64,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankWarrior, { // Resistances Stat.StatShadowResistance, ], - displayPseudoStats: [], - + displayPseudoStats: [ + PseudoStat.PseudoStatThornsDamage, + ], defaults: { // Default equipped gear. gear: Presets.DefaultGear.gear, From edcf066e0806e03e5c298627721c3d4417959d50 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 11 Jan 2025 21:07:16 -0500 Subject: [PATCH 05/15] add thorns display pseudo to all tanks --- ui/protection_paladin/sim.ts | 4 +++- ui/tank_rogue/sim.ts | 4 +++- ui/tank_warlock/sim.ts | 6 ++++-- ui/tank_warrior/sim.ts | 1 + ui/warden_shaman/sim.ts | 4 +++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ui/protection_paladin/sim.ts b/ui/protection_paladin/sim.ts index 5b290bc34f..df814d5f82 100644 --- a/ui/protection_paladin/sim.ts +++ b/ui/protection_paladin/sim.ts @@ -103,7 +103,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionPaladin, { Stat.StatShadowResistance, Stat.StatArcaneResistance, ], - displayPseudoStats: [], + displayPseudoStats: [ + PseudoStat.PseudoStatThornsDamage, + ], defaults: { // Default equipped gear. diff --git a/ui/tank_rogue/sim.ts b/ui/tank_rogue/sim.ts index c5bf49f82f..7c22e08296 100644 --- a/ui/tank_rogue/sim.ts +++ b/ui/tank_rogue/sim.ts @@ -63,7 +63,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankRogue, { // Resistances Stat.StatShadowResistance, ], - displayPseudoStats: [], + displayPseudoStats: [ + PseudoStat.PseudoStatThornsDamage, + ], defaults: { // Default equipped gear. diff --git a/ui/tank_warlock/sim.ts b/ui/tank_warlock/sim.ts index d5ee5eb5fb..925a053d4e 100644 --- a/ui/tank_warlock/sim.ts +++ b/ui/tank_warlock/sim.ts @@ -5,7 +5,7 @@ import * as OtherInputs from '../core/components/other_inputs.js'; import { Phase } from '../core/constants/other.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; import { Player } from '../core/player.js'; -import { Class, Faction, ItemSlot, PartyBuffs, Race, Spec, Stat } from '../core/proto/common.js'; +import { Class, Faction, ItemSlot, PartyBuffs, PseudoStat, Race, Spec, Stat } from '../core/proto/common.js'; import { WarlockRune } from '../core/proto/warlock.js'; import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon } from '../core/proto_utils/utils.js'; @@ -82,7 +82,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankWarlock, { // Resistances Stat.StatShadowResistance, ], - displayPseudoStats: [], + displayPseudoStats: [ + PseudoStat.PseudoStatThornsDamage, + ], defaults: { // Default equipped gear. diff --git a/ui/tank_warrior/sim.ts b/ui/tank_warrior/sim.ts index f5b90ce0bf..e2cee35fee 100644 --- a/ui/tank_warrior/sim.ts +++ b/ui/tank_warrior/sim.ts @@ -67,6 +67,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankWarrior, { displayPseudoStats: [ PseudoStat.PseudoStatThornsDamage, ], + defaults: { // Default equipped gear. gear: Presets.DefaultGear.gear, diff --git a/ui/warden_shaman/sim.ts b/ui/warden_shaman/sim.ts index 8328459f0d..2ff7dddcee 100644 --- a/ui/warden_shaman/sim.ts +++ b/ui/warden_shaman/sim.ts @@ -83,7 +83,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWardenShaman, { // Resistances Stat.StatShadowResistance, ], - displayPseudoStats: [], + displayPseudoStats: [ + PseudoStat.PseudoStatThornsDamage, + ], defaults: { race: Race.RaceTroll, From 3a750eec34174ac1cce41050b863ad9f0b394c1e Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 11 Jan 2025 21:18:13 -0500 Subject: [PATCH 06/15] add naglering effect --- sim/common/sod/item_effects/phase_7.go | 2 +- sim/common/vanilla/item_effects.go | 31 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/sim/common/sod/item_effects/phase_7.go b/sim/common/sod/item_effects/phase_7.go index c6844e2953..2cb7ed9038 100644 --- a/sim/common/sod/item_effects/phase_7.go +++ b/sim/common/sod/item_effects/phase_7.go @@ -35,7 +35,7 @@ func init() { }) core.MakePermanent(character.GetOrRegisterAura(core.Aura{ - Label: "Splintered Shieldd", + Label: "Splintered Shield", OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { procSpell.Cast(sim, spell.Unit) diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index 3b47f51a02..ccc3bc7f09 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -34,6 +34,7 @@ const ( Firebreather = 10797 VilerendSlicer = 11603 HookfangShanker = 11635 + Naglering = 11669 LinkensSwordOfMastery = 11902 SearingNeedle = 12531 PipsSkinner = 12709 @@ -2898,6 +2899,36 @@ func init() { BlazefuryTriggerAura(character, 7712, core.SpellSchoolFire, 2) }) + // https://www.wowhead.com/classic/item=11669/naglering + // When struck in combat inflicts 3 Arcane damage to the attacker. + core.NewItemEffect(Naglering, func(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += 3 + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: Naglering}, + SpellSchool: core.SpellSchoolArcane, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, 3, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: "Thorns (Naglering)", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) + }) + // https://www.wowhead.com/classic/item=1168/skullflame-shield // Equip: When struck in combat has a 3% chance of stealing 35 life from target enemy. (Proc chance: 3%) // Equip: When struck in combat has a 1% chance of dealing 75 to 125 Fire damage to all targets around you. (Proc chance: 1%) From b56c84ca2b3412e9999544b7c04f86f58de352f7 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 11 Jan 2025 21:38:37 -0500 Subject: [PATCH 07/15] add naglering, drillborer disk, razorbramble items --- sim/common/sod/item_effects/phase_6.go | 155 +++++++++---------------- sim/common/vanilla/item_effects.go | 65 +++++++---- 2 files changed, 96 insertions(+), 124 deletions(-) diff --git a/sim/common/sod/item_effects/phase_6.go b/sim/common/sod/item_effects/phase_6.go index 3a4c4307a2..cf5251214f 100644 --- a/sim/common/sod/item_effects/phase_6.go +++ b/sim/common/sod/item_effects/phase_6.go @@ -1,6 +1,7 @@ package item_effects import ( + "fmt" "math" "time" @@ -18,6 +19,9 @@ const ( RobesOfTheBattleguard = 233575 RazorspikeShoulderplates = 233793 RazorspikeHeadcage = 233795 + RazorbrambleShoulderpads = 233804 + RazorbrambleCowl = 233808 + RazorbrambleLeathers = 233813 LodestoneOfRetaliation = 233992 // Obsidian Weapons @@ -179,31 +183,7 @@ func init() { // When struck in combat inflicts 80 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(LodestoneOfRetaliation, func(agent core.Agent) { - character := agent.GetCharacter() - character.PseudoStats.ThornsDamage += 80 - - procSpell := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{ItemID: LodestoneOfRetaliation}, - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - ThreatMultiplier: 2, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealDamage(sim, target, 80, spell.OutcomeMagicHit) - }, - }) - - core.MakePermanent(character.GetOrRegisterAura(core.Aura{ - Label: "Damage Shield Dmg +80 (Lodestone of Retaliation)", - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { - procSpell.Cast(sim, spell.Unit) - } - }, - })) + thornsNatureDamageEffect(agent, LodestoneOfRetaliation, "Lodestone of Retaliation", 80) }) /////////////////////////////////////////////////////////////////////////// @@ -330,97 +310,46 @@ func init() { }) }) - // https://www.wowhead.com/classic/item=233492/razorspike-battleplate + // https://www.wowhead.com/classic/item=233808/razorbramble-cowl // When struck in combat inflicts 100 Nature damage to the attacker. // Causes twice as much threat as damage dealt. - core.NewItemEffect(RazorspikeBattleplate, func(agent core.Agent) { - character := agent.GetCharacter() - character.PseudoStats.ThornsDamage += 100 - - procSpell := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{ItemID: RazorspikeBattleplate}, - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + core.NewItemEffect(RazorbrambleCowl, func(agent core.Agent) { + thornsNatureDamageEffect(agent, RazorbrambleCowl, "Razorbramble Cowl", 100) + }) - DamageMultiplier: 1, - ThreatMultiplier: 2, + // https://www.wowhead.com/classic/item=233813/razorbramble-leathers + // When struck in combat inflicts 100 Nature damage to the attacker. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(RazorbrambleLeathers, func(agent core.Agent) { + thornsNatureDamageEffect(agent, RazorbrambleLeathers, "Razorbramble Leathers", 100) + }) - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealDamage(sim, target, 100, spell.OutcomeMagicHit) - }, - }) + // https://www.wowhead.com/classic/item=233804/razorbramble-shoulderpads + // When struck in combat inflicts 80 Nature damage to the attacker. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(RazorbrambleShoulderpads, func(agent core.Agent) { + thornsNatureDamageEffect(agent, RazorbrambleShoulderpads, "Razorbramble Shoulderpads", 80) + }) - core.MakePermanent(character.GetOrRegisterAura(core.Aura{ - Label: "Damage Shield Dmg +100 (Razorspike Battleplate)", - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { - procSpell.Cast(sim, spell.Unit) - } - }, - })) + // https://www.wowhead.com/classic/item=233492/razorspike-battleplate + // When struck in combat inflicts 100 Nature damage to the attacker. + // Causes twice as much threat as damage dealt. + core.NewItemEffect(RazorspikeBattleplate, func(agent core.Agent) { + thornsNatureDamageEffect(agent, RazorspikeBattleplate, "Razorspike Battleplate", 100) }) // https://www.wowhead.com/classic/item=233795/razorspike-headcage // When struck in combat inflicts 100 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorspikeHeadcage, func(agent core.Agent) { - character := agent.GetCharacter() - character.PseudoStats.ThornsDamage += 100 - - procSpell := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{ItemID: RazorspikeHeadcage}, - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - ThreatMultiplier: 2, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealDamage(sim, target, 100, spell.OutcomeMagicHit) - }, - }) - - core.MakePermanent(character.GetOrRegisterAura(core.Aura{ - Label: "Damage Shield Dmg +100 (Razorspike Headcage)", - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { - procSpell.Cast(sim, spell.Unit) - } - }, - })) + thornsNatureDamageEffect(agent, RazorspikeHeadcage, "Razorspike Headcage", 100) }) // https://www.wowhead.com/classic/item=233793/razorspike-shoulderplates // When struck in combat inflicts 80 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorspikeShoulderplates, func(agent core.Agent) { - character := agent.GetCharacter() - character.PseudoStats.ThornsDamage += 80 - - procSpell := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{ItemID: RazorspikeShoulderplates}, - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - ThreatMultiplier: 2, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealDamage(sim, target, 80, spell.OutcomeMagicHit) - }, - }) - - core.MakePermanent(character.GetOrRegisterAura(core.Aura{ - Label: "Damage Shield Dmg +80 (Razorspike Shoulderplates)", - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { - procSpell.Cast(sim, spell.Unit) - } - }, - })) + thornsNatureDamageEffect(agent, RazorspikeShoulderplates, "Razorspike Shoulderplates", 80) }) // https://www.wowhead.com/classic/item=233575/robes-of-the-battleguard @@ -668,3 +597,31 @@ func TimewornStrikeAura(agent core.Agent) { }, }) } + +func thornsNatureDamageEffect(agent core.Agent, itemID int32, itemName string, damage float64) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += damage + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: itemID}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 2, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: fmt.Sprintf("Damage Shield Dmg +%f (%s)", damage, itemName), + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) +} diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index ccc3bc7f09..d778a0f1cd 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -80,6 +80,7 @@ const ( FiendishMachete = 228056 // 18310 RefinedArcaniteChampion = 228125 TalismanOfEphemeralPower = 228255 // 18820 + DrillborerDisk = 228266 // 17066 GutgoreRipper = 228267 // 17071 Shadowstrike = 228272 // 17074 Thunderstrike = 228273 // 17223 @@ -106,6 +107,7 @@ const ( SeepingWillow = 228666 // 12969 DraconicInfusedEmblem = 228678 // 22268 QuelSerrar = 228679 // 18348 + DrillborerDiskMolten = 228702 HandOfJustice = 228722 // 11815 Felstriker = 228757 // 12590 GutgoreRipperMolten = 229372 @@ -2899,34 +2901,19 @@ func init() { BlazefuryTriggerAura(character, 7712, core.SpellSchoolFire, 2) }) + // https://www.wowhead.com/classic/item=228266/drillborer-disk + // When struck in combat inflicts 3 Arcane damage to the attacker. + core.NewItemEffect(DrillborerDisk, func(agent core.Agent) { + thornsArcaneDamageEffect(agent, DrillborerDisk, "Drillborer Disk", 3) + }) + core.NewItemEffect(DrillborerDiskMolten, func(agent core.Agent) { + thornsArcaneDamageEffect(agent, DrillborerDiskMolten, "Drillborer Disk (Molten)", 3) + }) + // https://www.wowhead.com/classic/item=11669/naglering // When struck in combat inflicts 3 Arcane damage to the attacker. core.NewItemEffect(Naglering, func(agent core.Agent) { - character := agent.GetCharacter() - character.PseudoStats.ThornsDamage += 3 - - procSpell := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{ItemID: Naglering}, - SpellSchool: core.SpellSchoolArcane, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealDamage(sim, target, 3, spell.OutcomeMagicHit) - }, - }) - - core.MakePermanent(character.GetOrRegisterAura(core.Aura{ - Label: "Thorns (Naglering)", - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { - procSpell.Cast(sim, spell.Unit) - } - }, - })) + thornsArcaneDamageEffect(agent, Naglering, "Naglering", 3) }) // https://www.wowhead.com/classic/item=1168/skullflame-shield @@ -3148,6 +3135,34 @@ func dreadbladeOfTheDestructorEffect(character *core.Character) *core.Spell { }) } +func thornsArcaneDamageEffect(agent core.Agent, itemID int32, itemName string, damage float64) { + character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += damage + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: itemID}, + SpellSchool: core.SpellSchoolArcane, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHit) + }, + }) + + core.MakePermanent(character.GetOrRegisterAura(core.Aura{ + Label: fmt.Sprintf("Thorns (%s)", itemName), + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { + procSpell.Cast(sim, spell.Unit) + } + }, + })) +} + func eskhandarsRightClawAura(character *core.Character) *core.Aura { return character.GetOrRegisterAura(core.Aura{ Label: "Eskhandar's Rage", From 409153e045d87c79a649f5210308d404ab94c90e Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 11 Jan 2025 21:42:30 -0500 Subject: [PATCH 08/15] add thorns damage from essence of the pure flame --- sim/common/sod/item_effects/phase_6.go | 14 +++++++------- sim/common/vanilla/item_effects.go | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/sim/common/sod/item_effects/phase_6.go b/sim/common/sod/item_effects/phase_6.go index cf5251214f..8f834e79c4 100644 --- a/sim/common/sod/item_effects/phase_6.go +++ b/sim/common/sod/item_effects/phase_6.go @@ -180,7 +180,7 @@ func init() { /////////////////////////////////////////////////////////////////////////// // https://www.wowhead.com/classic/item=233992/lodestone-of-retaliation - // When struck in combat inflicts 80 Nature damage to the attacker. + // Equip: When struck in combat inflicts 80 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(LodestoneOfRetaliation, func(agent core.Agent) { thornsNatureDamageEffect(agent, LodestoneOfRetaliation, "Lodestone of Retaliation", 80) @@ -311,42 +311,42 @@ func init() { }) // https://www.wowhead.com/classic/item=233808/razorbramble-cowl - // When struck in combat inflicts 100 Nature damage to the attacker. + // Equip: When struck in combat inflicts 100 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorbrambleCowl, func(agent core.Agent) { thornsNatureDamageEffect(agent, RazorbrambleCowl, "Razorbramble Cowl", 100) }) // https://www.wowhead.com/classic/item=233813/razorbramble-leathers - // When struck in combat inflicts 100 Nature damage to the attacker. + // Equip: When struck in combat inflicts 100 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorbrambleLeathers, func(agent core.Agent) { thornsNatureDamageEffect(agent, RazorbrambleLeathers, "Razorbramble Leathers", 100) }) // https://www.wowhead.com/classic/item=233804/razorbramble-shoulderpads - // When struck in combat inflicts 80 Nature damage to the attacker. + // Equip: When struck in combat inflicts 80 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorbrambleShoulderpads, func(agent core.Agent) { thornsNatureDamageEffect(agent, RazorbrambleShoulderpads, "Razorbramble Shoulderpads", 80) }) // https://www.wowhead.com/classic/item=233492/razorspike-battleplate - // When struck in combat inflicts 100 Nature damage to the attacker. + // Equip: When struck in combat inflicts 100 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorspikeBattleplate, func(agent core.Agent) { thornsNatureDamageEffect(agent, RazorspikeBattleplate, "Razorspike Battleplate", 100) }) // https://www.wowhead.com/classic/item=233795/razorspike-headcage - // When struck in combat inflicts 100 Nature damage to the attacker. + // Equip: When struck in combat inflicts 100 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorspikeHeadcage, func(agent core.Agent) { thornsNatureDamageEffect(agent, RazorspikeHeadcage, "Razorspike Headcage", 100) }) // https://www.wowhead.com/classic/item=233793/razorspike-shoulderplates - // When struck in combat inflicts 80 Nature damage to the attacker. + // Equip: When struck in combat inflicts 80 Nature damage to the attacker. // Causes twice as much threat as damage dealt. core.NewItemEffect(RazorspikeShoulderplates, func(agent core.Agent) { thornsNatureDamageEffect(agent, RazorspikeShoulderplates, "Razorspike Shoulderplates", 80) diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index d778a0f1cd..1230d32bb7 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -2347,13 +2347,14 @@ func init() { // Equip: When struck in combat inflicts 50 Fire damage to the attacker. core.NewItemEffect(EssenceOfThePureFlame, func(agent core.Agent) { character := agent.GetCharacter() + character.PseudoStats.ThornsDamage += 50 procSpell := character.GetOrRegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 461694}, SpellSchool: core.SpellSchoolFire, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + Flags: core.SpellFlagBinary | core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, DamageMultiplier: 1, ThreatMultiplier: 1, @@ -2902,7 +2903,7 @@ func init() { }) // https://www.wowhead.com/classic/item=228266/drillborer-disk - // When struck in combat inflicts 3 Arcane damage to the attacker. + // Equip: When struck in combat inflicts 3 Arcane damage to the attacker. core.NewItemEffect(DrillborerDisk, func(agent core.Agent) { thornsArcaneDamageEffect(agent, DrillborerDisk, "Drillborer Disk", 3) }) @@ -2911,7 +2912,7 @@ func init() { }) // https://www.wowhead.com/classic/item=11669/naglering - // When struck in combat inflicts 3 Arcane damage to the attacker. + // Equip: When struck in combat inflicts 3 Arcane damage to the attacker. core.NewItemEffect(Naglering, func(agent core.Agent) { thornsArcaneDamageEffect(agent, Naglering, "Naglering", 3) }) From 43088313c906c4d5c8b07b036efbb71ee5716298 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 11 Jan 2025 21:46:38 -0500 Subject: [PATCH 09/15] add razor gauntlets --- sim/common/vanilla/item_effects.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index 1230d32bb7..7379aaed59 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -50,6 +50,7 @@ const ( ThrashBlade = 17705 SatyrsLash = 17752 MarkOfTheChosen = 17774 + RazorGauntlets = 18326 Nightfall = 19169 EbonHand = 19170 RuneOfTheDawn = 19812 @@ -2917,6 +2918,12 @@ func init() { thornsArcaneDamageEffect(agent, Naglering, "Naglering", 3) }) + // https://www.wowhead.com/classic/item=18326/razor-gauntlets + // Equip: When struck in combat inflicts 3 Arcane damage to the attacker. + core.NewItemEffect(RazorGauntlets, func(agent core.Agent) { + thornsArcaneDamageEffect(agent, RazorGauntlets, "Razor Gauntlets", 3) + }) + // https://www.wowhead.com/classic/item=1168/skullflame-shield // Equip: When struck in combat has a 3% chance of stealing 35 life from target enemy. (Proc chance: 3%) // Equip: When struck in combat has a 1% chance of dealing 75 to 125 Fire damage to all targets around you. (Proc chance: 1%) From 92ac0e0827517fece10953154295480aa8d7ab37 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 11 Jan 2025 23:05:13 -0500 Subject: [PATCH 10/15] add force reactive disk items --- sim/common/sod/item_effects/phase_6.go | 80 ++++++++++++++++++++++++++ sim/common/vanilla/item_effects.go | 35 +++++++++++ 2 files changed, 115 insertions(+) diff --git a/sim/common/sod/item_effects/phase_6.go b/sim/common/sod/item_effects/phase_6.go index 8f834e79c4..d46bb7d01e 100644 --- a/sim/common/sod/item_effects/phase_6.go +++ b/sim/common/sod/item_effects/phase_6.go @@ -22,6 +22,7 @@ const ( RazorbrambleShoulderpads = 233804 RazorbrambleCowl = 233808 RazorbrambleLeathers = 233813 + TunedForceReactiveDisk = 233988 LodestoneOfRetaliation = 233992 // Obsidian Weapons @@ -384,6 +385,85 @@ func init() { }) }) + // https://www.wowhead.com/classic/item=233988/tuned-force-reactive-disk + // Equip: When the shield blocks it releases an electrical charge that damages all nearby enemies. (1s cooldown) + // Use: Charge up the energy within the shield for 3 sec to deal 450 to 750 Nature damage to all nearby enemies. After use, the shield needs 10 sec to recharge. (2 Min Cooldown) + core.NewItemEffect(TunedForceReactiveDisk, func(agent core.Agent) { + character := agent.GetCharacter() + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: TunedForceReactiveDisk}, + SpellSchool: core.SpellSchoolNature, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + for _, aoeTarget := range sim.Encounter.TargetUnits { + spell.CalcAndDealDamage(sim, aoeTarget, 35, spell.OutcomeMagicHitAndCrit) + } + }, + }) + + procTriggerAura := core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{ + Name: "Force Reactive Disk", + Callback: core.CallbackOnSpellHitTaken, + ProcMask: core.ProcMaskMelee, + Outcome: core.OutcomeBlock, + ICD: time.Second, + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + procSpell.Cast(sim, spell.Unit) + }, + }) + + spell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 1213967}, + SpellSchool: core.SpellSchoolNature, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + Cast: core.CastConfig{ + CD: core.Cooldown{ + Timer: character.NewTimer(), + Duration: time.Minute * 2, + }, + }, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + sim.AddPendingAction(&core.PendingAction{ + NextActionAt: sim.CurrentTime + time.Second*3, + OnAction: func(sim *core.Simulation) { + for _, aoeTarget := range sim.Encounter.TargetUnits { + spell.CalcAndDealDamage(sim, aoeTarget, sim.Roll(450, 750), spell.OutcomeMagicHitAndCrit) + } + + spell.CD.Use(sim) + + procTriggerAura.Deactivate(sim) + sim.AddPendingAction(&core.PendingAction{ + NextActionAt: sim.CurrentTime + time.Second*10, + OnAction: func(sim *core.Simulation) { + procTriggerAura.Activate(sim) + }, + }) + }, + }) + }, + }) + + character.AddMajorCooldown(core.MajorCooldown{ + Type: core.CooldownTypeDPS, + Spell: spell, + }) + }) + core.AddEffectsToTest = true } diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index 7379aaed59..6199978bf1 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -50,6 +50,7 @@ const ( ThrashBlade = 17705 SatyrsLash = 17752 MarkOfTheChosen = 17774 + ForceReactiveDisk = 18168 RazorGauntlets = 18326 Nightfall = 19169 EbonHand = 19170 @@ -2912,6 +2913,40 @@ func init() { thornsArcaneDamageEffect(agent, DrillborerDiskMolten, "Drillborer Disk (Molten)", 3) }) + // https://www.wowhead.com/classic/item=18168/force-reactive-disk + // Equip: When the shield blocks it releases an electrical charge that damages all nearby enemies. (1s cooldown) + core.NewItemEffect(ForceReactiveDisk, func(agent core.Agent) { + character := agent.GetCharacter() + + procSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{ItemID: ForceReactiveDisk}, + SpellSchool: core.SpellSchoolNature, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + for _, aoeTarget := range sim.Encounter.TargetUnits { + spell.CalcAndDealDamage(sim, aoeTarget, 25, spell.OutcomeMagicHitAndCrit) + } + }, + }) + + core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{ + Name: "Force Reactive Disk", + Callback: core.CallbackOnSpellHitTaken, + ProcMask: core.ProcMaskMelee, + Outcome: core.OutcomeBlock, + ICD: time.Second, + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + procSpell.Cast(sim, spell.Unit) + }, + }) + }) + // https://www.wowhead.com/classic/item=11669/naglering // Equip: When struck in combat inflicts 3 Arcane damage to the attacker. core.NewItemEffect(Naglering, func(agent core.Agent) { From b3a08cb64280f4b28397338b9429fc31ebd3cfee Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 12 Jan 2025 00:47:10 -0500 Subject: [PATCH 11/15] add in sanctified bonuses --- proto/common.proto | 8 ++- proto/ui.proto | 3 +- sim/common/sod/item_effects/phase_7.go | 88 +++++++++++++++++++++++++- sim/common/vanilla/item_effects.go | 5 ++ sim/core/character.go | 9 ++- sim/core/database.go | 4 +- sim/core/stats/stats.go | 3 + tools/database/wowhead_tooltips.go | 7 ++ ui/core/components/character_stats.tsx | 1 + ui/core/constants/other.ts | 2 + ui/core/player.ts | 4 ++ ui/core/proto_utils/names.ts | 1 + 12 files changed, 127 insertions(+), 8 deletions(-) diff --git a/proto/common.proto b/proto/common.proto index d5eb57e3c6..5f1f8e9912 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -127,7 +127,7 @@ enum Stat { // between the UI and backend. // // It's also OK to include things here which aren't in the PseudoStats struct. -// NextIndex: 33 +// NextIndex: 34 enum PseudoStat { PseudoStatMainHandDps = 0; PseudoStatOffHandDps = 1; @@ -136,6 +136,7 @@ enum PseudoStat { PseudoStatDodge = 4 [deprecated = true]; PseudoStatParry = 5 [deprecated = true]; PseudoStatBonusPhysicalDamage = 31; + PseudoStatThornsDamage = 32; // Melee Weapon Skill PseudoStatUnarmedSkill = 6; @@ -171,7 +172,7 @@ enum PseudoStat { // Special Pseudostats PseudoStatTimewornBonus = 30; - PseudoStatThornsDamage = 32; + PseudoStatSanctifiedBonus = 33; } message UnitStats { @@ -942,7 +943,7 @@ message SimDatabase { } // Contains only the Item info needed by the sim. -// NextIndex: 21 +// NextIndex: 22 message SimItem { int32 id = 1; int32 requires_level = 16; @@ -967,6 +968,7 @@ message SimItem { repeated double weapon_skills = 15; bool timeworn = 19; + bool sanctified = 21; } // Extra enum for describing which items are eligible for an enchant, when diff --git a/proto/ui.proto b/proto/ui.proto index da16891b81..14ac07dd6e 100644 --- a/proto/ui.proto +++ b/proto/ui.proto @@ -44,7 +44,7 @@ message UIFaction { // Contains all information about an Item needed by the UI. // Generally this will include everything needed by the sim, plus some // additional data for displaying / filtering. -// NextIndex: 32 +// NextIndex: 33 message UIItem { int32 id = 1; string name = 2; @@ -73,6 +73,7 @@ message UIItem { bool unique = 19; bool heroic = 20; bool timeworn = 30; + bool sanctified = 32; // Classes that are allowed to use the item. Empty indicates no special class restrictions. repeated Class class_allowlist = 21; diff --git a/sim/common/sod/item_effects/phase_7.go b/sim/common/sod/item_effects/phase_7.go index 2cb7ed9038..909388dbc0 100644 --- a/sim/common/sod/item_effects/phase_7.go +++ b/sim/common/sod/item_effects/phase_7.go @@ -1,14 +1,78 @@ package item_effects -import "github.com/wowsims/sod/sim/core" +import ( + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/stats" +) const ( BulwarkOfIre = 235868 + + // Seals of the Dawn + SquiresSealOfTheDawnDamage = 236356 + KnightsSealOfTheDawnDamage = 236357 + TemplarsSealOfTheDawnDamage = 236358 + ChampionsSealOfTheDawnDamage = 236360 + VanguardsSealOfTheDawnDamage = 236361 + CrusadersSealOfTheDawnDamage = 236362 + CommandersSealOfTheDawnDamage = 236363 + HighlordsSSealOfTheDawnDamage = 236364 + + SquiresSealOfTheDawnHealing = 236383 + KnightsSealOfTheDawnHealing = 236382 + TemplarsSealOfTheDawnHealing = 236380 + ChampionsSealOfTheDawnHealing = 236379 + VanguardsSealOfTheDawnHealing = 236378 + CrusadersSealOfTheDawnHealing = 236376 + CommandersSealOfTheDawnHealing = 236375 + HighlordsSSealOfTheDawnHealing = 236374 + + SquiresSealOfTheDawnTanking = 236394 + KnightsSealOfTheDawnTanking = 236393 + TemplarsSealOfTheDawnTanking = 236392 + ChampionsSealOfTheDawnTanking = 236391 + VanguardsSealOfTheDawnTanking = 236390 + CrusadersSealOfTheDawnTanking = 236389 + CommandersSealOfTheDawnTanking = 236388 + HighlordsSSealOfTheDawnTanking = 236386 ) func init() { core.AddEffectsToTest = false + /////////////////////////////////////////////////////////////////////////// + // Trinkets + /////////////////////////////////////////////////////////////////////////// + // https://www.wowhead.com/classic/item=236356/squires-seal-of-the-dawn + core.NewItemEffect(SquiresSealOfTheDawnDamage, sanctifiedDamageEffect) + core.NewItemEffect(KnightsSealOfTheDawnDamage, sanctifiedDamageEffect) + core.NewItemEffect(TemplarsSealOfTheDawnDamage, sanctifiedDamageEffect) + core.NewItemEffect(ChampionsSealOfTheDawnDamage, sanctifiedDamageEffect) + core.NewItemEffect(VanguardsSealOfTheDawnDamage, sanctifiedDamageEffect) + core.NewItemEffect(CrusadersSealOfTheDawnDamage, sanctifiedDamageEffect) + core.NewItemEffect(CommandersSealOfTheDawnDamage, sanctifiedDamageEffect) + core.NewItemEffect(HighlordsSSealOfTheDawnDamage, sanctifiedDamageEffect) + + // https://www.wowhead.com/classic/item=236383/squires-seal-of-the-dawn + core.NewItemEffect(SquiresSealOfTheDawnHealing, sanctifiedHealingEffect) + core.NewItemEffect(KnightsSealOfTheDawnHealing, sanctifiedHealingEffect) + core.NewItemEffect(TemplarsSealOfTheDawnHealing, sanctifiedHealingEffect) + core.NewItemEffect(ChampionsSealOfTheDawnHealing, sanctifiedHealingEffect) + core.NewItemEffect(VanguardsSealOfTheDawnHealing, sanctifiedHealingEffect) + core.NewItemEffect(CrusadersSealOfTheDawnHealing, sanctifiedHealingEffect) + core.NewItemEffect(CommandersSealOfTheDawnHealing, sanctifiedHealingEffect) + core.NewItemEffect(HighlordsSSealOfTheDawnHealing, sanctifiedHealingEffect) + + // https://www.wowhead.com/classic/item=236394/squires-seal-of-the-dawn + core.NewItemEffect(SquiresSealOfTheDawnTanking, sanctifiedTankingEffect) + core.NewItemEffect(KnightsSealOfTheDawnTanking, sanctifiedTankingEffect) + core.NewItemEffect(TemplarsSealOfTheDawnTanking, sanctifiedTankingEffect) + core.NewItemEffect(ChampionsSealOfTheDawnTanking, sanctifiedTankingEffect) + core.NewItemEffect(VanguardsSealOfTheDawnTanking, sanctifiedTankingEffect) + core.NewItemEffect(CrusadersSealOfTheDawnTanking, sanctifiedTankingEffect) + core.NewItemEffect(CommandersSealOfTheDawnTanking, sanctifiedTankingEffect) + core.NewItemEffect(HighlordsSSealOfTheDawnTanking, sanctifiedTankingEffect) + /////////////////////////////////////////////////////////////////////////// // Other /////////////////////////////////////////////////////////////////////////// @@ -46,3 +110,25 @@ func init() { core.AddEffectsToTest = true } + +// Equip: Unlocks your potential while inside Naxxramas. +// Increasing your damage by 2% for each piece of Sanctified armor equipped. +func sanctifiedDamageEffect(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.DamageDealtMultiplier *= 1 + 0.02*float64(character.PseudoStats.SanctifiedBonus) +} + +// Equip: Unlocks your potential while inside Naxxramas. +// Increasing your healing and shielding by 2% for each piece of Sanctified armor equipped. +func sanctifiedHealingEffect(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.HealingDealtMultiplier *= 1 + 0.02*float64(character.PseudoStats.SanctifiedBonus) +} + +// Equip: Unlocks your potential while inside Naxxramas. +// Increasing your threat caused by 1% and health by 1 for each piece of Sanctified armor equipped. +func sanctifiedTankingEffect(agent core.Agent) { + character := agent.GetCharacter() + character.PseudoStats.ThreatMultiplier *= 1 + 0.01*float64(character.PseudoStats.SanctifiedBonus) + character.AddStat(stats.Health, 1*float64(character.PseudoStats.SanctifiedBonus)) +} diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index 6199978bf1..a2b6d0bb67 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -44,6 +44,7 @@ const ( SerpentSlicer = 13035 TheNeedler = 13060 SealOfTheDawn = 13209 + CloudkeeperLegplates = 14554 JoonhosMercy = 17054 Deathbringer = 17068 ViskagTheBloodletter = 17075 @@ -2904,6 +2905,10 @@ func init() { BlazefuryTriggerAura(character, 7712, core.SpellSchoolFire, 2) }) + // https://www.wowhead.com/classic/item=14554/cloudkeeper-legplates + // Use: Increases Attack Power by 100 for 30 sec. (15 Min Cooldown) + core.NewSimpleStatOffensiveTrinketEffect(CloudkeeperLegplates, stats.Stats{stats.AttackPower: 100, stats.RangedAttackPower: 100}, time.Second*30, time.Minute*15) + // https://www.wowhead.com/classic/item=228266/drillborer-disk // Equip: When struck in combat inflicts 3 Arcane damage to the attacker. core.NewItemEffect(DrillborerDisk, func(agent core.Agent) { diff --git a/sim/core/character.go b/sim/core/character.go index 7eb8828e60..cd6d94b67b 100644 --- a/sim/core/character.go +++ b/sim/core/character.go @@ -191,6 +191,7 @@ func NewCharacter(party *Party, partyIndex int, player *proto.Player) Character character.PseudoStats.GunsSkill += ps[proto.PseudoStat_PseudoStatGunsSkill] character.PseudoStats.BonusPhysicalDamage += ps[proto.PseudoStat_PseudoStatBonusPhysicalDamage] character.PseudoStats.TimewornBonus += int32(ps[proto.PseudoStat_PseudoStatTimewornBonus]) + character.PseudoStats.SanctifiedBonus += int32(ps[proto.PseudoStat_PseudoStatSanctifiedBonus]) } } @@ -267,6 +268,8 @@ func (character *Character) applyEquipment() { for _, item := range character.Equipment { if item.Timeworn { character.PseudoStats.TimewornBonus += 1 + } else if item.Sanctified { + character.PseudoStats.SanctifiedBonus += 1 } character.PseudoStats.BonusPhysicalDamage += item.BonusPhysicalDamage @@ -613,6 +616,8 @@ func (character *Character) GetPseudoStatsProto() []float64 { proto.PseudoStat_PseudoStatRangedDps: character.AutoAttacks.Ranged().DPS(), proto.PseudoStat_PseudoStatBonusPhysicalDamage: float64(character.PseudoStats.BonusPhysicalDamage), proto.PseudoStat_PseudoStatBlockValueMultiplier: character.PseudoStats.BlockValueMultiplier, + proto.PseudoStat_PseudoStatThornsDamage: character.PseudoStats.ThornsDamage, + proto.PseudoStat_PseudoStatAxesSkill: float64(character.PseudoStats.AxesSkill), proto.PseudoStat_PseudoStatSwordsSkill: float64(character.PseudoStats.SwordsSkill), proto.PseudoStat_PseudoStatMacesSkill: float64(character.PseudoStats.MacesSkill), @@ -640,8 +645,8 @@ func (character *Character) GetPseudoStatsProto() []float64 { proto.PseudoStat_PseudoStatRangedSpeedMultiplier: float64(character.PseudoStats.RangedSpeedMultiplier), proto.PseudoStat_PseudoStatBlockValuePerStrength: float64(character.PseudoStats.BlockValuePerStrength), - proto.PseudoStat_PseudoStatTimewornBonus: float64(character.PseudoStats.TimewornBonus), - proto.PseudoStat_PseudoStatThornsDamage: character.PseudoStats.ThornsDamage, + proto.PseudoStat_PseudoStatTimewornBonus: float64(character.PseudoStats.TimewornBonus), + proto.PseudoStat_PseudoStatSanctifiedBonus: float64(character.PseudoStats.SanctifiedBonus), } } diff --git a/sim/core/database.go b/sim/core/database.go index 319af4020c..18f482baf7 100644 --- a/sim/core/database.go +++ b/sim/core/database.go @@ -66,7 +66,8 @@ type Item struct { SetID int32 // 0 if not part of a set. WeaponSkills stats.WeaponSkills - Timeworn bool + Timeworn bool + Sanctified bool // Modified for each instance of the item. RandomSuffix RandomSuffix @@ -97,6 +98,7 @@ func ItemFromProto(pData *proto.SimItem) Item { SetID: pData.SetId, WeaponSkills: stats.WeaponSkillsFloatArray(pData.WeaponSkills), Timeworn: pData.Timeworn, + Sanctified: pData.Sanctified, } } diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index 8019d84b65..d470a58947 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -471,6 +471,9 @@ type PseudoStats struct { // Tracks the number of Timeworn items equipped for Bronze Signet bonuses TimewornBonus int32 + // Tracks the number of Sanctified items equipped for Seal of the Dawn bonuses + SanctifiedBonus int32 + // Tracks the cumulative amount of Thorns damage from various effects purely for UI display ThornsDamage float64 diff --git a/tools/database/wowhead_tooltips.go b/tools/database/wowhead_tooltips.go index 5f677b3c9a..20ff9dc4f6 100644 --- a/tools/database/wowhead_tooltips.go +++ b/tools/database/wowhead_tooltips.go @@ -706,6 +706,7 @@ func (item WowheadItemResponse) ToItemProto() *proto.UIItem { Unique: item.GetUnique(), Heroic: item.IsHeroic(), Timeworn: item.IsTimeworn(), + Sanctified: item.IsSanctified(), RequiredProfession: item.GetRequiredProfession(), SetName: item.GetItemSetName(), @@ -766,6 +767,12 @@ func (item WowheadItemResponse) IsTimeworn() bool { return timewornRegexp.MatchString(item.Tooltip) } +var sanctifiedRegexp = regexp.MustCompile(`Sanctified<\/span>`) + +func (item WowheadItemResponse) IsSanctified() bool { + return sanctifiedRegexp.MatchString(item.Tooltip) +} + func (item WowheadItemResponse) GetRequiredProfession() proto.Profession { if alchemyRegex.MatchString(item.Tooltip) { return proto.Profession_Alchemy diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index b64affa537..65e2da1813 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -92,6 +92,7 @@ const statGroups = new Map>([ [ {stat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatThornsDamage), notEditable: true}, {stat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatTimewornBonus), notEditable: true}, + {stat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatSanctifiedBonus), notEditable: true}, ] ], ]) diff --git a/ui/core/constants/other.ts b/ui/core/constants/other.ts index e5058b29f3..35ef280a58 100644 --- a/ui/core/constants/other.ts +++ b/ui/core/constants/other.ts @@ -41,6 +41,7 @@ export const GLOBAL_DISPLAY_STATS = [ export const GLOBAL_DISPLAY_PSEUDO_STATS = [ PseudoStat.PseudoStatTimewornBonus, + PseudoStat.PseudoStatSanctifiedBonus, ]; export const GLOBAL_EP_STATS = [ @@ -51,6 +52,7 @@ export const GLOBAL_EP_STATS = [ export const GLOBAL_EP_PSEUDOSTATS = [ PseudoStat.PseudoStatTimewornBonus, + PseudoStat.PseudoStatSanctifiedBonus, ]; export enum SortDirection { diff --git a/ui/core/player.ts b/ui/core/player.ts index 43779fb882..627dfa763a 100644 --- a/ui/core/player.ts +++ b/ui/core/player.ts @@ -1098,6 +1098,10 @@ export class Player { ep += this.epWeights.getPseudoStat(PseudoStat.PseudoStatTimewornBonus) } + if (item.sanctified) { + ep += this.epWeights.getPseudoStat(PseudoStat.PseudoStatSanctifiedBonus) + } + this.itemEPCache[slot].set(item.id, ep); return ep; } diff --git a/ui/core/proto_utils/names.ts b/ui/core/proto_utils/names.ts index c5e35110ae..20069e2c17 100644 --- a/ui/core/proto_utils/names.ts +++ b/ui/core/proto_utils/names.ts @@ -208,6 +208,7 @@ export const pseudoStatNames: Map = new Map([ [PseudoStat.PseudoStatRangedDps, 'Ranged DPS'], [PseudoStat.PseudoStatBlockValueMultiplier, 'Block Value Multiplier'], [PseudoStat.PseudoStatTimewornBonus, 'Timeworn Pieces'], + [PseudoStat.PseudoStatSanctifiedBonus, 'Sanctified Pieces'], [PseudoStat.PseudoStatBonusPhysicalDamage, 'Bonus Weapon Damage'], [PseudoStat.PseudoStatThornsDamage, 'Total Thorns Damage'], ]); From eb6adb31861b87032060ea60d2435171b84fe9ce Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 12 Jan 2025 11:36:18 -0500 Subject: [PATCH 12/15] skullflame shield scales with spell power --- sim/common/vanilla/item_effects.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/sim/common/vanilla/item_effects.go b/sim/common/vanilla/item_effects.go index a2b6d0bb67..c187084f58 100644 --- a/sim/common/vanilla/item_effects.go +++ b/sim/common/vanilla/item_effects.go @@ -2973,11 +2973,16 @@ func init() { drainLifeActionID := core.ActionID{SpellID: 18817} healthMetrics := character.NewHealthMetrics(drainLifeActionID) drainLifeSpell := character.RegisterSpell(core.SpellConfig{ - ActionID: drainLifeActionID, - SpellSchool: core.SpellSchoolShadow, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskEmpty, + ActionID: drainLifeActionID, + SpellSchool: core.SpellSchoolShadow, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: 1, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcAndDealDamage(sim, target, 35, spell.OutcomeAlwaysHit) character.GainHealth(sim, result.Damage, healthMetrics) @@ -2985,12 +2990,15 @@ func init() { }) flamestrikeSpell := character.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 18818}, - SpellSchool: core.SpellSchoolFire, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskEmpty, + ActionID: core.ActionID{SpellID: 18818}, + SpellSchool: core.SpellSchoolFire, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + DamageMultiplier: 1, ThreatMultiplier: 1, + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { for _, aoeTarget := range sim.Encounter.TargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, sim.Roll(75, 125), spell.OutcomeMagicHit) From a7448d6bb221466819184c874f39d4989f456a38 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 12 Jan 2025 11:54:21 -0500 Subject: [PATCH 13/15] add vampiric phase 6 gear --- sim/common/sod/item_effects/phase_6.go | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/sim/common/sod/item_effects/phase_6.go b/sim/common/sod/item_effects/phase_6.go index d46bb7d01e..36bc1dcf24 100644 --- a/sim/common/sod/item_effects/phase_6.go +++ b/sim/common/sod/item_effects/phase_6.go @@ -22,6 +22,9 @@ const ( RazorbrambleShoulderpads = 233804 RazorbrambleCowl = 233808 RazorbrambleLeathers = 233813 + VampiricCowl = 233826 + VampiricShawl = 233833 + VampiricRobe = 233837 TunedForceReactiveDisk = 233988 LodestoneOfRetaliation = 233992 @@ -464,6 +467,24 @@ func init() { }) }) + // https://www.wowhead.com/classic/item=233826/vampiric-cowl + // Equip: When struck in combat has a 20% chance of stealing 50 life from target enemy. (Proc chance: 20%) + core.NewItemEffect(VampiricCowl, func(agent core.Agent) { + vampiricDrainLifeEffect(agent, VampiricCowl, "Vampiric Cowl", 50) + }) + + // https://www.wowhead.com/classic/item=233833/vampiric-shawl + // Equip: When struck in combat has a 20% chance of stealing 40 life from target enemy. (Proc chance: 20%) + core.NewItemEffect(VampiricShawl, func(agent core.Agent) { + vampiricDrainLifeEffect(agent, VampiricShawl, "Vampiric Shawl", 40) + }) + + // https://www.wowhead.com/classic/item=233837/vampiric-robe + // Equip: When struck in combat has a 20% chance of stealing 50 life from target enemy. (Proc chance: 20%) + core.NewItemEffect(VampiricRobe, func(agent core.Agent) { + vampiricDrainLifeEffect(agent, VampiricRobe, "Vampiric Robe", 50) + }) + core.AddEffectsToTest = true } @@ -705,3 +726,36 @@ func thornsNatureDamageEffect(agent core.Agent, itemID int32, itemName string, d }, })) } + +func vampiricDrainLifeEffect(agent core.Agent, itemID int32, itemName string, damage float64) { + character := agent.GetCharacter() + + actionID := core.ActionID{ItemID: itemID} + healthMetrics := character.NewHealthMetrics(actionID) + drainLifeSpell := character.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolShadow, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeAlwaysHit) + character.GainHealth(sim, result.Damage, healthMetrics) + }, + }) + + core.MakeProcTriggerAura(&character.Unit, core.ProcTrigger{ + Name: fmt.Sprintf("Drain Life Trigger (%s)", itemName), + Callback: core.CallbackOnSpellHitTaken, + Outcome: core.OutcomeLanded, + ProcMask: core.ProcMaskMelee, + ProcChance: 0.20, + Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + drainLifeSpell.Cast(sim, spell.Unit) + }, + }) +} From 44f0c45793c2296bd0abeb60b3136d4432739044 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sun, 12 Jan 2025 12:36:36 -0500 Subject: [PATCH 14/15] run make items --- assets/database/db.bin | Bin 6448409 -> 6448412 bytes assets/database/db.json | 6 +++--- assets/database/leftover_db.bin | Bin 927580 -> 927580 bytes 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/database/db.bin b/assets/database/db.bin index 34e7a7ff1e3bf424741470cbde68720b3b9be031..67ff833567d33852bd2f08d77739a092349a9851 100644 GIT binary patch delta 285 zcmWN_H%`M~7=U5@Hob;WLWdAz(|ahUg-$|AFb31xz?M5?c%=v-Au%u@v9hrsA^msY z5}Y8)75Ii{dES4Y%ny-6VPH~1DP@#XK_yjGQ$sCv)DxnCFpV_PObZcOX``JEI_aXD z9(sw=M+}R8Y#as{WQbu#7-fucCYWT3X=aEs%N+A8;IhaP%dD_Uf;HCJV3RGj*#M}K94 i7o{U23vnr~#I?8)x8hFRYu9`D3;zS6_Gf+o delta 281 zcmWm2M@|A!9Khi`uy?UzL1knVv5S4|4J_a|_!PSf*BrscFCoUnl`c$Nx$*!c?+#wV z6A-T8-^I82K7XG5AL0px4?ks;Q$ZzFR8vDOb=1>9BTY0DpoLc2Xs3ftx(L!u554pe zqMrc<31cxt1e;+-h%!oyF~*r-k}0N{VU{>^%(K8EODwa3!zyd6v%w}?Y_r2Id+d`S z$pMEPaZHLdC%BvzyhKrbnPkxm`|`PxCo(2iIyjl81nS!k!ZS$S}pI}PA ds=VWBaV{=IR$PiJaV>7dt+*5S#&I8h1AlEIXCMFo diff --git a/assets/database/db.json b/assets/database/db.json index b450b79a23..ec569dde34 100644 --- a/assets/database/db.json +++ b/assets/database/db.json @@ -9536,7 +9536,7 @@ {"id":236219,"name":"Harbinger of Doom","icon":"inv_knife_1h_stratholme_d_03","type":13,"weaponType":2,"handType":2,"requiresLevel":60,"stats":[0,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponDamageMin":83,"weaponDamageMax":126,"weaponSpeed":1.6,"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":83,"phase":7,"quality":4,"unique":true}, {"id":236220,"name":"Necro-Knight's Garb","icon":"inv_chest_cloth_46","type":5,"armorType":1,"requiresLevel":60,"stats":[0,0,32,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,448,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":85,"phase":7,"quality":4,"classAllowlist":[5,3,8]}, {"id":236221,"name":"Misplaced Servo Arm","icon":"inv_mace_28","type":13,"weaponType":4,"handType":2,"requiresLevel":60,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponDamageMin":128,"weaponDamageMax":238,"weaponSpeed":2.8,"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":83,"phase":7,"quality":4}, -{"id":236222,"name":"Ghoul Skin Tunic","icon":"inv_chest_leather_02","type":5,"armorType":2,"requiresLevel":60,"stats":[40,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,411,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":83,"phase":7,"quality":4}, +{"id":236222,"name":"Ghoul Skin Tunic","icon":"inv_chest_leather_02","type":5,"armorType":2,"requiresLevel":60,"stats":[40,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,411,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":83,"phase":7,"quality":4,"sanctified":true}, {"id":236223,"name":"Ring of the Eternal Flame","icon":"inv_jewelry_ring_48naxxramas","type":11,"requiresLevel":60,"stats":[0,0,0,10,0,0,0,34,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":83,"phase":7,"quality":4,"unique":true}, {"id":236224,"name":"Stygian Buckler","icon":"inv_armor_shield_naxxramas_d_02","type":13,"weaponType":7,"handType":3,"requiresLevel":60,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3106,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":83,"phase":7,"quality":4}, {"id":236225,"name":"Girdle of Elemental Fury","icon":"inv_belt_32","type":8,"armorType":3,"requiresLevel":60,"stats":[0,0,20,21,0,29,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,301,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"weaponSkills":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilvl":85,"phase":7,"quality":4}, @@ -10933,8 +10933,8 @@ {"effectId":63,"spellId":13538,"name":"Enchant Chest - Lesser Absorption","type":5,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":66,"spellId":7457,"name":"Enchant Bracer - Minor Stamina","type":6,"stats":[0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":66,"spellId":7863,"name":"Enchant Boots - Minor Stamina","type":10,"stats":[0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, -{"effectId":241,"spellId":13503,"name":"Enchant Weapon - Lesser Striking","type":13,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":241,"spellId":7745,"name":"Enchant 2H Weapon - Minor Impact","type":13,"enchantType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, +{"effectId":241,"spellId":13503,"name":"Enchant Weapon - Lesser Striking","type":13,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":242,"spellId":7748,"name":"Enchant Chest - Lesser Health","type":5,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":243,"spellId":7766,"name":"Enchant Bracer - Minor Spirit","type":6,"stats":[0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":2}, {"effectId":246,"spellId":7776,"name":"Enchant Chest - Lesser Mana","type":5,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":2}, @@ -11009,8 +11009,8 @@ {"effectId":929,"itemId":16217,"spellId":20017,"name":"Enchant Shield - Greater Stamina","type":13,"enchantType":2,"stats":[0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":930,"spellId":13947,"name":"Enchant Gloves - Riding Skill","type":7,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":2}, {"effectId":931,"spellId":13948,"name":"Enchant Gloves - Minor Haste","type":7,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, -{"effectId":943,"spellId":13529,"name":"Enchant 2H Weapon - Lesser Impact","type":13,"enchantType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":943,"spellId":13693,"name":"Enchant Weapon - Striking","type":13,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, +{"effectId":943,"spellId":13529,"name":"Enchant 2H Weapon - Lesser Impact","type":13,"enchantType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":963,"spellId":13937,"name":"Enchant 2H Weapon - Greater Impact","type":13,"enchantType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":1}, {"effectId":1483,"itemId":11622,"spellId":15340,"name":"Lesser Arcanum of Rumination","type":1,"extraTypes":[9],"enchantType":3,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"quality":2}, {"effectId":1503,"itemId":11642,"spellId":15389,"name":"Lesser Arcanum of Constitution","type":1,"extraTypes":[9],"enchantType":3,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0],"quality":2}, diff --git a/assets/database/leftover_db.bin b/assets/database/leftover_db.bin index a1c042cac97c4a76b075b16682324401f107250a..aa3fd0b1bbdcbfbba3227af71c54d51cf0ec029b 100644 GIT binary patch delta 73 zcmV-P0Ji_!&N$r8IDmu!gaU*Egam{Iga(8Mgb0KQgbIWUvvvy4Gp)*4GxqU?rItk delta 73 zcmV-P0Ji_!&N$r8IDmu!gaU*Egam{Iga(8Mgb0KQgbIWUv Date: Sun, 12 Jan 2025 12:40:17 -0500 Subject: [PATCH 15/15] run atlasloot scraper --- assets/database/db.bin | Bin 6448412 -> 6449312 bytes assets/database/db.json | 120 ++++++++++++++--------------- assets/database/leftover_db.bin | Bin 927580 -> 927580 bytes assets/database/leftover_db.json | 4 +- assets/db_inputs/atlasloot_db.json | 120 ++++++++++++++++++++++------- 5 files changed, 154 insertions(+), 90 deletions(-) diff --git a/assets/database/db.bin b/assets/database/db.bin index 67ff833567d33852bd2f08d77739a092349a9851..d725a4994c0063f90a6aa00069aadd7d16168f9b 100644 GIT binary patch delta 2466 zcmb7Fdr(wm6rT_7wig%jiYE8~m+(PA@~~i4kh_~ax2$zzpLwjy7he zMWz{sjpU<-WF=5Q5q!{Tk_2hee1tG*r8A2EIT_7pPGeD1eRmhQ%R)~3$M4Ra-=5$3 zo$s8x=c~V18R#0U4BP_*0w53sFu-GA1QCK@8kis$Lf~u1y4dWEQCd{7?wZ`JO!~32l4PUEQJI}gd|9YW$+9<3n`Ea7FZ4| zU?rqMI;?^WkiiNzcn(%WCS*Z28WSUfZ87Gr4{F zU8gKExh~zn?(L#Rwl-b~6E6~DtU;_YvUh&BRIt+OHG(F|XGbq(N$g&G-1`3|)VvOP zguBZMxJgnNe>BMhHJ$f&KGKq0$5jc+Wiqi4&Q>Q)V97PPLUU==TsAR#wJm(V%VuHH z9^(kRnW{wV3C2R(JO!oA%$1n(hsDl%_u7WpP^uC&Iraz}wDPo_C$lY1xsDBNRUK^Q z3h(}mWj(4Fz+9JYdShK@GYzaalSb;<%SJp!x9b|c=tsmKb61X+GL+q3jnT64#`Ie! zG{(*Lp?S5^Uq#w)fE1!}j+#}Q=5~-zp?5%bDt!@16FgxgqagZDM z@dKsVFVTDKw!OeaF5s2llmmV#9po!~@KCAqQ#61xM*OVNLB7Sbzm!Hlo$lb)zm;x3 zML%^NqG$Zmu+ol50e=ag{%8?6%Um_(n%9ijA)PPce`Kf}gZwAGs)WJQ(u-Ry~^`|~`B z0duBE(T7Xt(Jt>4Jh~F!h@eM&NC)Y_`USL6r`xnDD)C&DcdVOqkS=@}O9|7m^rXS{dKqx5M^N${S6hi2xt^90SIbyk)^i&$?+vdHqC1^Sx#ua(Fb*$t0IYPN~2DoB-TGx82Q z<(jE?86L=?C*sIANXzMIpRH&Z62z|$Gjlp!$IR`r#1elDmaz2alw`u({_otT|U9AVJD5yMcP3%(fLu4Ylk4Qgm+UV?Z)6`zdwE@dO2wqFn+X~ z2Ha$>tQ-LwHmF|T&**^8;=m^LluKy3l>k+`@yesDoY|)lSOH z4dDXO^VOBqo650{qln{qEZkQ7MJCpF6n{WaJXZW6!KKHGhd;p< srDQ+BxobeN2^Q6yI}s>ArLcE+2`CBH$Ma2=bLC>DyJfq$2Ng<3kYzolQ3ifiO2??arN> zm(~{%oHdJ~fk1%Zg?3C9AI);ZCLiI%Rtu`Ft^5O`?17v<)fyv>N^&z!XR1XwotV<&t5G?w8W*G;(q&2R(dbnan4xZE*m^J5fy!5lBoc=%J=RDrkLG!+ zXwKvwZ~M*Js=xb{#_H8s-<{rK#&>(HgSAQ!_Pl63g$pd|$W~uyW)~>vclBe2Z=AC^ zn2QeIQ(N2t;B-m0iJhatKh#G3I{2(4x3mNVn|D+mI(oe9?pw ztLEQzQ2QF*#_+Dm=A@ru_z=U5Yki%R8Y|q9hXy<8m3Tht5x$k63A;;KmLy6+{*8~5 z3R6S{TBd|){Dw(e%3By^<++$xTqNUcZJdlsiKV+%se9zQOu5PR@5nNa?o06wgm)bx ztz6$i_n3XFiH$IM)hP)tETN8je%X<70U*w;w0~9*xQ}xkZwxyml##etEl|9 zv?PY^p)Y%tM{0}VU^gkEnoqblT?_25%?C7N=Z5duCHnb*O|Oy>MVHw67|uv0r(v=K zXEN3OIGw@V+0;!3UiMC^?q0kQc|lN ztLwRYYN1>1pN8YiM5#^Sd4XJkJenxYzmh=_z4`Amdh=5LdyHIt`FB2|e-p$yaIu}0{a9C1@;RR2^F7M2#)7Pc1l7LFFq7OocV7M?AF7M2#)7Pc1l7LFFq7OocV7M?A