Newer
Older
if hitted and game.level.data.zero_gravity and rng.percent(util.bound(dam, 0, 100)) then
target:knockback(self.x, self.y, math.ceil(math.log(dam)))
end
-- Roll with it
if hitted and target:attr("knockback_on_hit") and not target.turn_procs.roll_with_it and rng.percent(util.bound(dam, 0, 100)) then
local ox, oy = self.x, self.y
DarkGod
committed
game:onTickEnd(function()
target:knockback(ox, oy, 1)
if not target:hasEffect(target.EFF_WILD_SPEED) then target:setEffect(target.EFF_WILD_SPEED, 1, {power=200}) end
end)
dg
committed
-- Weakness hate bonus
if hitted and effGloomWeakness and effGloomWeakness.hateBonus or 0 > 0 then
self:incHate(effGloomWeakness.hateBonus)
game.logPlayer(self, "#F53CBE#You revel in attacking a weakened foe! (+%d hate)", effGloomWeakness.hateBonus)
effGloomWeakness.hateBonus = nil
end
dg
committed
-- Rampage
if hitted and crit then
local eff = self:hasEffect(self.EFF_RAMPAGE)
if eff and not eff.critHit and eff.actualDuration < eff.maxDuration and self:knowTalent(self.T_BRUTALITY) then
dg
committed
game.logPlayer(self, "#F53CBE#Your rampage is invigorated by your fierce attack! (+1 duration)")
eff.critHit = true
eff.actualDuration = eff.actualDuration + 1
eff.dur = eff.dur + 1
end
end
dg
committed
-- Marked Prey
if hitted and not target.dead and effPredator and effPredator.type == target.type then
if effPredator.subtype == target.subtype then
-- Anatomy stun
if effPredator.subtypeStunChance > 0 and rng.percent(effPredator.subtypeStunChance) then
if target:canBe("stun") then
target:setEffect(target.EFF_STUNNED, 3, {})
else
game.logSeen(target, "%s resists the stun!", target.name:capitalize())
end
end
dg
committed
-- Outmaneuver
if effPredator.subtypeOutmaneuverChance > 0 and rng.percent(effPredator.subtypeOutmaneuverChance) then
local t = self:getTalentFromId(self.T_OUTMANEUVER)
target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { physicalResistChange=t.getPhysicalResistChange(self, t), statReduction=t.getStatReduction(self, t) })
dg
committed
end
else
-- Outmaneuver
if effPredator.typeOutmaneuverChance > 0 and rng.percent(effPredator.typeOutmaneuverChance) then
local t = self:getTalentFromId(self.T_OUTMANEUVER)
target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { physicalResistChange=t.getPhysicalResistChange(self, t), statReduction=t.getStatReduction(self, t) })
dg
committed
end
end
end
dg
committed
if hitted and crit and target:hasEffect(target.EFF_DISMAYED) then
target:removeEffect(target.EFF_DISMAYED)
end
if hitted and not target.dead then
-- Curse of Madness: Twisted Mind
if self.hasEffect and self:hasEffect(self.EFF_CURSE_OF_MADNESS) then
local eff = self:hasEffect(self.EFF_CURSE_OF_MADNESS)
local def = self.tempeffect_def[self.EFF_CURSE_OF_MADNESS]
def.doConspirator(self, eff, target)
end
if target.hasEffect and target:hasEffect(target.EFF_CURSE_OF_MADNESS) then
local eff = target:hasEffect(target.EFF_CURSE_OF_MADNESS)
local def = target.tempeffect_def[target.EFF_CURSE_OF_MADNESS]
def.doConspirator(target, eff, self)
end
-- Curse of Nightmares: Suffocate
if self.hasEffect and self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES) then
local eff = self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES)
local def = self.tempeffect_def[self.EFF_CURSE_OF_NIGHTMARES]
def.doSuffocate(self, eff, target)
end
if target.hasEffect and target:hasEffect(target.EFF_CURSE_OF_NIGHTMARES) then
local eff = target:hasEffect(target.EFF_CURSE_OF_NIGHTMARES)
local def = target.tempeffect_def[target.EFF_CURSE_OF_NIGHTMARES]
def.doSuffocate(target, eff, self)
end
end
self:fireTalentCheck("callbackOnMeleeAttack", target, hitted, crit, weapon, damtype, mult, dam)
local hd = {"Combat:attackTargetWith", hitted=hitted, crit=crit, target=target, weapon=weapon, damtype=damtype, mult=mult, dam=dam}
if self:triggerHook(hd) then hitted = hd.hitted end
self.__global_accuracy_damage_bonus = nil
DarkGod
committed
return hitted
sword = {"T_WEAPONS_MASTERY", "T_STRENGTH_OF_PURPOSE"},
axe = {"T_WEAPONS_MASTERY", "T_STRENGTH_OF_PURPOSE"},
mace = {"T_WEAPONS_MASTERY", "T_STRENGTH_OF_PURPOSE"},
knife = {"T_KNIFE_MASTERY", "T_STRENGTH_OF_PURPOSE"},
whip = "T_EXOTIC_WEAPONS_MASTERY",
trident = "T_EXOTIC_WEAPONS_MASTERY",
bow = {"T_BOW_MASTERY", "T_STRENGTH_OF_PURPOSE"},
staff = "T_STAFF_MASTERY",
mindstar ="T_PSIBLADES",
dream = "T_DREAM_CRUSHER",
unarmed = "T_UNARMED_MASTERY",
-- Training Talents can have the following fields:
-- getMasteryPriority(self, t, kind) - Only the talent with the highest is used. Defaults to the talent level.
-- getDamage(self, t, kind) - Extra physical power granted. Defaults to level * 10.
-- getPercentInc(self, t, kind) - Percentage increase to damage overall. Defaults to sqrt(level / 5) / 2.
function _M:addCombatTraining(kind, tid)
local wt = _M.weapon_talents
if not wt[kind] then wt[kind] = tid return end
if type(wt[kind]) == "table" then
wt[kind][#wt[kind]+1] = tid
else
wt[kind] = { wt[kind] }
wt[kind][#wt[kind]+1] = tid
end
end
--- Checks weapon training
function _M:combatGetTraining(weapon)
if not weapon then return nil end
if not weapon.talented then return nil end
if not _M.weapon_talents[weapon.talented] then return nil end
if type(_M.weapon_talents[weapon.talented]) == "table" then
local get_priority = function(tid)
local t = self:getTalentFromId(tid)
if t.getMasteryPriority then return util.getval(t.getMasteryPriority, self, t, weapon.talented) end
return self:getTalentLevel(t)
end
local max_tid -- = _M.weapon_talents[weapon.talented][1]
local max_priority = -math.huge -- get_priority(max_tid)
for _, tid in ipairs(_M.weapon_talents[weapon.talented]) do
DarkGod
committed
if self:knowTalent(tid) then
local priority = get_priority(tid)
if priority > max_priority then
max_tid = tid
max_priority = priority
end
end
end
else
return self:getTalentFromId(_M.weapon_talents[weapon.talented])
end
-- Gets the added damage for a weapon based on training.
function _M:combatTrainingDamage(weapon)
local t = self:combatGetTraining(weapon)
if not t then return 0 end
if t.getDamage then return util.getval(t.getDamage, self, t, weapon.talented) end
return self:getTalentLevel(t) * 10
end
-- Gets the percent increase for a weapon based on training.
function _M:combatTrainingPercentInc(weapon)
local t = self:combatGetTraining(weapon)
if not t then return 0 end
if t.getPercentInc then return util.getval(t.getPercentInc, self, t, weapon.talented) end
return math.sqrt(self:getTalentLevel(t) / 5) / 2
--- Fake denotes a check not actually being made, used by character sheets etc.
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
if not self:attr("encased_in_ice") then
if self:hasDualWeapon() and self:knowTalent(self.T_DUAL_WEAPON_DEFENSE) then
add = add + self:callTalent(self.T_DUAL_WEAPON_DEFENSE,"getDefense")
end
if not fake then
add = add + (self:checkOnDefenseCall("defense") or 0)
end
if self:knowTalent(self.T_TACTICAL_EXPERT) then
local t = self:getTalentFromId(self.T_TACTICAL_EXPERT)
add = add + t.do_tact_update(self, t)
end
if self:knowTalent(self.T_CORRUPTED_SHELL) then
add = add + self:getCon() / 3
end
if self:knowTalent(self.T_STEADY_MIND) then
local t = self:getTalentFromId(self.T_STEADY_MIND)
add = add + t.getDefense(self, t)
end
if self:isTalentActive(Talents.T_SURGE) then
local t = self:getTalentFromId(self.T_SURGE)
add = add + t.getDefenseChange(self, t)
end
local d = math.max(0, self.combat_def + (self:getDex() - 10) * 0.35 + (self:getLck() - 50) * 0.4)
DarkGod
committed
if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
mult = mult + self:callTalent(self.T_MOBILE_DEFENCE,"getDef")
mult = mult + self:callTalent(self.T_MISDIRECTION,"getDefense")/100
return math.max(0, d * mult + add) -- Add bonuses last to avoid compounding defense multipliers from talents
function _M:combatDefense(fake, add)
local base_defense = self:combatDefenseBase(true)
if not fake then base_defense = self:combatDefenseBase() end
local d = math.max(0, base_defense + (add or 0))
return self:rescaleCombatStats(d)
end
--- Gets the defense ranged
function _M:combatDefenseRanged(fake, add)
local base_defense = self:combatDefenseBase(true)
if not fake then base_defense = self:combatDefenseBase() end
local d = math.max(0, base_defense + (self.combat_def_ranged or 0) + (add or 0))
if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
local at = Talents:getTalentFromId(Talents.T_ARMOUR_TRAINING)
add = add + at.getArmor(self, at)
if self:knowTalent(self.T_GOLEM_ARMOUR) then
local ga = Talents:getTalentFromId(Talents.T_GOLEM_ARMOUR)
add = add + ga.getArmor(self, ga)
end
if self:knowTalent(self.T_ARMOUR_OF_SHADOWS) and not game.level.map.lites(self.x, self.y) then
add = add + self:callTalent(self.T_ARMOUR_OF_SHADOWS,"ArmourBonus")
if self:knowTalent(self.T_CARBON_SPIKES) and self:isTalentActive(self.T_CARBON_SPIKES) then
add = add + self.carbon_armor
end
if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then add = add + self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getArmorBoost") end
--- Gets armor hardiness
-- This is how much % of a blow we can reduce with armor
function _M:combatArmorHardiness()
local add = 0
if self:knowTalent(self.T_SKIRMISHER_BUCKLER_EXPERTISE) then
add = add + self:callTalent(self.T_SKIRMISHER_BUCKLER_EXPERTISE, "getHardiness")
end
if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
local at = Talents:getTalentFromId(Talents.T_ARMOUR_TRAINING)
add = add + at.getArmorHardiness(self, at)
if self:knowTalent(self.T_GOLEM_ARMOUR) then
local ga = Talents:getTalentFromId(Talents.T_GOLEM_ARMOUR)
add = add + ga.getArmorHardiness(self, ga)
end
if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
add = add + self:callTalent(self.T_MOBILE_DEFENCE, "getHardiness")
if self:knowTalent(self.T_ARMOUR_OF_SHADOWS) and not game.level.map.lites(self.x, self.y) then
add = add + 50
end
if self:hasEffect(self.EFF_BREACH) then
multi = 0.5
end
return util.bound(30 + self.combat_armor_hardiness + add, 0, 100) * multi
function _M:combatAttackBase(weapon, ammo)
local atk = 4 + self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 10 + (weapon.atk or 0) + (ammo and ammo.atk or 0) + (self:getLck() - 50) * 0.4
if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then atk = atk + self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getDamBoost", weapon) end
return atk
end
function _M:combatAttack(weapon, ammo)
HousePet
committed
if self:attr("use_psi_combat") then stats = (self:getCun(100, true) - 10) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
elseif weapon and weapon.wil_attack then stats = self:getWil(100, true) - 10
local d = self:combatAttackBase(weapon, ammo) + stats
if self:attr("dazed") then d = d / 2 end
HousePet
committed
if self:attr("use_psi_combat") then stats = (self:getCun(100, true) - 10) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
elseif weapon and weapon.wil_attack then stats = self:getWil(100, true) - 10
else stats = self:getDex(100, true) - 10
end
local d = self:combatAttackBase(weapon, ammo) + stats + (self.combat_atk_ranged or 0)
function _M:combatAttackStr(weapon, ammo)
local d = self:combatAttackBase(weapon, ammo) + (self:getStr(100, true) - 10)
if self:attr("dazed") then d = d / 2 end
function _M:combatAttackDex(weapon, ammo)
local d = self:combatAttackBase(weapon, ammo) + (self:getDex(100, true) - 10)
if self:attr("dazed") then d = d / 2 end
function _M:combatAttackMag(weapon, ammo)
local d = self:combatAttackBase(weapon, ammo) + (self:getMag(100, true) - 10)
if self:attr("dazed") then d = d / 2 end
--- Gets the armor penetration
function _M:combatAPR(weapon)
local addapr = 0
return self.combat_apr + (weapon.apr or 0) + addapr
function _M:combatSpeed(weapon, add)
return (weapon.physspeed or 1) / math.max(self.combat_physspeed + (add or 0), 0.1)
end
--- Gets the crit rate
function _M:combatCrit(weapon)
if weapon.talented and self:knowTalent(Talents.T_LETHALITY) then
addcrit = 1 + self:callTalent(Talents.T_LETHALITY, "getCriticalChance")
local crit = self.combat_physcrit + (self.combat_generic_crit or 0) + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + (weapon.physcrit or 1) + addcrit
return math.max(crit, 0) -- note: crit > 100% may be offset by crit reduction elsewhere
function _M:combatDamageRange(weapon, add)
return (self.combat_damrange or 0) + (weapon.damrange or (1.1 - (add or 0))) + (add or 0)
-- This currently beefs up high-end damage values to make up for the combat stat rescale nerf.
function _M:rescaleDamage(dam)
if dam <= 0 then return dam end
-- return dam * (1 - math.log10(dam * 2) / 7) --this is the old version, pre-combat-stat-rescale
return dam ^ 1.04
end
--Diminishing-returns method of scaling combat stats, observing this rule: the first twenty ranks cost 1 point each, the second twenty cost two each, and so on. This is much, much better for players than some logarithmic mess, since they always know exactly what's going on, and there are nice breakpoints to strive for.
function _M:rescaleCombatStats(raw_combat_stat_value)
local x = raw_combat_stat_value
local tiers = 50 -- Just increase this if you want to add high-level content that allows for combat stat scores over 100.
--return math.floor(math.min(x, 20) + math.min(math.max((x-20), 0)/2, 20) + math.min(math.max((x-60), 0)/3, 20) + math.min(math.max((x-120), 0)/4, 20) + math.min(math.max((x-200), 0)/5, 20)) --Five terms of the summation below.
local total = 0
for i = 1, tiers do
local sub = 20*(i*(i-1)/2)
total = total + math.min(math.max(x-sub, 0)/i, 20)
end
return total
-- Scale a value up or down by a power
-- x = a numeric value
-- y_low = value to match at x_low
-- y_high = value to match at x_high
-- power = scaling factor (default 0.5)
-- add = amount to add the result (default 0)
-- shift = amount to add to the input value before computation (default 0)
function _M:combatScale(x, y_low, x_low, y_high, x_high, power, add, shift)
power, add, shift = power or 0.5, add or 0, shift or 0
local x_low_adj, x_high_adj = (x_low+shift)^power, (x_high+shift)^power
local m = (y_high - y_low)/(x_high_adj - x_low_adj)
local b = y_low - m*x_low_adj
end
-- Scale a value up or down subject to a limit
-- x = a numeric value
-- limit = value approached as x increases
-- y_high = value to match at when x = x_high
-- y_low (optional) = value to match when x = x_low
-- returns (limit - add)*x/(x + halfpoint) + add (= add when x = 0 and limit when x = infinity)
-- halfpoint and add are internally computed to match the desired high/low values
-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
function _M:combatLimit(x, limit, y_low, x_low, y_high, x_high)
-- local x_low, x_high = 1,5 -- Implied talent levels for low and high values respectively
-- local tl = type(t) == "table" and (raw and self:getTalentLevelRaw(t) or self:getTalentLevel(t)) or t
if y_low and x_low then
local p = limit*(x_high-x_low)
local m = x_high*y_high - x_low*y_low
-- local halfpoint = (p-m)/(y_high - y_low)
-- local add = (limit*(x_high*y_low-x_low*y_high) + y_high*y_low*(x_low-x_high))/(p-m)
-- return (limit-add)*x/(x + halfpoint) + add
local ah = (limit*(x_high*y_low-x_low*y_high)+ y_high*y_low*(x_low-x_high))/(y_high - y_low) -- add*halfpoint product calculated at once to avoid possible divide by zero
return (limit*x + ah)/(x + (p-m)/(y_high - y_low)) --factored version of above formula
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
-- return (limit-add)*x/(x + halfpoint) + add, halfpoint, add
else
local add = 0
local halfpoint = limit*x_high/(y_high-add)-x_high
return (limit-add)*x/(x + halfpoint) + add
-- return (limit-add)*x/(x + halfpoint) + add, halfpoint, add
end
end
-- Compute a diminishing returns value based on talent level that scales with a power
-- t = talent def table or a numeric value
-- low = value to match at talent level 1
-- high = value to match at talent level 5
-- power = scaling factor (default 0.5) or "log" for log10
-- add = amount to add the result (default 0)
-- shift = amount to add to the talent level before computation (default 0)
-- raw if true specifies use of raw talent level
function _M:combatTalentScale(t, low, high, power, add, shift, raw)
local tl = type(t) == "table" and (raw and self:getTalentLevelRaw(t) or self:getTalentLevel(t)) or t
power, add, shift = power or 0.5, add or 0, shift or 0
local x_low, x_high = 1, 5 -- Implied talent levels to fit
local x_low_adj, x_high_adj
if power == "log" then
x_low_adj, x_high_adj = math.log10(x_low+shift), math.log10(x_high+shift)
tl = math.max(1, tl)
else
x_low_adj, x_high_adj = (x_low+shift)^power, (x_high+shift)^power
end
local m = (high - low)/(x_high_adj - x_low_adj)
local b = low - m*x_low_adj
if power == "log" then -- always >= 0
return math.max(0, m * math.log10(tl + shift) + b + add)
-- return math.max(0, m * math.log10(tl + shift) + b + add), m, b
DarkGod
committed
else
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
return math.max(0, m * (tl + shift)^power + b + add)
-- return math.max(0, m * (tl + shift)^power + b + add), m, b
end
end
-- Compute a diminishing returns value based on a stat value that scales with a power
-- stat == "str", "con",.... or a numeric value
-- low = value to match when stat = 10
-- high = value to match when stat = 100
-- power = scaling factor (default 0.5) or "log" for log10
-- add = amount to add the result (default 0)
-- shift = amount to add to the stat value before computation (default 0)
function _M:combatStatScale(stat, low, high, power, add, shift)
stat = type(stat) == "string" and self:getStat(stat,nil,true) or stat
power, add, shift = power or 0.5, add or 0, shift or 0
local x_low, x_high = 10, 100 -- Implied stat values to match
local x_low_adj, x_high_adj
if power == "log" then
x_low_adj, x_high_adj = math.log10(x_low+shift), math.log10(x_high+shift)
stat = math.max(1, stat)
else
x_low_adj, x_high_adj = (x_low+shift)^power, (x_high+shift)^power
end
local m = (high - low)/(x_high_adj - x_low_adj)
local b = low -m*x_low_adj
if power == "log" then -- always >= 0
return math.max(0, m * math.log10(stat + shift) + b + add)
-- return math.max(0, m * math.log10(stat + shift) + b + add), m, b
DarkGod
committed
else
return math.max(0, m * (stat + shift)^power + b + add)
-- return math.max(0, m * (stat + shift)^power + b + add), m, b
end
end
-- Compute a diminishing returns value based on talent level that cannot go beyond a limit
-- t = talent def table or a numeric value
-- limit = value approached as talent levels increase
-- high = value at talent level 5
-- low = value at talent level 1 (optional)
-- raw if true specifies use of raw talent level
-- returns (limit - add)*TL/(TL + halfpoint) + add == add when TL = 0 and limit when TL = infinity
-- TL = talent level, halfpoint and add are internally computed to match the desired high/low values
-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
function _M:combatTalentLimit(t, limit, low, high, raw)
local x_low, x_high = 1,5 -- Implied talent levels for low and high values respectively
local tl = type(t) == "table" and (raw and self:getTalentLevelRaw(t) or self:getTalentLevel(t)) or t
if low then
local p = limit*(x_high-x_low)
local m = x_high*high - x_low*low
-- local halfpoint = (p-m)/(high - low) -- point at which half progress towards the limit is reached
-- local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
local ah = (limit*(x_high*low-x_low*high)+ high*low*(x_low-x_high))/(high - low) -- add*halfpoint product calculated at once to avoid possible divide by zero
return (limit*tl + ah)/(tl + (p-m)/(high - low)) --factored version of above formula
-- return (limit-add)*tl/(tl + halfpoint) + add, halfpoint, add
else -- assume low and x_low are both 0
local halfpoint = limit*x_high/high-x_high
return limit*tl/(tl + halfpoint)
-- return (limit-add)*tl/(tl + halfpoint) + add, halfpoint, add
end
end
-- Compute a diminishing returns value based on a stat value that cannot go beyond a limit
-- stat == "str", "con",.... or a numeric value
-- limit = value approached as talent levels increase
-- high = value to match when stat = 100
-- low = value to match when stat = 10 (optional)
-- returns (limit - add)*stat/(stat + halfpoint) + add == add when STAT = 0 and limit when stat = infinity
-- halfpoint and add are internally computed to match the desired high/low values
-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
function _M:combatStatLimit(stat, limit, low, high)
local x_low, x_high = 10,100 -- Implied stat levels for low and high values respectively
stat = type(stat) == "string" and self:getStat(stat,nil,true) or stat
if low then
local p = limit*(x_high-x_low)
local m = x_high*high - x_low*low
-- local halfpoint = (p-m)/(high - low) -- point at which half progress towards the limit is reached
-- local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
local ah = (limit*(x_high*low-x_low*high)+ high*low*(x_low-x_high))/(high - low) -- add*halfpoint product calculated at once to avoid possible divide by zero
return (limit*stat + ah)/(stat + (p-m)/(high - low)) --factored version of above formula
-- return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
else -- assume low and x_low are both 0
local halfpoint = limit*x_high/high-x_high
return limit*stat/(stat + halfpoint)
-- return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
end
end
--- Gets the dammod table for a given weapon.
function _M:getDammod(combat)
combat = combat or self.combat or {}
local dammod = table.clone(combat.dammod or {str = 0.6}, true)
local sub = function(from, to)
dammod[to] = (dammod[from] or 0) + (dammod[to] or 0)
dammod[from] = nil
end
if combat.talented == 'knife' and self:knowTalent('T_LETHALITY') then sub('str', 'cun') end
if combat.talented and self:knowTalent('T_STRENGTH_OF_PURPOSE') then sub('str', 'mag') end
if self:attr 'use_psi_combat' then
HousePet
committed
if dammod['str'] then
dammod['str'] = (dammod['str'] or 0) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
HousePet
committed
sub('str', 'wil')
end
if dammod['dex'] then
dammod['dex'] = (dammod['dex'] or 0) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
HousePet
committed
sub('dex', 'cun')
end
end
-- Add stuff like lethality here.
local hd = {"Combat:getDammod:subs", combat=combat, dammod=dammod, sub=sub}
if self:triggerHook(hd) then dammod = hd.dammod end
local add = function(stat, val)
dammod[stat] = (dammod[stat] or 0) + val
end
if self:knowTalent(self.T_SUPERPOWER) then add('wil', 0.3) end
if self:knowTalent(self.T_ARCANE_MIGHT) then add('mag', 0.5) end
return dammod
end
-- Calculate combat damage for a weapon (with an optional damage field for ranged)
-- Talent bonuses are always based on the base weapon
function _M:combatDamage(weapon, adddammod, damage)
local dammod = self:getDammod(damage or weapon)
for stat, mod in pairs(dammod) do
totstat = totstat + self:getStat(stat) * mod
if adddammod then
for stat, mod in pairs(adddammod) do
totstat = totstat + self:getStat(stat) * mod
end
end
local talented_mod = 1 + self:combatTrainingPercentInc(weapon)
local power = self:combatDamagePower(damage or weapon)
return self:rescaleDamage(0.3*(self:combatPhysicalpower(nil, weapon) + totstat) * power * talented_mod)
--- Gets the 'power' portion of the damage
function _M:combatDamagePower(weapon_combat, add)
local power = math.max((weapon_combat.dam or 1) + (add or 0), 1)
if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then power = power + self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getDamBoost", weapon_combat) end
return (math.sqrt(power / 10) - 1) * 0.5 + 1
end
function _M:combatPhysicalpower(mod, weapon, add)
add = add or 0
if self.combat_generic_power then
add = add + self.combat_generic_power
end
if self:knowTalent(Talents.T_ARCANE_DESTRUCTION) then
add = add + self:combatSpellpower() * self:callTalent(Talents.T_ARCANE_DESTRUCTION, "getSPMult")
if self:isTalentActive(Talents.T_BLOOD_FRENZY) then
add = add + self.blood_frenzy
end
DarkGod
committed
if self:knowTalent(self.T_BLOODY_BUTCHER) then
add = add + self:callTalent(Talents.T_BLOODY_BUTCHER, "getDam")
end
if self:knowTalent(self.T_EMPTY_HAND) and self:isUnarmed() then
local t = self:getTalentFromId(self.T_EMPTY_HAND)
add = add + t.getDamage(self, t)
end
if self:attr("psychometry_power") then
add = add + self:attr("psychometry_power")
end
if not weapon then
local inven = self:getInven(self.INVEN_MAINHAND)
if inven and inven[1] then weapon = self:getObjectCombat(inven[1], "mainhand") else weapon = self.combat end
add = add + self:combatTrainingDamage(weapon)
local d = math.max(0, (self.combat_dam or 0) + add + str) -- allows strong debuffs to offset strength
if self:attr("hit_penalty_2h") then d = d * (1 - math.max(0, 20 - (self.size_category - 4) * 5) / 100) end
return self:combatSpellpower(mod, d) -- will do the rescaling and multiplying for us
else
return self:rescaleCombatStats(d) * mod
--- Gets damage based on talent
function _M:combatTalentPhysicalDamage(t, base, max)
-- Compute at "max"
local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1))
-- Compute real
return self:rescaleDamage((base + (self:combatPhysicalpower())) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod)
end
function _M:combatSpellpower(mod, add)
add = add or 0
if self.combat_generic_power then
add = add + self.combat_generic_power
end
if self:knowTalent(self.T_ARCANE_CUNNING) then
add = add + self:callTalent(self.T_ARCANE_CUNNING,"getSpellpower") * self:getCun() / 100
if self:knowTalent(self.T_SHADOW_CUNNING) then
add = add + self:callTalent(self.T_SHADOW_CUNNING,"getSpellpower") * self:getCun() / 100
add = add + self:hasEffect(self.EFF_BLOODLUST).power
local am = 1
if self:attr("spellpower_reduction") then am = 1 / (1 + self:attr("spellpower_reduction")) end
local d = math.max(0, (self.combat_spellpower or 0) + add + self:getMag())
if self:attr("hit_penalty_2h") then d = d * (1 - math.max(0, 20 - (self.size_category - 4) * 5) / 100) end
return self:rescaleCombatStats(d) * mod * am
end
--- Gets damage based on talent
function _M:combatTalentSpellDamage(t, base, max, spellpower_override)
-- Compute at "max"
local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1))
-- Compute real
return self:rescaleDamage((base + (spellpower_override or self:combatSpellpower())) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod)
--- Gets weapon damage mult based on talent
function _M:combatTalentWeaponDamage(t, base, max, t2)
if t2 then t2 = t2 / 2 else t2 = 0 end
local diff = max - base
local mult = base + diff * math.sqrt((self:getTalentLevel(t) + t2) / 5)
-- print("[TALENT WEAPON MULT]", self:getTalentLevel(t), base, max, t2, mult)
Hachem_Muche
committed
if combat and combat.range and not combat.dam then return mult or 1 end --no penalty for ranged shooters
local offmult = 1/2
-- Take the bigger multiplier from Dual weapon training and Corrupted Strength
if self:knowTalent(Talents.T_DUAL_WEAPON_TRAINING) then
offmult = math.max(offmult,self:callTalent(Talents.T_DUAL_WEAPON_TRAINING,"getoffmult"))
offmult = math.max(offmult,self:callTalent(Talents.T_CORRUPTED_STRENGTH,"getoffmult"))
if self:hasEffect(self.EFF_CURSE_OF_MADNESS) then
local eff = self:hasEffect(self.EFF_CURSE_OF_MADNESS)
if eff.level >= 1 and eff.unlockLevel >= 1 then
local def = self.tempeffect_def[self.EFF_CURSE_OF_MADNESS]
offmult = offmult + ((mult or 1) * def.getOffHandMultChange(eff.level) / 100)
end
end
if combat and combat.no_offhand_penalty then
return math.max(1, offmult)
else
return offmult
end
if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then fatigue = fatigue - self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getFatigueBoost") end
if fatigue < min then return min end
if self:knowTalent(self.T_NO_FATIGUE) then return min end
local crit = self.combat_spellcrit + (self.combat_generic_crit or 0) + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + 1
dg
committed
--- Gets mindcrit
dg
committed
function _M:combatMindCrit(add)
local add = add or 0
dg
committed
if self:knowTalent(self.T_GESTURE_OF_POWER) then
local t = self:getTalentFromId(self.T_GESTURE_OF_POWER)
add = t.getMindCritChange(self, t)
end
local crit = self.combat_mindcrit + (self.combat_generic_crit or 0) + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + 1 + add
dg
committed
end
return 1 / math.max(self.combat_spellspeed, 0.1)
function _M:combatMindSpeed()
return 1 / math.max(self.combat_mindspeed, 0.1)
--- Gets summon speed
function _M:combatSummonSpeed()
return math.max(1 - ((self:attr("fast_summons") or 0) / 100), 0.1)
end
--- Computes physical crit chance reduction
function _M:combatCritReduction()
local crit_reduction = 0
if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
local at = Talents:getTalentFromId(Talents.T_ARMOUR_TRAINING)
crit_reduction = crit_reduction + at.getCriticalChanceReduction(self, at)
if self:knowTalent(self.T_GOLEM_ARMOUR) then
local ga = Talents:getTalentFromId(Talents.T_GOLEM_ARMOUR)
crit_reduction = crit_reduction + ga.getCriticalChanceReduction(self, ga)
end
end
if self:attr("combat_crit_reduction") then
crit_reduction = crit_reduction + self:attr("combat_crit_reduction")
end
return crit_reduction
end
function _M:physicalCrit(dam, weapon, target, atk, def, add_chance, crit_power_add)
local chance = self:combatCrit(weapon) + (add_chance or 0)
crit_power_add = crit_power_add or 0
if target and target:hasEffect(target.EFF_DISMAYED) then
if self:knowTalent(self.T_BACKSTAB) and target and target:attr("stunned") then chance = chance + self:callTalent(self.T_BACKSTAB,"getCriticalChance") end
if target and target:attr("combat_crit_vulnerable") then
chance = chance + target:attr("combat_crit_vulnerable")
if target and target:hasEffect(target.EFF_SET_UP) then
if p and p.src == self then
if target and self:hasEffect(self.EFF_WARDEN_S_FOCUS) then
local eff = self:hasEffect(self.EFF_WARDEN_S_FOCUS)
if eff and eff.target == target then
chance = chance + eff.power
crit_power_add = crit_power_add + (eff.power/100)
end
end
if target then
chance = chance - target:combatCritReduction()
end
if self:attr("cut") and target and target:knowTalent(self.T_SCOUNDREL) then
chance = chance - target:callTalent(target.T_SCOUNDREL,"getCritPenalty")
if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and target and not target:canSee(self) then -- bug fix
chance = 100
self.turn_procs.shadowstrike_crit = self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
crit_power_add = crit_power_add + self.turn_procs.shadowstrike_crit
if self:isAccuracyEffect(weapon, "axe") then
local bonus = self:getAccuracyEffect(weapon, atk, def, 0.2, 10)
print("[PHYS CRIT %] axe accuracy bonus", atk, def, "=", bonus)
chance = chance + bonus
end
print("[PHYS CRIT %]", self.turn_procs.auto_phys_crit and 100 or chance)
if self.turn_procs.auto_phys_crit or rng.percent(chance) then
if target and target:hasEffect(target.EFF_OFFGUARD) then
crit_power_add = crit_power_add + 0.1
if self:isAccuracyEffect(weapon, "sword") then
local bonus = self:getAccuracyEffect(weapon, atk, def, 0.004, 0.25)
print("[PHYS CRIT %] sword accuracy bonus", atk, def, "=", bonus)
crit_power_add = crit_power_add + bonus
end
self.turn_procs.is_crit = "physical"
self.turn_procs.crit_power = (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
dam = dam * (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
if self:knowTalent(self.T_EYE_OF_THE_TIGER) then self:triggerTalent(self.T_EYE_OF_THE_TIGER, nil, "physical") end
self:fireTalentCheck("callbackOnCrit", "physical", dam, chance, target)
local chance = self:combatSpellCrit() + (add_chance or 0)
-- Unlike physical crits we can't know anything about our target here so we can't check if they can see us
if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) then
chance = 100
crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
self.turn_procs.shadowstrike_crit = self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
print("[SPELL CRIT %]", self.turn_procs.auto_spell_crit and 100 or chance)
if self.turn_procs.auto_spell_crit or rng.percent(chance) then
self.turn_procs.crit_power = (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
dam = dam * (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
dg
committed
game.logSeen(self, "#{bold}#%s's spell attains critical power!#{normal}#", self.name:capitalize())
if self:attr("mana_on_crit") then self:incMana(self:attr("mana_on_crit")) end
if self:attr("vim_on_crit") then self:incVim(self:attr("vim_on_crit")) end
if self:attr("paradox_on_crit") then self:incParadox(self:attr("paradox_on_crit")) end
if self:attr("positive_on_crit") then self:incPositive(self:attr("positive_on_crit")) end
if self:attr("negative_on_crit") then self:incNegative(self:attr("negative_on_crit")) end
if self:attr("spellsurge_on_crit") then
local power = self:attr("spellsurge_on_crit")
self:setEffect(self.EFF_SPELLSURGE, 10, {power=power, max=power*3})
end
if self:isTalentActive(self.T_BLOOD_FURY) then
local t = self:getTalentFromId(self.T_BLOOD_FURY)
t.on_crit(self, t)
end
if self:isTalentActive(self.T_CORONA) then
local t = self:getTalentFromId(self.T_CORONA)
if self:knowTalent(self.T_EYE_OF_THE_TIGER) then self:triggerTalent(self.T_EYE_OF_THE_TIGER, nil, "spell") end
self:fireTalentCheck("callbackOnCrit", "spell", dam, chance)
local chance = self:combatMindCrit() + (add_chance or 0)
local crit = false
if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) then
chance = 100
crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
self.turn_procs.shadowstrike_crit = self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
print("[MIND CRIT %]", self.turn_procs.auto_mind_crit and 100 or chance)
if self.turn_procs.auto_mind_crit or rng.percent(chance) then
self.turn_procs.crit_power = (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
dam = dam * (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
dg
committed
game.logSeen(self, "#{bold}#%s's mind surges with critical power!#{normal}#", self.name:capitalize())
if self:attr("hate_on_crit") then self:incHate(self:attr("hate_on_crit")) end
if self:attr("psi_on_crit") then self:incPsi(self:attr("psi_on_crit")) end
if self:attr("equilibrium_on_crit") then self:incEquilibrium(self:attr("equilibrium_on_crit")) end
if self:knowTalent(self.T_EYE_OF_THE_TIGER) then self:triggerTalent(self.T_EYE_OF_THE_TIGER, nil, "mind") end
if self:knowTalent(self.T_LIVING_MUCUS) then self:callTalent(self.T_LIVING_MUCUS, "on_crit") end
self:fireTalentCheck("callbackOnCrit", "mind", dam, chance)
dg
committed
--- Do we get hit by our own AOE ?
function _M:spellFriendlyFire()
local chance = (self:getLck() - 50) * 0.2
if self:isTalentActive(self.T_SPELLCRAFT) then chance = chance + self:getTalentLevelRaw(self.T_SPELLCRAFT) * 20 end
dg
committed
chance = 100 - chance
print("[SPELL] friendly fire chance", chance)
dg
committed
end
dg
committed
function _M:combatMindpower(mod, add)
dg
committed
add = add or 0
dg
committed
if self.combat_generic_power then
add = add + self.combat_generic_power
end
if self:knowTalent(self.T_SUPERPOWER) then
add = add + 50 * self:getStr() / 100
end
if self:knowTalent(self.T_GESTURE_OF_POWER) then