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..2ef23a5729 100644 --- a/src/Data/Skills/other.lua +++ b/src/Data/Skills/other.lua @@ -398,6 +398,9 @@ skills["BarnacleSnap"] = { skill("hitTimeOverride", nil), div = 1000, }, + ["spider_aspect_max_web_count"] = { + mod("Multiplier:SpiderWebApplyStackMax", "BASE", nil), + }, }, baseFlags = { attack = true, @@ -5878,6 +5881,74 @@ 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, + }, + 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..e7c882524d 100644 --- a/src/Data/Uniques/sword.lua +++ b/src/Data/Uniques/sword.lua @@ -1049,7 +1049,7 @@ Your Elemental Damage can Shock 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} @@ -1063,5 +1063,14 @@ Implicits: 1 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 -]], +]], [[ +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 +]] } diff --git a/src/Export/Skills/other.txt b/src/Export/Skills/other.txt index bcdd1766c9..56510ea6d5 100644 --- a/src/Export/Skills/other.txt +++ b/src/Export/Skills/other.txt @@ -1513,6 +1513,11 @@ local skills, mod, flag, skill = ... }, #mods +#skill GhostCannons Ghostly Artillery +#flags attack + 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..7cad56f573 100644 --- a/src/Export/Uniques/sword.lua +++ b/src/Export/Uniques/sword.lua @@ -1034,7 +1034,7 @@ ElementalDamageCanShockUnique__1__ IncreasedAttackAreaOfEffectUnique__1_ DealNoNonElementalDamageUnique__1 ]], -[[ + [[ The Golden Charlatan Lion Sword Source: Drops from unique{Uber Incarnation of Dread} in normal{Moment of Reverence} @@ -1048,5 +1048,13 @@ LocalCriticalStrikeChanceUnique__24 LocalNoCriticalStrikeMultiplierUnique_1 GainShrineOnRareOrUniqueKillUnique_1 GainManaPer2IntelligenceUnique_1 -]], +]], [[ +Dread Captain's Cutlass +Ghostflame Blade +Source: Drops from unique{Captainsbane} in normal{The Fathomless Depths} +League: Allflame +Crafted: true +Implicits: 1 +CanBeGhostflameCraftedAsThoughRare +]] } diff --git a/src/Modules/CalcTriggers.lua b/src/Modules/CalcTriggers.lua index 46c61a5918..4789587b33 100644 --- a/src/Modules/CalcTriggers.lua +++ b/src/Modules/CalcTriggers.lua @@ -1522,6 +1522,17 @@ local configTable = { return skill.skillData.triggeredBySettlersEnchantTrigger and slotMatch(env, skill) end} end, + ["ghostly artillery"] = function(env) + -- Skill is triggered only when the weapon with the enchant on it hits + return { + triggerSkillCond = function(env, skill) + return skill.skillTypes[SkillType.Melee] + end, + triggeredSkillCond = function(env, skill) + return slotMatch(env, skill) + end + } + end, ["replica gifts from above"] = function() return { triggerSkillCond = function(env, skill) 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,