diff --git a/spec/System/TestTriggers_spec.lua b/spec/System/TestTriggers_spec.lua index 5769afa3b6..12e9a3ec10 100644 --- a/spec/System/TestTriggers_spec.lua +++ b/spec/System/TestTriggers_spec.lua @@ -1,4 +1,32 @@ describe("TestTriggers", function() + local function equipDreadCaptainsCutlass() + build.itemsTab:CreateDisplayItemFromRaw([[Dread Captain's Cutlass + Ghostflame Blade + League: Allflame + Crafted: true + Prefix: DeepwaterSwordGrantedSkillGhostCannons + Sockets: R-R-R + LevelReq: 68 + Implicits: 2 + Can be Allflame Crafted as if Rare + Cannot gain Intangibility + Trigger level 20 Ghostly Artillery when you Attack with this Weapon]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + end + + local function equipReviewSword() + build.itemsTab:CreateDisplayItemFromRaw([[Rarity: RARE + Review Sword + Rusted Sword + Crafted: true + Implicits: 0 + Adds 1000 to 1000 Physical Damage + Hits can't be Evaded]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + end + before_each(function() newBuild() end) @@ -1437,7 +1465,7 @@ describe("TestTriggers", function() assert.are.not_equals(math.floor(build.calcsTab.mainOutput.SkillTriggerRate * 100), math.floor(baseRate * 100)) end) - it("skillId config search", function() + it("Trigger Fiery Impact with its source weapon", function() build.itemsTab:CreateDisplayItemFromRaw([[Rarity: RARE Physical 1H Mace Boom Mace @@ -1465,7 +1493,63 @@ describe("TestTriggers", function() build.skillsTab:PasteSocketGroup("Smite 20/0 1\n") runCallback("OnFrame") - local baseRate = build.calcsTab.mainOutput.SkillTriggerRate + local baseRate = build.calcsTab.mainOutput.EffectiveSourceRate assert.True(build.calcsTab.mainOutput.SkillTriggerRate ~= nil) + + equipReviewSword() + runCallback("OnFrame") + + assert.is_true(build.calcsTab.mainOutput.EffectiveSourceRate < baseRate) + assert.is_true(build.calcsTab.mainEnv.player.mainSkill.skillFlags.weapon1Attack) + assert.is_falsy(build.calcsTab.mainEnv.player.mainSkill.skillFlags.weapon2Attack) + end) + + it("Trigger Ghostly Artillery with a projectile attack", function() + equipDreadCaptainsCutlass() + build.skillsTab:PasteSocketGroup("Lancing Steel 20/0 1\n") + runCallback("OnFrame") + + assert.are.near(build.calcsTab.mainOutput.EffectiveSourceRate, build.calcsTab.mainOutput.SkillTriggerRate, 0.01) + end) + + it("Do not add socketed attacks to the Ghostly Artillery trigger rotation", function() + equipDreadCaptainsCutlass() + build.skillsTab:PasteSocketGroup("Smite 20/0 1\n") + runCallback("OnFrame") + local bodyArmourRate = build.calcsTab.mainOutput.SkillTriggerRate + + newBuild() + equipDreadCaptainsCutlass() + build.skillsTab:PasteSocketGroup("Slot: Weapon 1\nSmite 20/0 1\n") + runCallback("OnFrame") + + assert.are.near(bodyArmourRate, build.calcsTab.mainOutput.SkillTriggerRate, 10 ^ -9) + end) + + it("Use only the Cutlass when dual wielding", function() + equipDreadCaptainsCutlass() + build.skillsTab:PasteSocketGroup("Smite 20/0 1\n") + runCallback("OnFrame") + local singleWeaponRate = build.calcsTab.mainOutput.EffectiveSourceRate + + newBuild() + equipDreadCaptainsCutlass() + equipReviewSword() + build.skillsTab:PasteSocketGroup("Smite 20/0 1\n") + runCallback("OnFrame") + + assert.is_true(build.calcsTab.mainOutput.EffectiveSourceRate < singleWeaponRate) + assert.is_true(build.calcsTab.mainEnv.player.mainSkill.skillFlags.weapon1Attack) + assert.is_falsy(build.calcsTab.mainEnv.player.mainSkill.skillFlags.weapon2Attack) + assert.is_nil(build.calcsTab.mainOutput.OffHand and build.calcsTab.mainOutput.OffHand.AverageHit) + + newBuild() + equipReviewSword() + equipDreadCaptainsCutlass() + build.skillsTab:PasteSocketGroup("Smite 20/0 1\n") + runCallback("OnFrame") + + assert.is_falsy(build.calcsTab.mainEnv.player.mainSkill.skillFlags.weapon1Attack) + assert.is_true(build.calcsTab.mainEnv.player.mainSkill.skillFlags.weapon2Attack) end) end) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index deb1fcc43f..a61947b5b0 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -607,10 +607,12 @@ holding Shift will put it in the second.]]) self.controls.displayItemSectionInfluence = new("Control", {"TOPLEFT",self.controls.displayItemSectionEnchant,"BOTTOMLEFT"}, {0, 0, 0, function() return self.displayItem and self.displayItem.canBeInfluenced and 28 or 0 end}) + local influenceTipText = table.concat(main:WrapString("Selecting an influence here will also allow the modifier dropdowns to contain influenced mods.", 16, 140), "\n") self.controls.displayItemInfluence = new("DropDownControl", {"TOPLEFT",self.controls.displayItemSectionInfluence,"TOPRIGHT"}, {0, 0, 100, 20}, influenceDisplayList, function(index, value) local otherIndex = self.controls.displayItemInfluence2.selIndex setDisplayItemInfluence({ index - 1, otherIndex - 1 }) end) + self.controls.displayItemInfluence.tooltipText = influenceTipText self.controls.displayItemInfluence.shown = function() return self.displayItem and self.displayItem.canBeInfluenced end @@ -621,6 +623,7 @@ holding Shift will put it in the second.]]) self.controls.displayItemInfluence2.shown = function() return self.displayItem and self.displayItem.canBeInfluenced end + self.controls.displayItemInfluence2.tooltipText = influenceTipText -- Section: Item Quality self.controls.displayItemSectionQuality = new("Control", {"TOPLEFT",self.controls.displayItemSectionInfluence,"BOTTOMLEFT"}, {0, 0, 0, function() @@ -959,7 +962,7 @@ holding Shift will put it in the second.]]) self:AddCustomModifierToDisplayItem() end) self.controls.displayItemAddCustom.shown = function() - return self.displayItem and (self.displayItem.rarity == "MAGIC" or self.displayItem.rarity == "RARE") + return self.displayItem and (self.displayItem.rarity == "MAGIC" or self.displayItem.rarity == "RARE" or (self.displayItem.rareLikeUnique and self.displayItem.rareLikeUnique.supportsCustomModifiers)) end -- Section: Crucible modifiers @@ -3465,6 +3468,18 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() t_insert(sourceList, { label = "Suffix", sourceId = "SUFFIX" }) end end + if self.displayItem.rareLikeUnique then + local applicableSourceIds = self.displayItem.rareLikeUnique.supportsCustomModifiers + if applicableSourceIds then + local newSourceList = {} + for _, list in ipairs(sourceList) do + if applicableSourceIds[list.sourceId] then + table.insert(newSourceList, list) + end + end + sourceList = newSourceList + end + end t_insert(sourceList, { label = "Custom", sourceId = "CUSTOM" }) buildMods(sourceList[1].sourceId) local function addModifier() @@ -3477,8 +3492,10 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() end else local listMod = modList[controls.modSelect.selIndex] - for _, line in ipairs(listMod.mod) do - t_insert(item.explicitModLines, { line = line, modTags = listMod.mod.modTags, [listMod.type] = true }) + if listMod then + for _, line in ipairs(listMod.mod) do + t_insert(item.explicitModLines, { line = line, modTags = listMod.mod.modTags, [listMod.type] = true }) + end end end item:BuildAndParseRaw() diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index 81bd59d929..b7d020cc0a 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -7952,6 +7952,7 @@ c["Can Allocate Passives from the Templar's starting point"]={{},nil} c["Can Allocate Passives from the Witch's starting point"]={{},nil} c["Can Consume 4 Uncorrupted Support Gems"]={nil,"Can Consume 4 Uncorrupted Support Gems "} c["Can Consume 4 Uncorrupted Support Gems Has not Consumed any Gems"]={nil,"Can Consume 4 Uncorrupted Support Gems Has not Consumed any Gems "} +c["Can be Allflame Crafted as if Rare"]={{},nil} c["Can be Enchanted by a Kalguuran Runesmith"]={nil,"Can be Enchanted by a Kalguuran Runesmith "} c["Can be Enchanted by a Kalguuran Runesmith Can have 2 additional Runesmithing Enchantments"]={nil,"Can be Enchanted by a Kalguuran Runesmith Can have 2 additional Runesmithing Enchantments "} c["Can be Runesmithed as though it were all One Handed Melee Weapon Types"]={nil,"Can be Runesmithed as though it were all One Handed Melee Weapon Types "} @@ -8047,6 +8048,7 @@ c["Cannot deal non-Cold Damage"]={{[1]={flags=0,keywordFlags=0,name="DealNoPhysi c["Cannot deal non-Fire Damage"]={{[1]={flags=0,keywordFlags=0,name="DealNoPhysical",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="DealNoLightning",type="FLAG",value=true},[3]={flags=0,keywordFlags=0,name="DealNoCold",type="FLAG",value=true},[4]={flags=0,keywordFlags=0,name="DealNoChaos",type="FLAG",value=true}},nil} c["Cannot deal non-Lightning Damage"]={{[1]={flags=0,keywordFlags=0,name="DealNoPhysical",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="DealNoCold",type="FLAG",value=true},[3]={flags=0,keywordFlags=0,name="DealNoFire",type="FLAG",value=true},[4]={flags=0,keywordFlags=0,name="DealNoChaos",type="FLAG",value=true}},nil} c["Cannot gain Energy Shield"]={{[1]={flags=0,keywordFlags=0,name="CannotGainEnergyShield",type="FLAG",value=true}},nil} +c["Cannot gain Intangibility"]={{},nil} c["Cannot gain Life during effect"]={{[1]={[1]={type="Condition",var="UsingFlask"},flags=0,keywordFlags=0,name="CannotGainLife",type="FLAG",value=true}},nil} c["Cannot gain Mana during effect"]={{[1]={[1]={type="Condition",var="UsingFlask"},flags=0,keywordFlags=0,name="CannotGainMana",type="FLAG",value=true}},nil} c["Cannot gain Power Charges"]={nil,"Cannot gain Power Charges "} diff --git a/src/Data/Skills/other.lua b/src/Data/Skills/other.lua index 04dfd1f7d4..33c503b2af 100644 --- a/src/Data/Skills/other.lua +++ b/src/Data/Skills/other.lua @@ -5878,6 +5878,76 @@ skills["TriggerSummonedAncestorUtula"] = { [20] = { 10, 50, cooldown = 20, levelRequirement = 0, storedUses = 1, statInterpolation = { 1, 1, }, }, }, } +skills["GhostCannons"] = { + name = "Ghostly Artillery", + hidden = true, + color = 2, + description = "Manifest a row of Ghostly Cannons, each of which then fires after a short delay dealing the damage of the weapon that triggered it. For each additional projectile this skill would fire, create an additional Ghostly Cannon instead.", + skillTypes = { [SkillType.Attack] = true, [SkillType.RangedAttack] = true, [SkillType.Projectile] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.Fire] = true, [SkillType.ProjectilesNotFromUser] = true, [SkillType.Area] = true, [SkillType.Cooldown] = true, }, + statDescriptionScope = "skill_stat_descriptions", + castTime = 1, + fromItem = true, + baseFlags = { + attack = true, + projectile = true, + area = true, + }, + constantStats = { + { "skill_physical_damage_%_to_convert_to_fire", 50 }, + { "active_skill_additional_projectiles_fire_parallel_x_dist", 60 }, + { "base_number_of_projectiles", 1 }, + { "active_skill_base_area_of_effect_radius", 2 }, + { "triggered_ghost_cannons_base_number_of_cannons_to_fire", 6 }, + }, + stats = { + "base_is_projectile", + "skill_has_trigger_from_unique_item", + "modifiers_to_number_of_projectiles_instead_apply_to_number_of_summoned_ghost_cannons", + "is_area_damage", + }, + levels = { + [1] = { cooldown = 0.15, levelRequirement = 12, storedUses = 1, }, + [2] = { baseMultiplier = 1.032, cooldown = 0.15, damageEffectiveness = 1.032, levelRequirement = 15, storedUses = 1, }, + [3] = { baseMultiplier = 1.063, cooldown = 0.15, damageEffectiveness = 1.063, levelRequirement = 19, storedUses = 1, }, + [4] = { baseMultiplier = 1.095, cooldown = 0.15, damageEffectiveness = 1.095, levelRequirement = 23, storedUses = 1, }, + [5] = { baseMultiplier = 1.126, cooldown = 0.15, damageEffectiveness = 1.126, levelRequirement = 27, storedUses = 1, }, + [6] = { baseMultiplier = 1.158, cooldown = 0.15, damageEffectiveness = 1.158, levelRequirement = 31, storedUses = 1, }, + [7] = { baseMultiplier = 1.189, cooldown = 0.15, damageEffectiveness = 1.189, levelRequirement = 35, storedUses = 1, }, + [8] = { baseMultiplier = 1.221, cooldown = 0.15, damageEffectiveness = 1.221, levelRequirement = 38, storedUses = 1, }, + [9] = { baseMultiplier = 1.253, cooldown = 0.15, damageEffectiveness = 1.253, levelRequirement = 41, storedUses = 1, }, + [10] = { baseMultiplier = 1.284, cooldown = 0.15, damageEffectiveness = 1.284, levelRequirement = 44, storedUses = 1, }, + [11] = { baseMultiplier = 1.316, cooldown = 0.15, damageEffectiveness = 1.316, levelRequirement = 47, storedUses = 1, }, + [12] = { baseMultiplier = 1.347, cooldown = 0.15, damageEffectiveness = 1.347, levelRequirement = 50, storedUses = 1, }, + [13] = { baseMultiplier = 1.379, cooldown = 0.15, damageEffectiveness = 1.379, levelRequirement = 53, storedUses = 1, }, + [14] = { baseMultiplier = 1.411, cooldown = 0.15, damageEffectiveness = 1.411, levelRequirement = 56, storedUses = 1, }, + [15] = { baseMultiplier = 1.442, cooldown = 0.15, damageEffectiveness = 1.442, levelRequirement = 59, storedUses = 1, }, + [16] = { baseMultiplier = 1.474, cooldown = 0.15, damageEffectiveness = 1.474, levelRequirement = 62, storedUses = 1, }, + [17] = { baseMultiplier = 1.505, cooldown = 0.15, damageEffectiveness = 1.505, levelRequirement = 64, storedUses = 1, }, + [18] = { baseMultiplier = 1.537, cooldown = 0.15, damageEffectiveness = 1.537, levelRequirement = 66, storedUses = 1, }, + [19] = { baseMultiplier = 1.568, cooldown = 0.15, damageEffectiveness = 1.568, levelRequirement = 68, storedUses = 1, }, + [20] = { baseMultiplier = 1.6, cooldown = 0.15, damageEffectiveness = 1.6, levelRequirement = 70, storedUses = 1, }, + [21] = { baseMultiplier = 1.632, cooldown = 0.15, damageEffectiveness = 1.632, levelRequirement = 72, storedUses = 1, }, + [22] = { baseMultiplier = 1.663, cooldown = 0.15, damageEffectiveness = 1.663, levelRequirement = 74, storedUses = 1, }, + [23] = { baseMultiplier = 1.695, cooldown = 0.15, damageEffectiveness = 1.695, levelRequirement = 76, storedUses = 1, }, + [24] = { baseMultiplier = 1.726, cooldown = 0.15, damageEffectiveness = 1.726, levelRequirement = 78, storedUses = 1, }, + [25] = { baseMultiplier = 1.758, cooldown = 0.15, damageEffectiveness = 1.758, levelRequirement = 80, storedUses = 1, }, + [26] = { baseMultiplier = 1.789, cooldown = 0.15, damageEffectiveness = 1.789, levelRequirement = 82, storedUses = 1, }, + [27] = { baseMultiplier = 1.821, cooldown = 0.15, damageEffectiveness = 1.821, levelRequirement = 84, storedUses = 1, }, + [28] = { baseMultiplier = 1.853, cooldown = 0.15, damageEffectiveness = 1.853, levelRequirement = 86, storedUses = 1, }, + [29] = { baseMultiplier = 1.884, cooldown = 0.15, damageEffectiveness = 1.884, levelRequirement = 88, storedUses = 1, }, + [30] = { baseMultiplier = 1.916, cooldown = 0.15, damageEffectiveness = 1.916, levelRequirement = 90, storedUses = 1, }, + [31] = { baseMultiplier = 1.932, cooldown = 0.15, damageEffectiveness = 1.932, levelRequirement = 91, storedUses = 1, }, + [32] = { baseMultiplier = 1.947, cooldown = 0.15, damageEffectiveness = 1.947, levelRequirement = 92, storedUses = 1, }, + [33] = { baseMultiplier = 1.963, cooldown = 0.15, damageEffectiveness = 1.963, levelRequirement = 93, storedUses = 1, }, + [34] = { baseMultiplier = 1.979, cooldown = 0.15, damageEffectiveness = 1.979, levelRequirement = 94, storedUses = 1, }, + [35] = { baseMultiplier = 1.995, cooldown = 0.15, damageEffectiveness = 1.995, levelRequirement = 95, storedUses = 1, }, + [36] = { baseMultiplier = 2.011, cooldown = 0.15, damageEffectiveness = 2.011, levelRequirement = 96, storedUses = 1, }, + [37] = { baseMultiplier = 2.026, cooldown = 0.15, damageEffectiveness = 2.026, levelRequirement = 97, storedUses = 1, }, + [38] = { baseMultiplier = 2.042, cooldown = 0.15, damageEffectiveness = 2.042, levelRequirement = 98, storedUses = 1, }, + [39] = { baseMultiplier = 2.058, cooldown = 0.15, damageEffectiveness = 2.058, levelRequirement = 99, storedUses = 1, }, + [40] = { baseMultiplier = 2.074, cooldown = 0.15, damageEffectiveness = 2.074, levelRequirement = 100, storedUses = 1, }, + }, +} skills["SupportUniqueCastCurseOnCurse"] = { name = "Vixen's Entrapment", hidden = true, diff --git a/src/Data/Uniques/sword.lua b/src/Data/Uniques/sword.lua index 725e345a06..70edcaf0b4 100644 --- a/src/Data/Uniques/sword.lua +++ b/src/Data/Uniques/sword.lua @@ -28,6 +28,15 @@ Implicits: 1 Skills fire 2 additional Projectiles if you've used a Movement Skill Recently Far Shot ]],[[ +Dread Captain's Cutlass +Ghostflame Blade +Source: Drops from unique{Captainsbane} in normal{The Fathomless Depths} +League: Allflame +Crafted: true +Implicits: 2 +Can be Allflame Crafted as if Rare +Cannot gain Intangibility +]],[[ Dreamfeather Eternal Sword Variant: Pre 2.0.0 @@ -804,6 +813,18 @@ Socketed Melee Gems have 15% increased Area of Effect {variant:4,5}Attack Skills gain 5% of Physical Damage as Extra Fire Damage per Socketed Red Gem {variant:4,5}You have Vaal Pact while all Socketed Gems are Red ]],[[ +Echoforge +Infernal Sword +Source: Drops from unique{The Maven} +Implicits: 1 +30% increased Chaos Damage +Adds (600-650) to (750-800) Chaos Damage +(-16-16)% reduced Attack Speed ++(-200-200) to maximum Life +Your Chaos Damage can Shock +(-40-40)% reduced Area of Effect for Attacks +Deal no Physical or Elemental Damage +]],[[ Edge of Madness Etched Greatsword League: Beyond @@ -824,6 +845,20 @@ Adds (60-68) to (90-102) Chaos Damage {variant:1,2,4}1% increased Chaos Damage per Level {variant:2,3,4}Adds 1 to 2 Physical Damage to Attacks per Level ]],[[ +The Golden Charlatan +Lion Sword +Source: Drops from unique{Uber Incarnation of Dread} in normal{Moment of Reverence} +Requires Level 65, 104 Str, 122 Dex +Implicits: 1 ++50 to Strength and Dexterity ++200 Intelligence Requirement +(200-300)% increased Physical Damage +(10-16)% increased Attack Speed +(100-200)% increased Critical Strike Chance +Critical Strikes with this Weapon do not deal extra Damage +Gain a random Shrine Buff for 30 seconds when you Kill a Rare or Unique Enemy ++1 to maximum Mana per 2 Intelligence +]],[[ Hiltless Reaver Sword Variant: Pre 2.6.0 @@ -893,18 +928,6 @@ Adds (385-440) to (490-545) Cold Damage {variant:2}Culling Strike against Frozen Enemies Gain an Endurance Charge if an Attack Freezes an Enemy ]],[[ -Echoforge -Infernal Sword -Source: Drops from unique{The Maven} -Implicits: 1 -30% increased Chaos Damage -Adds (600-650) to (750-800) Chaos Damage -(-16-16)% reduced Attack Speed -+(-200-200) to maximum Life -Your Chaos Damage can Shock -(-40-40)% reduced Area of Effect for Attacks -Deal no Physical or Elemental Damage -]],[[ Queen's Decree Ornate Sword Variant: Pre 2.6.0 @@ -1048,20 +1071,5 @@ Your Elemental Damage can Shock {variant:3}Gain 700% of Weapon Physical Damage as Extra Damage of a random Element 20% increased Area of Effect for Attacks Deal no Non-Elemental Damage -]], -[[ -The Golden Charlatan -Lion Sword -Source: Drops from unique{Uber Incarnation of Dread} in normal{Moment of Reverence} -Requires Level 65, 104 Str, 122 Dex -Implicits: 1 -+50 to Strength and Dexterity -+200 Intelligence Requirement -(200-300)% increased Physical Damage -(10-16)% increased Attack Speed -(100-200)% increased Critical Strike Chance -Critical Strikes with this Weapon do not deal extra Damage -Gain a random Shrine Buff for 30 seconds when you Kill a Rare or Unique Enemy -+1 to maximum Mana per 2 Intelligence -]], +]] } diff --git a/src/Export/Skills/other.txt b/src/Export/Skills/other.txt index bcdd1766c9..ab103e85e7 100644 --- a/src/Export/Skills/other.txt +++ b/src/Export/Skills/other.txt @@ -151,9 +151,6 @@ local skills, mod, flag, skill = ... skill("hitTimeOverride", nil), div = 1000, }, - ["spider_aspect_max_web_count"] = { - mod("Multiplier:SpiderWebApplyStackMax", "BASE", nil), - }, }, #mods @@ -1513,6 +1510,11 @@ local skills, mod, flag, skill = ... }, #mods +#skill GhostCannons Ghostly Artillery +#flags attack projectile area + fromItem = true, +#mods + #skill SupportUniqueCastCurseOnCurse Vixen's Entrapment #flags spell curse fromItem = true, diff --git a/src/Export/Uniques/sword.lua b/src/Export/Uniques/sword.lua index fb10166572..d2ab4d412f 100644 --- a/src/Export/Uniques/sword.lua +++ b/src/Export/Uniques/sword.lua @@ -28,6 +28,14 @@ EvasionRatingWhileMovingUnique__1 NumberOfProjectilesIfUsedAMovementSkillRecentlyUnique__1 PlayerFarShotUnique__2 ]],[[ +Dread Captain's Cutlass +Ghostflame Blade +Source: Drops from unique{Captainsbane} in normal{The Fathomless Depths} +League: Allflame +Crafted: true +Implicits: 1 +CanBeGhostflameCraftedAsThoughRare +]],[[ Dreamfeather Eternal Sword Variant: Pre 2.0.0 @@ -795,6 +803,18 @@ LocalIncreasedAttackSpeedUniqueTwoHandSword8 {variant:4,5}AttackSkillsHavePhysToExtraFireDamagePerSocketedRedGemUniqueTwoHandSword8 {variant:4,5}VaalPactIfAllSocketedGemsAreRedUniqueTwoHandSword8 ]],[[ +Echoforge +Infernal Sword +Source: Drops from unique{The Maven} +Implicits: 1 +IncreasedChaosDamageImplicitUnique__1 +LocalAddedChaosDamageUnique__3 +LocalIncreasedAttackSpeedUnique__38 +IncreasedLifeUnique__117 +ChaosDamageCanShockUnique__1 +IncreasedAttackAreaOfEffectUnique__3 +DealNoElementalPhysicalDamageUnique__1 +]],[[ Edge of Madness Etched Greatsword League: Beyond @@ -815,6 +835,20 @@ LocalChaosDamageUniqueTwoHandSword7 {variant:1,2,4}IncreasedElementalDamagePerLevelUniqueTwoHandSword7 {variant:2,3,4}PhysicalDamageToAttacksPerLevelUnique__1_ ]],[[ +The Golden Charlatan +Lion Sword +Source: Drops from unique{Uber Incarnation of Dread} in normal{Moment of Reverence} +Requires Level 65, 104 Str, 122 Dex +Implicits: 1 +StrengthDexterityImplicitSword_1 +IntelligenceRequirementsUnique_1 +LocalIncreasedPhysicalDamagePercentUnique__53 +IncreasedAttackSpeedUniqueGlovesDemigods1 +LocalCriticalStrikeChanceUnique__24 +LocalNoCriticalStrikeMultiplierUnique_1 +GainShrineOnRareOrUniqueKillUnique_1 +GainManaPer2IntelligenceUnique_1 +]],[[ Hiltless Reaver Sword Variant: Pre 2.6.0 @@ -884,18 +918,6 @@ ColdDamageTakenUnique__2 {variant:2}CullingAgainstFrozenEnemiesUnique__1 EnduranceChargeIfAttackFreezesUnique__1 ]],[[ -Echoforge -Infernal Sword -Source: Drops from unique{The Maven} -Implicits: 1 -IncreasedChaosDamageImplicitUnique__1 -LocalAddedChaosDamageUnique__3 -LocalIncreasedAttackSpeedUnique__38 -IncreasedLifeUnique__117 -ChaosDamageCanShockUnique__1 -IncreasedAttackAreaOfEffectUnique__3 -DealNoElementalPhysicalDamageUnique__1 -]],[[ Queen's Decree Ornate Sword Variant: Pre 2.6.0 @@ -1033,20 +1055,5 @@ ElementalDamageCanShockUnique__1__ {variant:3}WeaponPhysicalDamageAddedAsRandomElementUnique__1__ IncreasedAttackAreaOfEffectUnique__1_ DealNoNonElementalDamageUnique__1 -]], -[[ -The Golden Charlatan -Lion Sword -Source: Drops from unique{Uber Incarnation of Dread} in normal{Moment of Reverence} -Requires Level 65, 104 Str, 122 Dex -Implicits: 1 -StrengthDexterityImplicitSword_1 -IntelligenceRequirementsUnique_1 -LocalIncreasedPhysicalDamagePercentUnique__53 -IncreasedAttackSpeedUniqueGlovesDemigods1 -LocalCriticalStrikeChanceUnique__24 -LocalNoCriticalStrikeMultiplierUnique_1 -GainShrineOnRareOrUniqueKillUnique_1 -GainManaPer2IntelligenceUnique_1 -]], +]] } diff --git a/src/Modules/CalcActiveSkill.lua b/src/Modules/CalcActiveSkill.lua index 543bc6c616..1ff52a4b8b 100644 --- a/src/Modules/CalcActiveSkill.lua +++ b/src/Modules/CalcActiveSkill.lua @@ -278,13 +278,22 @@ function calcs.buildActiveSkillModList(env, activeSkill) activeSkill.weapon2Flags = 0 else -- Set weapon flags + if skillFlags.attack and activeSkill.socketGroup and activeSkill.socketGroup.sourceItem then + -- Item-granted attacks use the weapon that grants the skill + local sourceSlot = activeSkill.socketGroup.slot or "" + skillFlags.forceMainHand = sourceSlot:match("^Weapon 1") ~= nil + skillFlags.forceOffHand = sourceSlot:match("^Weapon 2") ~= nil + end local weaponTypes = { activeGrantedEffect.weaponTypes } for _, skillEffect in pairs(activeSkill.effectList) do if skillEffect.grantedEffect.support and skillEffect.grantedEffect.weaponTypes then t_insert(weaponTypes, skillEffect.grantedEffect.weaponTypes) end end - local weapon1Flags, weapon1Info = getWeaponFlags(env, activeSkill.actor.weaponData1, weaponTypes) + local weapon1Flags, weapon1Info + if not skillFlags.forceOffHand then + weapon1Flags, weapon1Info = getWeaponFlags(env, activeSkill.actor.weaponData1, weaponTypes) + end if not weapon1Flags and activeSkill.summonSkill then -- Minion skills seem to ignore weapon types weapon1Flags, weapon1Info = ModFlag[env.data.weaponTypeInfo["None"].flag], env.data.weaponTypeInfo["None"] diff --git a/src/Modules/CalcTriggers.lua b/src/Modules/CalcTriggers.lua index 46c61a5918..ae3b4f3201 100644 --- a/src/Modules/CalcTriggers.lua +++ b/src/Modules/CalcTriggers.lua @@ -395,11 +395,14 @@ local function defaultTriggerHandler(env, config) local triggeredSkills = config.triggeredSkills or {} local trigRate = config.trigRate local uuid + local sourceWeapon = config.sourceWeapon + -- Only attacks using the granting weapon can activate source-weapon triggers + local sourceWeaponFlag = sourceWeapon and actor.mainSkill.socketGroup.slot and (actor.mainSkill.socketGroup.slot:match("^Weapon 2") and "weapon2Attack" or "weapon1Attack") -- Find trigger skill and triggered skills if config.triggeredSkillCond or config.triggerSkillCond then for _, skill in ipairs(env.player.activeSkillList) do - if config.triggerSkillCond and config.triggerSkillCond(env, skill) and (not isTriggered(skill) or actor.mainSkill.skillFlags.globalTrigger or config.allowTriggered) and skill ~= actor.mainSkill then + if config.triggerSkillCond and config.triggerSkillCond(env, skill) and (not sourceWeaponFlag or skill.skillFlags[sourceWeaponFlag]) and (not isTriggered(skill) or actor.mainSkill.skillFlags.globalTrigger or config.allowTriggered) and skill ~= actor.mainSkill then source, trigRate, uuid = findTriggerSkill(env, skill, source, trigRate, config.comparer) end if config.triggeredSkillCond and config.triggeredSkillCond(env,skill) then @@ -436,7 +439,9 @@ local function defaultTriggerHandler(env, config) end -- Dual wield triggers - if trigRate and source and env.player.weaponData1.type and env.player.weaponData2.type and not source.skillData.doubleHitsWhenDualWielding and (source.skillTypes[SkillType.Melee] or source.skillTypes[SkillType.Attack]) and actor.mainSkill.triggeredBy and actor.mainSkill.triggeredBy.grantedEffect.support and actor.mainSkill.triggeredBy.grantedEffect.fromItem then + local sourceWeaponTrigger = sourceWeapon and source and source.skillFlags.bothWeaponAttack + local itemSupportTrigger = actor.mainSkill.triggeredBy and actor.mainSkill.triggeredBy.grantedEffect.support and actor.mainSkill.triggeredBy.grantedEffect.fromItem + if trigRate and source and env.player.weaponData1.type and env.player.weaponData2.type and not source.skillData.doubleHitsWhenDualWielding and (source.skillTypes[SkillType.Melee] or source.skillTypes[SkillType.Attack]) and (sourceWeaponTrigger or itemSupportTrigger) then trigRate = trigRate / 2 if breakdown then t_insert(breakdown.EffectiveSourceRate, 2, s_format("/ 2 ^8(due to dual wielding)")) @@ -1522,6 +1527,15 @@ local configTable = { return skill.skillData.triggeredBySettlersEnchantTrigger and slotMatch(env, skill) end} end, + ["ghostly artillery"] = function(env) + return { + sourceWeapon = true, + triggerOnUse = true, + triggerSkillCond = function(env, skill) + return skill.skillTypes[SkillType.Attack] + end + } + end, ["replica gifts from above"] = function() return { triggerSkillCond = function(env, skill) @@ -1553,7 +1567,7 @@ local configTable = { return {triggerSkillCond = function(env, skill) return (skill.skillTypes[SkillType.Melee] or skill.skillTypes[SkillType.Attack]) end} end, ["FieryImpactHeistMaceImplicit"] = function(env) - return {triggerSkillCond = function(env, skill) return (skill.skillTypes[SkillType.Melee] or skill.skillTypes[SkillType.Attack]) end} + return {sourceWeapon = true, triggerSkillCond = function(env, skill) return (skill.skillTypes[SkillType.Melee] or skill.skillTypes[SkillType.Attack]) end} end, } diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index 42360b0ab6..fd4fa2e137 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -1148,14 +1148,80 @@ data.printMissingMinionSkills = function() end end --- Item bases +---@class ItemBase +---@field type string # e.g. "Helmet", "Wand", "Body Armour", "Flask", "Jewel" +---@field subType? string # e.g. "Armour", "Evasion/Energy Shield", "Life", "Utility" +---@field socketLimit? integer # max sockets (weapons/armour only) +---@field hidden? boolean # excluded from the base-type selection lists +---@field cannotBeAnointed? boolean +---@field tags table # e.g. { armour = true, helmet = true, str_armour = true } +---@field influenceTags? table # influence -> mod tag, e.g. { shaper = "helmet_shaper" } +---@field implicit? string # implicit mod line(s), newline-separated +---@field implicitModTypes ModTypeList[] # per-implicit list of mod-type tags +---@field implicitIds? string[] # per-implicit GGG mod id +---@field enchant? string # enchant mod line(s) +---@field enchantModTypes? ModTypeList[] +---@field enchantIds? string[] +---@field flavourText? string +---@field req ItemBaseReq +---@field armour? ItemBaseArmour # present on armour bases +---@field weapon? ItemBaseWeapon # present on weapon bases +---@field flask? ItemBaseFlask # present on flask bases +---@field tincture? ItemBaseTincture # present on tincture bases + +---@alias ModTypeList string[] # list of mod-type tags, e.g. { "caster_damage", "damage", "caster" } + +---@class ItemBaseReq +---@field level? integer +---@field str? integer +---@field dex? integer +---@field int? integer + +---@class ItemBaseArmour +---@field ArmourBaseMin? number +---@field ArmourBaseMax? number +---@field EvasionBaseMin? number +---@field EvasionBaseMax? number +---@field EnergyShieldBaseMin? number +---@field EnergyShieldBaseMax? number +---@field WardBaseMin? number +---@field WardBaseMax? number +---@field BlockChance? number # shields +---@field MovementPenalty? number + +---@class ItemBaseWeapon +---@field PhysicalMin? number +---@field PhysicalMax? number +---@field CritChanceBase? number +---@field AttackRateBase? number +---@field Range? number + +---@class ItemBaseFlask +---@field life? number +---@field mana? number +---@field duration? number +---@field chargesUsed? integer +---@field chargesMax? integer +---@field buff? string[] # utility-flask granted buff line(s) + +---@class ItemBaseTincture +---@field manaBurn? number +---@field cooldown? number + +---@type table data.itemBases = { } for _, type in pairs(itemTypes) do LoadModule("Data/Bases/"..type, data.itemBases) end +---@class ItemBaseEntry +---@field label string +---@field name string +---@field base ItemBase + +---@type table -- Build lists of item bases, separated by type -data.itemBaseLists = { } +data.itemBaseLists = {} for name, base in pairs(data.itemBases) do if not base.hidden then local type = base.type @@ -1241,13 +1307,17 @@ for modId, mod in pairs(data.veiledMods) do end end +local dreadCaptainBase = { base = copyTable(data.itemBases["Ghostflame Blade"]) } +dreadCaptainBase.base.tags.deepwater_sword = true + ---@class RareLikeUniqueDescription ---@field affixes table ----@field validBases table[]? Bases used to check modifier spawn tags instead of the item's base +---@field validBases ItemBaseEntry[]? Bases used to check modifier spawn tags instead of the item's base ---@field prefixLimit integer ---@field suffixLimit integer ---@field ignoreModType boolean? ----@field allowDuplicateGroups boolean? +---@field allowDuplicateGroups boolean? Whether the same modifier can appear multiple times on the item. +---@field supportsCustomModifiers table? A table which describes which mod source IDs are applicable in the custom modifier menu. ---@type table -- Uniques which use the existing rare item crafting controls. data.rareLikeUniques = { @@ -1263,6 +1333,17 @@ data.rareLikeUniques = { affixes = crimsonStormMods, prefixLimit = 0, suffixLimit = 1, + }, + ["dread captain's cutlass"] = { + validBases = { dreadCaptainBase }, + affixes = data.itemMods.Explicit, + prefixLimit = 3, + suffixLimit = 3, + supportsCustomModifiers = { + ESSENCE = true, + VEILED = true, + CUSTOM = true, + }, } } -- Uniques (loaded after version-specific data because reasons) diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index ca197b1008..ef48ad519c 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -3094,6 +3094,12 @@ local specialModList = { ["reflects your o[tp][hp][eo][rs]i?t?e? ring"] = { -- Display only. For Kalandra's Touch. }, + ["can be allflame crafted as if rare"] = { + -- Display only. For Dread Captain's Cutlass and supported by crafting UI + }, + ["cannot gain intangibility"] = { + -- Display only. For Dread Captain's Cutlass + }, ["has (%d+) sockets?"] = function(num) return { mod("SocketCount", "BASE", num) } end, ["has (%d+) abyssal sockets?"] = function(num) return { mod("AbyssalSocketCount", "BASE", num) } end, ["no physical damage"] = { mod("WeaponData", "LIST", { key = "PhysicalMin" }), mod("WeaponData", "LIST", { key = "PhysicalMax" }), mod("WeaponData", "LIST", { key = "PhysicalDPS" }) }, @@ -3281,6 +3287,7 @@ local specialModList = { ["trigger level (%d+) (.+) when you use a skill while you have a spirit charge"] = function(num, _, skill) return triggerExtraSkill(skill, num) end, ["trigger level (%d+) (.+) when you hit an enemy while cursed"] = function(num, _, skill) return triggerExtraSkill(skill, num) end, ["trigger level (%d+) (.+) when you hit a bleeding enemy"] = function(num, _, skill) return triggerExtraSkill(skill, num) end, + ["trigger level (%d+) (.+) when you attack with this weapon"] = function(num, _, skill) return triggerExtraSkill(skill, num) end, ["trigger level (%d+) (.+) when you hit a rare or unique enemy"] = function(num, _, skill) return triggerExtraSkill(skill, num) end, ["trigger level (%d+) (.+) when you hit a rare or unique enemy and have no mark"] = function(num, _, skill) return triggerExtraSkill(skill, num) end, ["trigger level (%d+) (.+) when you hit a frozen enemy"] = function(num, _, skill) return triggerExtraSkill(skill, num) end,