Skip to content
Snippets Groups Projects
Combat.lua 79 KiB
Newer Older
  • Learn to ignore specific revisions
  • dg's avatar
    dg committed
    	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))
    
    	if self:attr("dazed") then d = d / 2 end
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats(d)
    end
    
    --- Gets the defense ranged
    
    function _M:combatDefenseRanged(fake, add)
    
    dg's avatar
    dg committed
    	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:attr("dazed") then d = d / 2 end
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the armor
    function _M:combatArmor()
    
    dg's avatar
    dg committed
    	local add = 0
    
    	if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
    
    dg's avatar
    dg committed
    		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
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_CARBON_SPIKES) and self:isTalentActive(self.T_CARBON_SPIKES) then
    		add = add + self.carbon_armor
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_ARMOUR_OF_SHADOWS) and not game.level.map.lites(self.x, self.y) then
    		add = add + 30
    	end
    
    dg's avatar
    dg committed
    	return self.combat_armor + add
    
    dg's avatar
    dg committed
    end
    
    
    --- Gets armor hardiness
    -- This is how much % of a blow we can reduce with armor
    function _M:combatArmorHardiness()
    	local add = 0
    	if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
    
    dg's avatar
    dg committed
    		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
    
    dg's avatar
    dg committed
    	if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
    
    		add = add + self:callTalent(self.T_MOBILE_DEFENCE, "getHardiness")
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_ARMOUR_OF_SHADOWS) and not game.level.map.lites(self.x, self.y) then
    		add = add + 50
    	end
    
    dg's avatar
    dg committed
    	return util.bound(30 + self.combat_armor_hardiness + add, 0, 100)
    
    dg's avatar
    dg committed
    --- Gets the attack
    
    function _M:combatAttackBase(weapon, ammo)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    	return 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
    
    end
    function _M:combatAttack(weapon, ammo)
    
    dg's avatar
    dg committed
    	local stats
    
    dg's avatar
    dg committed
    	if self.use_psi_combat then stats = self:getCun(100, true) - 10
    
    	elseif weapon and weapon.wil_attack then stats = self:getWil(100, true) - 10
    
    dg's avatar
    dg committed
    	else stats = self:getDex(100, true) - 10
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	local d = self:combatAttackBase(weapon, ammo) + stats
    	if self:attr("dazed") then d = d / 2 end
    	return self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    function _M:combatAttackRanged(weapon, ammo)
    	local stats
    	if self.use_psi_combat then stats = self:getCun(100, true) - 10
    	elseif weapon and weapon.wil_attack then stats = self:getWil(100, true) - 10
    	else stats = self:getDex(100, true) - 10
    	end
    
    dg's avatar
    dg committed
    	local d = self:combatAttackBase(weapon, ammo) + stats + (self.combat_atk_ranged or 0)
    
    dg's avatar
    dg committed
    	if self:attr("dazed") then d = d / 2 end
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the attack using only strength
    
    function _M:combatAttackStr(weapon, ammo)
    
    dg's avatar
    dg committed
    	local d = self:combatAttackBase(weapon, ammo) + (self:getStr(100, true) - 10)
    	if self:attr("dazed") then d = d / 2 end
    	return self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack using only dexterity
    
    function _M:combatAttackDex(weapon, ammo)
    
    dg's avatar
    dg committed
    	local d = self:combatAttackBase(weapon, ammo) + (self:getDex(100, true) - 10)
    	if self:attr("dazed") then d = d / 2 end
    	return self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the attack using only magic
    
    function _M:combatAttackMag(weapon, ammo)
    
    dg's avatar
    dg committed
    	local d = self:combatAttackBase(weapon, ammo) + (self:getMag(100, true) - 10)
    	if self:attr("dazed") then d = d / 2 end
    	return self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the armor penetration
    function _M:combatAPR(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    	local addapr = 0
    	return self.combat_apr + (weapon.apr or 0) + addapr
    
    dg's avatar
    dg committed
    end
    
    --- Gets the weapon speed
    function _M:combatSpeed(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    	return (weapon.physspeed or 1) / math.max(self.combat_physspeed, 0.1)
    
    dg's avatar
    dg committed
    end
    
    --- Gets the crit rate
    function _M:combatCrit(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    dg's avatar
    dg committed
    	local addcrit = 0
    
    	if weapon.talented and self:knowTalent(Talents.T_LETHALITY) then
    
    		addcrit = 1 + self:callTalent(Talents.T_LETHALITY, "getCriticalChance")
    
    dg's avatar
    dg committed
    	end
    
    	local crit = self.combat_physcrit + (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
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the damage range
    function _M:combatDamageRange(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    dg's avatar
    dg committed
    	return (self.combat_damrange or 0) + (weapon.damrange or 1.1)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Scale damage values
    
    dg's avatar
    dg committed
    -- This currently beefs up high-end damage values to make up for the combat stat rescale nerf.
    
    dg's avatar
    dg committed
    function _M:rescaleDamage(dam)
    	if dam <= 0 then return dam end
    
    dg's avatar
    dg committed
    --	return dam * (1 - math.log10(dam * 2) / 7) --this is the old version, pre-combat-stat-rescale
    	return dam ^ 1.04
    end
    
    dg's avatar
    dg committed
    --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
    
    DarkGod's avatar
    DarkGod committed
    	local tiers = 50 -- Just increase this if you want to add high-level content that allows for combat stat scores over 100.
    
    dg's avatar
    dg committed
    	--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
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    -- 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
    
    dg's avatar
    dg committed
    	local m = (y_high - y_low)/(x_high_adj - x_low_adj)
    	local b = y_low - m*x_low_adj
    
    	return m * (x + shift)^power + b + add
    
    dg's avatar
    dg committed
    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, add
    -- 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
    --		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
    	else 
    		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
    	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)
    		local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
    		return (limit-add)*tl/(tl + halfpoint) + add
    --		return (limit-add)*tl/(tl + halfpoint) + add, halfpoint, add
    	else
    		local add = 0
    		local halfpoint = limit*x_high/(high-add)-x_high
    		return (limit-add)*tl/(tl + halfpoint) + add
    --		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 talent 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)
    		local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
    		return (limit-add)*stat/(stat + halfpoint) + add
    --		return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
    	else
    		local add = 0
    		local halfpoint = limit*x_high/(high-add)-x_high
    		return (limit-add)*stat/(stat + halfpoint) + add
    --		return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
    	end
    end
    
    
    dg's avatar
    dg committed
    --- Gets the damage
    function _M:combatDamage(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	local sub_cun_to_str = false
    	if weapon.talented and weapon.talented == "knife" and self:knowTalent(Talents.T_LETHALITY) then sub_cun_to_str = true end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	local dammod = weapon.dammod or {str=0.6}
    
    	for stat, mod in pairs(dammod) do
    
    dg's avatar
    dg committed
    		if sub_cun_to_str and stat == "str" then stat = "cun" end
    
    dg's avatar
    dg committed
    		if self.use_psi_combat and stat == "str" then stat = "wil" end
    		if self.use_psi_combat and stat == "dex" then stat = "cun" end
    
    		totstat = totstat + self:getStat(stat) * mod
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if self.use_psi_combat then
    
    		if self:knowTalent(self.T_GREATER_TELEKINETIC_GRASP) then
    
    			local g = self:getTalentFromId(self.T_GREATER_TELEKINETIC_GRASP)
    
    			totstat = totstat * g.stat_sub(self, g)
    		else
    
    dg's avatar
    dg committed
    			totstat = totstat * 0.6
    
    	if self:knowTalent(self.T_SUPERPOWER) then
    		totstat = totstat + self:getStat("wil") * 0.3
    	end
    
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_ARCANE_MIGHT) then
    		totstat = totstat + self:getStat("mag") * 0.5
    	end
    
    
    	local talented_mod = math.sqrt(self:combatCheckTraining(weapon) / 5) / 2 + 1
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	local power = math.max((weapon.dam or 1), 1)
    	power = (math.sqrt(power / 10) - 1) * 0.5 + 1
    	--print(("[COMBAT DAMAGE] power(%f) totstat(%f) talent_mod(%f)"):format(power, totstat, talented_mod))
    
    	return self:rescaleDamage(0.3*(self:combatPhysicalpower(nil, weapon) + totstat) * power * talented_mod)
    
    dg's avatar
    dg committed
    end
    
    
    function _M:combatPhysicalpower(mod, weapon, add)
    
    dg's avatar
    dg committed
    	mod = mod or 1
    
    dg's avatar
    dg committed
    	if self:knowTalent(Talents.T_ARCANE_DESTRUCTION) then
    
    		add = add + self:combatSpellpower() * self:callTalent(Talents.T_ARCANE_DESTRUCTION, "getSPMult")
    
    dg's avatar
    dg committed
    	end
    
    	if self:isTalentActive(Talents.T_BLOOD_FRENZY) then
    		add = add + self.blood_frenzy
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_EMPTY_HAND) and self:isUnarmed() then
    
    dg's avatar
    dg committed
    		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
    
    dg's avatar
    dg committed
    	end
    
    
    	add = add + 10 * self:combatCheckTraining(weapon)
    
    	local d = math.max(0, self.combat_dam + add) + self:getStr() -- allows strong debuffs to offset strength
    
    dg's avatar
    dg committed
    	if self:attr("dazed") then d = d / 2 end
    
    	return self:rescaleCombatStats(d) * mod
    
    dg's avatar
    dg committed
    end
    
    
    --- 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
    
    
    dg's avatar
    dg committed
    --- Gets spellpower
    
    function _M:combatSpellpower(mod, add)
    
    dg's avatar
    dg committed
    	mod = mod or 1
    
    	if self:knowTalent(self.T_ARCANE_CUNNING) then
    
    		add = add + self:callTalent(self.T_ARCANE_CUNNING,"getSpellpower") * self:getCun() / 100
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_SHADOW_CUNNING) then
    
    		add = add + self:callTalent(self.T_SHADOW_CUNNING,"getSpellpower") * self:getCun() / 100
    
    dg's avatar
    dg committed
    	end
    	if self:hasEffect(self.EFF_BLOODLUST) then
    
    		add = add + self:hasEffect(self.EFF_BLOODLUST).power
    
    dg's avatar
    dg committed
    
    
    	local am = 1
    	if self:attr("spellpower_reduction") then am = 1 / (1 + self:attr("spellpower_reduction")) end
    
    
    	local d = (self.combat_spellpower > 0 and self.combat_spellpower or 0) + add + self:getMag()
    
    dg's avatar
    dg committed
    	if self:attr("dazed") then d = d / 2 end
    
    	return self:rescaleCombatStats(d) * mod * am
    
    end
    
    --- Gets damage based on talent
    
    dg's avatar
    dg committed
    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
    
    dg's avatar
    dg committed
    	return self:rescaleDamage((base + (spellpower_override or self:combatSpellpower())) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- 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
    
    dg's avatar
    dg committed
    	local mult = base + diff * math.sqrt((self:getTalentLevel(t) + t2) / 5)
    
    dg's avatar
    dg committed
    --	print("[TALENT WEAPON MULT]", self:getTalentLevel(t), base, max, t2, mult)
    
    dg's avatar
    dg committed
    	return mult
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the off hand multiplier
    
    dg's avatar
    dg committed
    function _M:getOffHandMult(combat, mult)
    
    	local offmult = 1/2
    	-- Take the bigger multiplier from Dual weapon training and Corrupted Strength
    
    dg's avatar
    dg committed
    	if self:knowTalent(Talents.T_DUAL_WEAPON_TRAINING) then
    
    		offmult = math.max(offmult,self:callTalent(Talents.T_DUAL_WEAPON_TRAINING,"getoffmult"))
    
    dg's avatar
    dg committed
    	end
    	if self:knowTalent(Talents.T_CORRUPTED_STRENGTH) then
    
    		offmult = math.max(offmult,self:callTalent(Talents.T_CORRUPTED_STRENGTH,"getoffmult"))
    
    dg's avatar
    dg committed
    	end
    
    	offmult = (mult or 1)*offmult
    
    	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
    
    dg's avatar
    dg committed
    		return math.max(1, offmult)
    	else
    		return offmult
    	end
    
    dg's avatar
    dg committed
    --- Gets fatigue
    function _M:combatFatigue()
    	if self.fatigue < 0 then return 0 end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_NO_FATIGUE) then return 0 end
    
    dg's avatar
    dg committed
    	return self.fatigue
    end
    
    
    dg's avatar
    dg committed
    --- Gets spellcrit
    function _M:combatSpellCrit()
    
    	local crit = self.combat_spellcrit + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + 1
    
    dg's avatar
    dg committed
    	return util.bound(crit, 0, 100)
    
    dg's avatar
    dg committed
    end
    
    
    function _M:combatMindCrit(add)
    	local add = add or 0
    
    	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:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + 1 + add
    
    dg's avatar
    dg committed
    	return util.bound(crit, 0, 100)
    
    dg's avatar
    dg committed
    --- Gets spellspeed
    function _M:combatSpellSpeed()
    
    	return 1 / math.max(self.combat_spellspeed, 0.1)
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    -- Gets mental speed
    
    function _M:combatMindSpeed()
    
    	return 1 / math.max(self.combat_mindspeed, 0.1)
    
    dg's avatar
    dg committed
    end
    
    
    --- 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
    
    dg's avatar
    dg committed
    		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
    
    
    dg's avatar
    dg committed
    --- Computes physical crit for a damage
    
    dg's avatar
    dg committed
    function _M:physicalCrit(dam, weapon, target, atk, def, add_chance, crit_power_add)
    
    dg's avatar
    dg committed
    	self.turn_procs.is_crit = nil
    
    
    dg's avatar
    dg committed
    	local tier_diff = self:getTierDiff(atk, def)
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	local chance = self:combatCrit(weapon) + (add_chance or 0)
    	crit_power_add = crit_power_add or 0
    
    dg's avatar
    dg committed
    
    	if target:hasEffect(target.EFF_DISMAYED) then
    		chance = 100
    	end
    
    
    dg's avatar
    dg committed
    	local crit = false
    
    DarkGod's avatar
    DarkGod committed
    	if self:knowTalent(self.T_BACKSTAB) and target:attr("stunned") then chance = chance + self:callTalent(self.T_BACKSTAB,"getCriticalChance") end
    
    dg's avatar
    dg committed
    
    
    	if target:attr("combat_crit_vulnerable") then
    		chance = chance + target:attr("combat_crit_vulnerable")
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if target:hasEffect(target.EFF_SET_UP) then
    		local p = target:hasEffect(target.EFF_SET_UP)
    
    		if p and p.src == self then
    
    dg's avatar
    dg committed
    			chance = chance + p.power
    		end
    	end
    
    dg's avatar
    dg committed
    
    
    	chance = chance - target:combatCritReduction()
    
    dg's avatar
    dg committed
    	-- Scoundrel's Strategies
    	if self:attr("cut") 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 not target:canSee(self) then -- bug fix
    
    		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
    
    dg's avatar
    dg committed
    	chance = util.bound(chance, 0, 100)
    
    
    dg's avatar
    dg committed
    	print("[PHYS CRIT %]", chance)
    
    dg's avatar
    dg committed
    	if rng.percent(chance) then
    
    		if target:hasEffect(target.EFF_OFFGUARD) then
    			crit_power_add = crit_power_add + 0.1
    
    dg's avatar
    dg committed
    		end
    
    dg's avatar
    dg committed
    		self.turn_procs.is_crit = "physical"
    		self.turn_procs.crit_power = (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
    
    dg's avatar
    dg committed
    		dam = dam * (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
    
    dg's avatar
    dg committed
    		crit = true
    
    		if self:knowTalent(self.T_EYE_OF_THE_TIGER) then self:triggerTalent(self.T_EYE_OF_THE_TIGER, nil, "physical") end
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	return dam, crit
    
    dg's avatar
    dg committed
    end
    
    --- Computes spell crit for a damage
    
    dg's avatar
    dg committed
    function _M:spellCrit(dam, add_chance, crit_power_add)
    
    dg's avatar
    dg committed
    	self.turn_procs.is_crit = nil
    
    
    dg's avatar
    dg committed
    	crit_power_add = crit_power_add or 0
    
    	local chance = self:combatSpellCrit() + (add_chance or 0)
    
    dg's avatar
    dg committed
    	local crit = false
    
    DarkGod's avatar
    DarkGod committed
    --	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and not target:canSee(self) then -- bug fix
    	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) then -- bug fix
    
    		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
    
    dg's avatar
    dg committed
    	print("[SPELL CRIT %]", chance)
    
    dg's avatar
    dg committed
    	if rng.percent(chance) then
    
    dg's avatar
    dg committed
    		self.turn_procs.is_crit = "spell"
    
    dg's avatar
    dg committed
    		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's avatar
    dg committed
    		crit = true
    
    		game.logSeen(self, "#{bold}#%s's spell attains critical power!#{normal}#", self.name:capitalize())
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    		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
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    		if self:isTalentActive(self.T_BLOOD_FURY) then
    			local t = self:getTalentFromId(self.T_BLOOD_FURY)
    			t.on_crit(self, t)
    		end
    
    dg's avatar
    dg committed
    
    
    		if self:isTalentActive(self.T_CORONA) then
    			local t = self:getTalentFromId(self.T_CORONA)
    
    dg's avatar
    dg committed
    			t.on_crit(self, t)
    
    
    		if self:knowTalent(self.T_EYE_OF_THE_TIGER) then self:triggerTalent(self.T_EYE_OF_THE_TIGER, nil, "spell") end
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	return dam, crit
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    
    --- Computes mind crit for a damage
    
    dg's avatar
    dg committed
    function _M:mindCrit(dam, add_chance, crit_power_add)
    
    dg's avatar
    dg committed
    	self.turn_procs.is_crit = nil
    
    
    dg's avatar
    dg committed
    	crit_power_add = crit_power_add or 0
    
    	local chance = self:combatMindCrit() + (add_chance or 0)
    	local crit = false
    
    
    DarkGod's avatar
    DarkGod committed
    --	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and not target:canSee(self) then -- bug fix
    	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) then -- bug fix
    
    		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
    
    	print("[MIND CRIT %]", chance)
    	if rng.percent(chance) then
    
    dg's avatar
    dg committed
    		self.turn_procs.is_crit = "mind"
    
    dg's avatar
    dg committed
    		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)
    
    		crit = true
    
    		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
    
    dg's avatar
    dg committed
    		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
    
    dg's avatar
    dg committed
    		if self:knowTalent(self.T_LIVING_MUCUS) then self:callTalent(self.T_LIVING_MUCUS, "on_crit") end
    
    	end
    	return dam, crit
    end
    
    
    --- 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's avatar
    dg committed
    	chance = chance + (self.combat_spell_friendlyfire or 0)
    
    
    	chance = 100 - chance
    	print("[SPELL] friendly fire chance", chance)
    
    dg's avatar
    dg committed
    	return util.bound(chance, 0, 100)
    
    --- Gets mindpower
    
    	mod = mod or 1
    
    	if self:knowTalent(self.T_SUPERPOWER) then
    
    		add = add + 50 * self:getStr() / 100
    
    	if self:knowTalent(self.T_GESTURE_OF_POWER) then
    		local t = self:getTalentFromId(self.T_GESTURE_OF_POWER)
    
    	if self:attr("psychometry_power") then
    		add = add + self:attr("psychometry_power")
    	end
    
    	local d = (self.combat_mindpower > 0 and self.combat_mindpower or 0) + add + self:getWil() * 0.7 + self:getCun() * 0.4
    
    dg's avatar
    dg committed
    	if self:attr("dazed") then d = d / 2 end
    
    	return self:rescaleCombatStats(d) * mod
    
    end
    
    --- Gets damage based on talent
    function _M:combatTalentMindDamage(t, base, max)
    	-- Compute at "max"
    	local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1))
    	-- Compute real
    
    dg's avatar
    dg committed
    	return self:rescaleDamage((base + (self:combatMindpower())) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod)
    
    --- Gets damage based on talent
    function _M:combatTalentStatDamage(t, stat, base, max)
    	-- Compute at "max"
    	local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1))
    	-- Compute real
    
    dg's avatar
    dg committed
    	local dam = (base + (self:getStat(stat))) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod
    	dam =  dam * (1 - math.log10(dam * 2) / 7)
    	dam = dam ^ (1 / 1.04)
    	return self:rescaleDamage(dam)
    
    dg's avatar
    dg committed
    --- Gets damage based on talent, basic stat, and interval
    
    function _M:combatTalentIntervalDamage(t, stat, min, max, stat_weight)
    	local stat_weight = stat_weight or 0.5
    
    dg's avatar
    dg committed
    	local dam = min + (max - min)*((stat_weight * self:getStat(stat)/100) + (1 - stat_weight) * self:getTalentLevel(t)/6.5)
    	dam =  dam * (1 - math.log10(dam * 2) / 7)
    	dam = dam ^ (1 / 1.04)
    	return self:rescaleDamage(dam)
    end
    
    --- Gets damage based on talent, stat, and interval
    function _M:combatStatTalentIntervalDamage(t, stat, min, max, stat_weight)
    	local stat_weight = stat_weight or 0.5
    	scaled_stat = self[stat](self)
    	return self:rescaleDamage(min + (max - min)*((stat_weight * self[stat](self)/100) + (1 - stat_weight) * self:getTalentLevel(t)/6.5))
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Computes physical resistance
    
    --- Fake denotes a check not actually being made, used by character sheets etc.
    function _M:combatPhysicalResist(fake)
    
    dg's avatar
    dg committed
    	local add = 0
    
    dg's avatar
    dg committed
    	if not fake then
    
    		add = add + (self:checkOnDefenseCall("physical") or 0)
    	end
    
    	if self:knowTalent(self.T_CORRUPTED_SHELL) then
    		add = add + self:getCon() / 3
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_POWER_IS_MONEY) then
    
    		add = add + self:callTalent(self.T_POWER_IS_MONEY, "getSaves")
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	-- To return later
    
    	local d = self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.35 + add
    	if self:attr("dazed") then d = d / 2 end
    	local total = self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    	-- Psionic Balance
    	if self:knowTalent(self.T_BALANCE) then
    		local t = self:getTalentFromId(self.T_BALANCE)
    		local ratio = t.getBalanceRatio(self, t)
    		total = (1 - ratio)*total + self:combatMentalResist(fake)*ratio
    	end
    	return total
    
    dg's avatar
    dg committed
    end
    
    --- Computes spell resistance
    
    --- Fake denotes a check not actually being made, used by character sheets etc.
    function _M:combatSpellResist(fake)
    
    dg's avatar
    dg committed
    	local add = 0
    
    dg's avatar
    dg committed
    	if not fake then
    
    		add = add + (self:checkOnDefenseCall("spell") or 0)
    	end
    
    	if self:knowTalent(self.T_CORRUPTED_SHELL) then
    		add = add + self:getCon() / 3
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_POWER_IS_MONEY) then
    
    		add = add + self:callTalent(self.T_POWER_IS_MONEY, "getSaves")
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	-- To return later
    
    	local d = self.combat_spellresist + (self:getMag() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add
    	if self:attr("dazed") then d = d / 2 end
    	local total = self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    	-- Psionic Balance
    	if self:knowTalent(self.T_BALANCE) then
    		local t = self:getTalentFromId(self.T_BALANCE)
    		local ratio = t.getBalanceRatio(self, t)
    		total = (1 - ratio)*total + self:combatMentalResist(fake)*ratio
    	end
    	return total
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    --- Computes mental resistance
    
    --- Fake denotes a check not actually being made, used by character sheets etc.
    function _M:combatMentalResist(fake)
    
    dg's avatar
    dg committed
    	local add = 0
    
    dg's avatar
    dg committed
    	if not fake then
    
    		add = add + (self:checkOnDefenseCall("mental") or 0)
    	end
    
    	if self:knowTalent(self.T_CORRUPTED_SHELL) then
    		add = add + self:getCon() / 3
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_STEADY_MIND) then
    		local t = self:getTalentFromId(self.T_STEADY_MIND)
    		add = add + t.getMental(self, t)
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_POWER_IS_MONEY) then
    
    		add = add + self:callTalent(self.T_POWER_IS_MONEY, "getSaves")
    
    dg's avatar
    dg committed
    	end
    
    
    	local d = self.combat_mentalresist + (self:getCun() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add
    	if self:attr("dazed") then d = d / 2 end
    
    	
    	local nm = self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES)
    	if nm and rng.percent(20) then
    		d = d * (1-self.tempeffect_def.EFF_CURSE_OF_NIGHTMARES.getVisionsReduction(nm, nm.level)/100)
    	end	
    
    	return self:rescaleCombatStats(d)
    
    -- Called when a Save or Defense is checked
    function _M:checkOnDefenseCall(type)
    	local add = 0
    	if self:knowTalent(self.T_SPIN_FATE) then
    		print("Spin Fate", type)
    		local t = self:getTalentFromId(self.T_SPIN_FATE)
    		t.do_spin_fate(self, t, type)
    	end
    	return add
    end
    
    
    --- Returns the resistance
    function _M:combatGetResist(type)
    
    	local power = 100
    	if self.force_use_resist and self.force_use_resist ~= type then 
    		type = self.force_use_resist 
    		power = self.force_use_resist_percent or 100
    	end
    
    dg's avatar
    dg committed
    
    
    	local a = math.min((self.resists.all or 0) / 100,1) -- Prevent large numbers from inverting the resist formulas
    	local b = math.min((self.resists[type] or 0) / 100,1)
    
    	local r = math.min(100 * (1 - (1 - a) * (1 - b)), (self.resists_cap.all or 0) + (self.resists_cap[type] or 0))
    
    --- Returns the damage increase
    function _M:combatHasDamageIncrease(type)
    	if self.inc_damage[type] and self.inc_damage[type] ~= 0 then return true else return false end
    end
    
    --- Returns the damage increase
    function _M:combatGetDamageIncrease(type, straight)
    	local a = self.inc_damage.all or 0
    	local b = self.inc_damage[type] or 0
    	local inc = a + b
    	if straight then return inc end
    
    	if self.auto_highest_inc_damage and self.auto_highest_inc_damage[type] then
    		local highest = self.inc_damage.all or 0
    		for kind, v in pairs(self.inc_damage) do
    			if kind ~= "all" then
    				local inc = self:combatGetDamageIncrease(kind, true)
    				highest = math.max(highest, inc)
    			end
    		end
    		return highest + self.auto_highest_inc_damage[type]
    	end
    
    	return inc
    end
    
    
    dg's avatar
    dg committed
    --- Computes movement speed
    
    function _M:combatMovementSpeed(x, y)
    
    dg's avatar
    dg committed
    	local mult = 1
    
    	if game.level and game.level.data.zero_gravity then
    
    dg's avatar
    dg committed
    		mult = 3
    	end
    
    dg's avatar
    dg committed
    
    
    	local movement_speed = self.movement_speed
    	if x and y and game.level.map:checkAllEntities(x, y, "creepingDark") and self:knowTalent(self.T_DARK_VISION) then
    		local t = self:getTalentFromId(self.T_DARK_VISION)
    		movement_speed = movement_speed + t.getMovementSpeedChange(self, t)
    	end
    
    	movement_speed = math.max(movement_speed, 0.1)
    
    	return mult * (self.base_movement_speed or 1) / movement_speed
    
    dg's avatar
    dg committed
    end
    
    
    --- Computes see stealth
    function _M:combatSeeStealth()
    	local bonus = 0
    
    	if self:knowTalent(self.T_PIERCING_SIGHT) then bonus = bonus + self:callTalent(self.T_PIERCING_SIGHT,"seePower") end
    
    	if self:knowTalent(self.T_PRETERNATURAL_SENSES) then bonus = bonus + self:callTalent(self.T_PRETERNATURAL_SENSES, "sensePower") end
    
    	-- level 50 with 100 cun ==> 50
    
    	return self:combatScale(self.level/2 + self:getCun()/4 + (self:attr("see_stealth") or 0), 0, 0, 50, 50) + bonus -- Note bonus scaled separately from talents
    
    end
    
    --- Computes see invisible
    function _M:combatSeeInvisible()
    	local bonus = 0
    
    	if self:knowTalent(self.T_PIERCING_SIGHT) then bonus = bonus + self:callTalent(self.T_PIERCING_SIGHT,"seePower") end
    
    	if self:knowTalent(self.T_PRETERNATURAL_SENSES) then bonus = bonus + self:callTalent(self.T_PRETERNATURAL_SENSES, "sensePower") end
    
    	return (self:attr("see_invisible") or 0) + bonus
    end
    
    
    dg's avatar
    dg committed
    --- Check if the actor has a gem bomb in quiver
    function _M:hasAlchemistWeapon()
    	if not self:getInven("QUIVER") then return nil, "no ammo" end
    	local ammo = self:getInven("QUIVER")[1]
    	if not ammo or not ammo.alchemist_power then
    		return nil, "bad or no ammo"
    	end
    	return ammo
    end
    
    
    dg's avatar
    dg committed
    --- Check if the actor has a staff weapon
    function _M:hasStaffWeapon()
    
    dg's avatar
    dg committed
    	if self:attr("disarmed") then
    		return nil, "disarmed"
    	end
    
    
    dg's avatar
    dg committed
    	if not self:getInven("MAINHAND") then return end
    	local weapon = self:getInven("MAINHAND")[1]
    	if not weapon or weapon.subtype ~= "staff" then
    		return nil
    	end
    	return weapon
    end
    
    
    dg's avatar
    dg committed
    --- Check if the actor has an axe weapon
    function _M:hasAxeWeapon()
    	if self:attr("disarmed") then
    		return nil, "disarmed"
    	end
    
    	if not self:getInven("MAINHAND") then return end
    	local weapon = self:getInven("MAINHAND")[1]
    	if not weapon or (weapon.subtype ~= "battleaxe" and weapon.subtype ~= "waraxe") then
    		return nil
    	end
    	return weapon
    end
    
    
    --- Check if the actor has a cursed weapon
    function _M:hasCursedWeapon()
    	if self:attr("disarmed") then
    		return nil, "disarmed"
    	end
    
    	if not self:getInven("MAINHAND") then return end
    	local weapon = self:getInven("MAINHAND")[1]
    
    	if not weapon or not weapon.curse then
    
    	local t = self:getTalentFromId(self.T_DEFILING_TOUCH)
    	if not t.canCurseItem(self, t, weapon) then return nil end
    
    --- Check if the actor has a cursed weapon
    function _M:hasCursedOffhandWeapon()
    	if self:attr("disarmed") then
    		return nil, "disarmed"
    	end
    
    	if not self:getInven("OFFHAND") then return end
    	local weapon = self:getInven("OFFHAND")[1]
    
    	if not weapon or not weapon.combat or not weapon.curse then
    
    		return nil
    	end
    
    	local t = self:getTalentFromId(self.T_DEFILING_TOUCH)
    	if not t.canCurseItem(self, t, weapon) then return nil end
    
    	return weapon
    end
    
    
    --- Check if the actor has a two handed weapon
    
    dg's avatar
    dg committed
    function _M:hasTwoHandedWeapon()
    
    dg's avatar
    dg committed
    	if self:attr("disarmed") then
    		return nil, "disarmed"
    	end
    
    
    dg's avatar
    dg committed
    	if not self:getInven("MAINHAND") then return end
    
    dg's avatar
    dg committed
    	local weapon = self:getInven("MAINHAND")[1]
    	if not weapon or not weapon.twohanded then
    		return nil
    	end
    	return weapon
    end
    
    
    --- Check if the actor has a shield
    function _M:hasShield()
    
    dg's avatar
    dg committed
    	if self:attr("disarmed") then
    		return nil, "disarmed"
    	end
    
    
    dg's avatar
    dg committed
    	if not self:getInven("MAINHAND") or not self:getInven("OFFHAND") then return end
    
    	local shield = self:getInven("OFFHAND")[1]
    	if not shield or not shield.special_combat then
    		return nil
    	end
    	return shield
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    -- Check if actor is unarmed
    function _M:isUnarmed()
    	local unarmed = true
    	if not self:getInven("MAINHAND") or not self:getInven("OFFHAND") then return end
    	local weapon = self:getInven("MAINHAND")[1]
    	local offweapon = self:getInven("OFFHAND")[1]
    	if weapon or offweapon then
    		unarmed = false
    	end
    	return unarmed
    end
    
    
    -- Get the number of free hands the actor has
    function _M:getFreeHands()
    
    dg's avatar
    dg committed
    	if not self:getInven("MAINHAND") or not self:getInven("OFFHAND") then return 0 end
    
    	local weapon = self:getInven("MAINHAND")[1]
    	local offweapon = self:getInven("OFFHAND")[1]
    	if weapon and offweapon then return 0 end