Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions spec/System/TestFacebreaker_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
-- Tests for Facebreaker-style gloves: empty-handed gloves that grant their own base
-- weapon damage and let you attack as though using a One Hand Mace.
describe("TestFacebreaker", function()
before_each(function()
newBuild()
end)

teardown(function()
-- newBuild() takes care of resetting everything in setup()
end)

-- Physical variant (Facebreaker)
local function equipFacebreaker()
build.itemsTab:CreateDisplayItemFromRaw([[
New Item
Stocky Mitts
Has 8 to 12 Physical damage, +3 to +4 per Boss's Face Broken
Can Attack as though using a One Handed Mace while both of your hand slots are empty
Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage
]])
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
end

it("grants its base Physical damage to Unarmed attacks", function()
equipFacebreaker()
local modDB = build.calcsTab.mainEnv.player.modDB
assert.are.equals(8, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "PhysicalMin"))
assert.are.equals(12, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "PhysicalMax"))
end)

it("lets you attack as though using a One Hand Mace while unarmed", function()
equipFacebreaker()
local weaponData1 = build.calcsTab.mainEnv.player.weaponData1
assert.is_true(weaponData1.asThoughUsing ~= nil and weaponData1.asThoughUsing["One Hand Mace"] == true)
end)

it("scales its base damage per Boss's Face Broken", function()
equipFacebreaker()
build.configTab.input.configBossFaceBroken = 10
build.configTab:BuildModList()
runCallback("OnFrame")
local modDB = build.calcsTab.mainEnv.player.modDB
-- 8 base + 3 per face broken * 10, 12 base + 4 per face broken * 10
assert.are.equals(8 + 3 * 10, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "PhysicalMin"))
assert.are.equals(12 + 4 * 10, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "PhysicalMax"))
end)

it("matches the in-game resolved damage at 60 Boss's Faces Broken (188-252)", function()
-- Real in-game Facebreaker shows "Physical Damage: 188-252" at 60 Boss's Faces Broken
equipFacebreaker()
build.configTab.input.configBossFaceBroken = 60
build.configTab:BuildModList()
runCallback("OnFrame")
local modDB = build.calcsTab.mainEnv.player.modDB
assert.are.equals(188, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "PhysicalMin"))
assert.are.equals(252, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "PhysicalMax"))
end)

it("makes One Hand Mace skills usable unarmed and applies 'more Unarmed Damage per Strength' to them", function()
build.itemsTab:CreateDisplayItemFromRaw([[
New Item
Stocky Mitts
Has 8 to 12 Physical damage, +3 to +4 per Boss's Face Broken
1% more Unarmed Damage per 5 Strength
Can Attack as though using a One Handed Mace while both of your hand slots are empty
Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage
]])
build.itemsTab:AddDisplayItem()
-- strip enemy Armour so the small base damage still resolves to a positive hit
build.configTab.input.customMods = "Nearby Enemies have 100% less Armour"
build.configTab:BuildModList()
runCallback("OnFrame")
-- Boneshatter requires a One/Two Hand Mace (no "None"): only usable unarmed thanks to Facebreaker
build.skillsTab:PasteSocketGroup("Boneshatter 1/0 1")
runCallback("OnFrame")
build.calcsTab:BuildOutput()
runCallback("OnFrame")
local mainSkill = build.calcsTab.mainEnv.player.mainSkill
assert.is_truthy(mainSkill)
-- the Mace-only skill resolves to a real (unarmed) attack producing a positive hit
assert.are.equals("Boneshatter", mainSkill.activeEffect.grantedEffect.name)
assert.is_truthy(build.calcsTab.mainOutput.MainHand)
assert.is_true(build.calcsTab.mainOutput.MainHand.AverageHit > 0)
local modDB = build.calcsTab.mainEnv.player.modDB
-- 'more Unarmed Damage per 5 Strength' applies to unarmed Hits (which is what Facebreaker mace attacks are)...
assert.is_true(modDB:Sum("MORE", { flags = ModFlag.Unarmed + ModFlag.Hit }, "Damage") > 0)
-- ...but not to actual weapon (e.g. Sword) Hits
assert.are.equals(0, modDB:Sum("MORE", { flags = ModFlag.Sword + ModFlag.Hit }, "Damage"))
end)

it("auto-imports the Boss's Faces Broken count from character quest stats", function()
build.importTab:ImportQuestRewardConfig({ "57 [BrokenFace|Broken Boss Faces]" })
assert.are.equals(57, build.configTab.input.configBossFaceBroken)
end)

it("supports the Fire damage variant", function()
build.itemsTab:CreateDisplayItemFromRaw([[
New Item
Stocky Mitts
Has 9 to 14 Fire damage, +3 to +5 per Boss's Face Broken
Can Attack as though using a One Handed Mace while both of your hand slots are empty
Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage
]])
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
local modDB = build.calcsTab.mainEnv.player.modDB
assert.are.equals(9, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "FireMin"))
assert.are.equals(14, modDB:Sum("BASE", { flags = ModFlag.Unarmed }, "FireMax"))
end)
end)
12 changes: 12 additions & 0 deletions src/Classes/ImportTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,18 @@ function ImportTabClass:ImportQuestRewardConfig(questStats)
end
end

-- Facebreaker: auto-fill the "# of Boss's Faces Broken" config from the character's quest stats
for _, stat in ipairs(questStats) do
if stat:lower():find("broken boss face", 1, true) then
local faces = tonumber(stat:match("%d+"))
if faces and configTab.input.configBossFaceBroken ~= faces then
configTab.input.configBossFaceBroken = faces
updated = true
end
break
end
end

if updated then
configTab:BuildModList()
configTab:UpdateControls()
Expand Down
7 changes: 7 additions & 0 deletions src/Data/ModCache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ c["+(9-14)% to Fire Resistance"]={nil,"+(9-14)% to Fire Resistance "}
c["+(9-14)% to Lightning Resistance"]={nil,"+(9-14)% to Lightning Resistance "}
c["+0.15% to Thorns Critical Hit Chance"]={{[1]={flags=32,keywordFlags=0,name="CritChance",type="BASE",value=0.15}},nil}
c["+0.2 metres to Melee Strike Range"]={{[1]={flags=0,keywordFlags=0,name="MeleeWeaponRangeMetre",type="BASE",value=0.2},[2]={flags=0,keywordFlags=0,name="UnarmedRangeMetre",type="BASE",value=0.2}},nil}
c["+0.3 metres to Melee Strike Range while Unarmed"]={{[1]={[1]={type="Condition",var="Unarmed"},flags=0,keywordFlags=0,name="MeleeWeaponRangeMetre",type="BASE",value=0.3},[2]={[1]={type="Condition",var="Unarmed"},flags=0,keywordFlags=0,name="UnarmedRangeMetre",type="BASE",value=0.3}},nil}
c["+0.4 metres to Melee Strike Range if you've dealt a Projectile Attack Hit in the past eight seconds"]={{[1]={[1]={type="Condition",var="HitProjectileRecently"},flags=0,keywordFlags=0,name="MeleeWeaponRangeMetre",type="BASE",value=0.4},[2]={[1]={type="Condition",var="HitProjectileRecently"},flags=0,keywordFlags=0,name="UnarmedRangeMetre",type="BASE",value=0.4}},nil}
c["+0.5 metres to Dodge Roll distance while Surrounded"]={{}," metres to Dodge Roll distance "}
c["+0.5 metres to Dodge Roll distance while Surrounded 10% increased Movement Speed while Surrounded"]={{[1]={[1]={type="Condition",var="Surrounded"},[2]={type="Condition",var="Surrounded"},flags=0,keywordFlags=0,name="MovementSpeed",type="BASE",value=0.5}}," metres to Dodge Roll distance 10% increased "}
Expand All @@ -89,6 +90,7 @@ c["+1 Ring Slot"]={{[1]={flags=0,keywordFlags=0,name="AdditionalRingSlot",type="
c["+1 maximum stacks of Puppet Master"]={{}," maximum stacks of Puppet Master "}
c["+1 metre to Dodge Roll distance"]={{}," metre to Dodge Roll distance "}
c["+1 metre to Dodge Roll distance 50% increased Evasion Rating if you've Dodge Rolled Recently"]={{[1]={[1]={type="Condition",var="DodgeRolledRecently"},flags=0,keywordFlags=0,name="Evasion",type="BASE",value=1}}," metre to Dodge Roll distance 50% increased "}
c["+1 to Armour per Strength"]={{[1]={[1]={stat="Str",type="PerStat"},flags=0,keywordFlags=0,name="Armour",type="BASE",value=1}},nil}
c["+1 to Evasion Rating per 1 Item Armour on Equipped Gloves"]={{[1]={[1]={div=1,stat="ArmourOnGloves",type="PerStat"},flags=0,keywordFlags=0,name="Evasion",type="BASE",value=1}},nil}
c["+1 to Evasion Rating per 1 Item Energy Shield on Equipped Helmet"]={{[1]={[1]={div=1,stat="EnergyShieldOnHelmet",type="PerStat"},flags=0,keywordFlags=0,name="Evasion",type="BASE",value=1}},nil}
c["+1 to Level of all Chaos Skills"]={{[1]={flags=0,keywordFlags=0,name="GemProperty",type="LIST",value={key="level",keyOfScaledMod="value",keyword="chaos",value=1}}},nil}
Expand Down Expand Up @@ -983,6 +985,7 @@ c["1% increased Spirit Reservation Efficiency of Skills per 20 Tribute"]={{[1]={
c["1% increased damage taken per 10 Tribute"]={{[1]={[1]={actor="parent",div=10,stat="Tribute",type="PerStat"},flags=0,keywordFlags=0,name="DamageTaken",type="INC",value=1}},nil}
c["1% increased maximum Darkness per 1% Chaos Resistance"]={{[1]={[1]={div=1,stat="ChaosResist",type="PerStat"},flags=0,keywordFlags=0,name="Darkness",type="INC",value=1}},nil}
c["1% increased maximum Life"]={{[1]={flags=0,keywordFlags=0,name="Life",type="INC",value=1}},nil}
c["1% more Unarmed Damage per 5 Strength"]={{[1]={[1]={div=5,stat="Str",type="PerStat"},flags=16777220,keywordFlags=0,name="Damage",type="MORE",value=1}},nil}
c["1% of Maximum Life Converted to Energy Shield per 20 Tribute"]={{[1]={[1]={actor="parent",div=20,stat="Tribute",type="PerStat"},flags=0,keywordFlags=0,name="LifeConvertToEnergyShield",type="BASE",value=1}},nil}
c["1% of damage taken Recouped as Life per 10 Tribute"]={{[1]={[1]={actor="parent",div=10,stat="Tribute",type="PerStat"},flags=0,keywordFlags=0,name="LifeRecoup",type="BASE",value=1}},nil}
c["1% of damage taken Recouped as Mana per 10 Tribute"]={{[1]={[1]={actor="parent",div=10,stat="Tribute",type="PerStat"},flags=0,keywordFlags=0,name="ManaRecoup",type="BASE",value=1}},nil}
Expand Down Expand Up @@ -3042,6 +3045,7 @@ c["50% increased Spell Damage"]={{[1]={flags=2,keywordFlags=0,name="Damage",type
c["50% increased Spell damage for each 200 total Mana you have Spent Recently"]={{[1]={[1]={div=200,type="Multiplier",var="ManaSpentRecently"},flags=2,keywordFlags=0,name="Damage",type="INC",value=50}},nil}
c["50% increased Spirit"]={{[1]={flags=0,keywordFlags=0,name="Spirit",type="INC",value=50}},nil}
c["50% increased Strength Requirement"]={{[1]={flags=0,keywordFlags=0,name="StrRequirement",type="INC",value=50}},nil}
c["50% increased Stun Buildup"]={{[1]={flags=0,keywordFlags=0,name="EnemyHeavyStunBuildup",type="INC",value=50}},nil}
c["50% increased Stun Threshold while Channelling"]={{[1]={[1]={type="Condition",var="Channelling"},flags=0,keywordFlags=0,name="StunThreshold",type="INC",value=50}},nil}
c["50% increased Surrounded Area of Effect"]={{[1]={flags=0,keywordFlags=0,name="SurroundedArea",type="INC",value=50}},nil}
c["50% increased Thorns damage if you've consumed an Endurance Charge Recently"]={{[1]={[1]={limit=1,type="Multiplier",var="RemovableEnduranceCharge"},flags=32,keywordFlags=0,name="Damage",type="INC",value=50}},nil}
Expand Down Expand Up @@ -4691,6 +4695,7 @@ c["Can Allocate Passive Skills from the Sorceress's starting point"]={nil,"Can A
c["Can Allocate Passive Skills from the Sorceress's starting point Grants 4 Passive Skill Point"]={nil,"Can Allocate Passive Skills from the Sorceress's starting point Grants 4 Passive Skill Point "}
c["Can Allocate Passive Skills from the Warrior's starting point"]={nil,"Can Allocate Passive Skills from the Warrior's starting point "}
c["Can Allocate Passive Skills from the Warrior's starting point Grants 4 Passive Skill Point"]={nil,"Can Allocate Passive Skills from the Warrior's starting point Grants 4 Passive Skill Point "}
c["Can Attack as though using a One Handed Mace while both of your hand slots are empty"]={{[1]={flags=0,keywordFlags=0,name="CanAttackAsOneHandMaceUnarmed",type="FLAG",value=true}},nil}
c["Can Attack as though using a Quarterstaff while both of your hand slots are empty"]={nil,"Can Attack as though using a Quarterstaff while both of your hand slots are empty "}
c["Can Attack as though using a Quarterstaff while both of your hand slots are empty Unarmed Attacks that would use an Equipped Quarterstaff's damage have:"]={nil,"Can Attack as though using a Quarterstaff while both of your hand slots are empty Unarmed Attacks that would use an Equipped Quarterstaff's damage have: "}
c["Can Attack as though using a Quarterstaff while both of your hand slots are empty Unarmed Attacks that would use an Equipped Quarterstaff's damage have: Base Unarmed Physical damage replaced with damage based on their Skill Level"]={nil,"Can Attack as though using a Quarterstaff while both of your hand slots are empty Unarmed Attacks that would use an Equipped Quarterstaff's damage have: Base Unarmed Physical damage replaced with damage based on their Skill Level "}
Expand Down Expand Up @@ -5574,6 +5579,7 @@ c["Has 2 Charm Slots"]={{[1]={flags=0,keywordFlags=0,name="CharmLimit",type="BAS
c["Has 3 Charm Slot"]={{[1]={flags=0,keywordFlags=0,name="CharmLimit",type="BASE",value=3}},nil}
c["Has 3 Charm Slots"]={{[1]={flags=0,keywordFlags=0,name="CharmLimit",type="BASE",value=3}},nil}
c["Has 4 Augment Sockets"]={nil,"Has 4 Augment Sockets "}
c["Has 8 to 12 Physical damage, +3 to +4 per Boss's Face Broken"]={{[1]={flags=16777216,keywordFlags=0,name="PhysicalMin",type="BASE",value=8},[2]={flags=16777216,keywordFlags=0,name="PhysicalMax",type="BASE",value=12},[3]={[1]={type="Multiplier",var="BossFaceBroken"},flags=16777216,keywordFlags=0,name="PhysicalMin",type="BASE",value=3},[4]={[1]={type="Multiplier",var="BossFaceBroken"},flags=16777216,keywordFlags=0,name="PhysicalMax",type="BASE",value=4}},nil}
c["Has no Attribute Requirements"]={{[1]={flags=0,keywordFlags=0,name="NoAttributeRequirements",type="FLAG",value=true}},nil}
c["Hazards have 15% chance to rearm after they are triggered"]={{[1]={[1]={skillType=203,type="SkillType"},flags=0,keywordFlags=0,name="HazardRearmChance",type="BASE",value=15}},nil}
c["Hazards have 5% chance to rearm after they are triggered"]={{[1]={[1]={skillType=203,type="SkillType"},flags=0,keywordFlags=0,name="HazardRearmChance",type="BASE",value=5}},nil}
Expand Down Expand Up @@ -6412,6 +6418,7 @@ c["Trusted Kinship"]={{[1]={flags=0,keywordFlags=0,name="Keystone",type="LIST",v
c["Unaffected by Chill during Dodge Roll"]={nil,"Unaffected by Chill during Dodge Roll "}
c["Unaffected by Chill while Leeching Mana"]={{[1]={[1]={type="Condition",var="LeechingMana"},flags=0,keywordFlags=0,name="SelfChillEffect",type="MORE",value=-100}},nil}
c["Unaffected by Elemental Weakness"]={nil,"Unaffected by Elemental Weakness "}
c["Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage"]={{},nil}
c["Undead Minions have 25% less maximum Life"]={{[1]={[1]={skillType=127,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Life",type="MORE",value=-25}}}},nil}
c["Unique Tamed Beasts are Possessed by random Azmeri Spirits, changing every 20 seconds"]={nil,"Unique Tamed Beasts are Possessed by random Azmeri Spirits, changing every 20 seconds "}
c["Unique Tamed Beasts have 30% increased movement speed"]={nil,"Unique Tamed Beasts have 30% increased movement speed "}
Expand Down
10 changes: 10 additions & 0 deletions src/Data/Uniques/gloves.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ Critical Hits inflict Impale
Critical Hits cannot Extract Impale
(20-31) to (31-49) Physical Thorns damage
]],[[
Facebreaker
Stocky Mitts
Has 8 to 12 Physical damage, +3 to +4 per Boss's Face Broken
(30-50)% increased Stun Buildup
1% more Unarmed Damage per 5 Strength
+0.3 metres to Melee Strike Range while Unarmed
+1 to Armour per Strength
Can Attack as though using a One Handed Mace while both of your hand slots are empty
Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage
]],[[
Hateforge
Moulded Mitts
Source: Drops from unique{Trialmaster} in normal{The Trial of Chaos}
Expand Down
9 changes: 9 additions & 0 deletions src/Export/Uniques/gloves.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ UniqueImpaleOnCriticalHit1
UniqueCriticalsCannotConsumeImpale1
UniqueAttackerTakesDamage8
]],[[
Facebreaker
Stocky Mitts
UniqueBaseDamageOverrideForMaceAttacks1
UniqueStunDamageIncrease1
UniqueUnarmedAttackDamagePerXStrength1
UnarmedStrikeRangeUnique1
UniqueGainArmourEqualToStrength1
UniqueOneHandMaceSkillsUsableUnarmed1
]],[[
Hateforge
Moulded Mitts
Source: Drops from unique{Trialmaster} in normal{The Trial of Chaos}
Expand Down
5 changes: 5 additions & 0 deletions src/Modules/CalcSetup.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1593,6 +1593,11 @@ function calcs.initEnv(build, mode, override, specEnv)
end
end
end
-- Facebreaker: Can Attack as though using a One Handed Mace while both of your hand slots are empty
if (not env.player.itemList["Weapon 1"]) and env.modDB:Flag(nil, "CanAttackAsOneHandMaceUnarmed") then
env.player.weaponData1.asThoughUsing = env.player.weaponData1.asThoughUsing or { }
env.player.weaponData1.asThoughUsing["One Hand Mace"] = true
end
env.player.weaponData2 = env.player.weaponData2 or { }
else
env.player.weaponData2 = env.player.itemList["Weapon 2"].weaponData and env.player.itemList["Weapon 2"].weaponData[2] or { }
Expand Down
3 changes: 3 additions & 0 deletions src/Modules/ConfigOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,9 @@ Huge sets the radius to 11.
{ var = "overrideGhostShrouds", type = "count", label = "# of Ghost Shrouds (if not maximum):", ifOption = "useGhostShrouds", apply = function(val, modList, enemyModList)
modList:NewMod("GhostShrouds", "OVERRIDE", val, "Config", { type = "Condition", var = "Combat" })
end },
{ var = "configBossFaceBroken", type = "count", label = "# of Boss's Faces Broken:", ifMult = "BossFaceBroken", tooltip = "The number of times you have Broken a Boss's Face (e.g. with Facebreaker).\nEach one raises the item's base damage by its 'per Boss's Face Broken' amount.\nTo find it in-game, read the item's Physical Damage line and solve, e.g. for Facebreaker: faces = (min damage - 8) / 3.\nCheck the Physical damage breakdown in the Calcs tab to see the result.", apply = function(val, modList, enemyModList)
modList:NewMod("Multiplier:BossFaceBroken", "BASE", val, "Config")
end },
{ var = "waitForMaxSeals", type = "check", label = "Do you wait for Max Seals?", ifFlag = "HasSeals", apply = function(val, modList, enemyModList)
modList:NewMod("UseMaxUnleash", "FLAG", true, "Config", { type = "Condition", var = "Combat" })
end },
Expand Down
Loading