Skip to content
Snippets Groups Projects
Combat.lua 67.7 KiB
Newer Older
	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) / self.combat_physspeed
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
dg's avatar
dg committed
		addcrit = 1 + self:getTalentLevel(Talents.T_LETHALITY) * 1.3
	end
	local crit = self.combat_physcrit + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + (weapon.physcrit or 1) + addcrit
dg's avatar
dg committed
	return util.bound(crit, 0, 100)
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
--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 = 5 -- 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
dg's avatar
dg committed
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:getTalentLevel(Talents.T_ARCANE_DESTRUCTION) / 7
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 = (self.combat_dam > 0 and self.combat_dam or 0) + add + self:getStr()
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 + (15 + self:getTalentLevel(self.T_ARCANE_CUNNING) * 5) * self:getCun() / 100
dg's avatar
dg committed
	end
dg's avatar
dg committed
	if self:knowTalent(self.T_SHADOW_CUNNING) then
dg's avatar
dg committed
		add = add + (15 + self:getTalentLevel(self.T_SHADOW_CUNNING) * 5) * self:getCun() / 100
dg's avatar
dg committed
	end
	if self:hasEffect(self.EFF_BLOODLUST) then
		add = add + self:hasEffect(self.EFF_BLOODLUST).dur
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)
dg's avatar
dg committed
	local offmult = (mult or 1) / 2
	if self:knowTalent(Talents.T_DUAL_WEAPON_TRAINING) then
dg's avatar
dg committed
		offmult = math.max(offmult, (mult or 1) / (2 - (math.min(self:getTalentLevel(Talents.T_DUAL_WEAPON_TRAINING), 8) / 6)))
	end
	if self:knowTalent(Talents.T_CORRUPTED_STRENGTH) then
		offmult = math.max(offmult, (mult or 1) / (2 - (math.min(self:getTalentLevel(Talents.T_CORRUPTED_STRENGTH), 8) / 9)))
dg's avatar
dg committed
	end
	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()
dg's avatar
dg committed
end
dg's avatar
dg committed

dg's avatar
dg committed
-- Gets mental speed
function _M:combatMindSpeed()
	return 1 / self.combat_mindspeed
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
dg's avatar
dg committed
	if self:knowTalent(self.T_BACKSTAB) and target:attr("stunned") then chance = chance + self:getTalentLevel(self.T_BACKSTAB) * 10 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 - (5 + (target:getTalentLevel(self.T_SCOUNDREL)*5))
	end

	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
		chance = 100
		crit_power_add = crit_power_add + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7
	end

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
	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
		chance = 100
		crit_power_add = crit_power_add + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7
	end

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

	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
		chance = 100
		crit_power_add = crit_power_add + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7
	end

	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 + 25 * self:getStr() / 100
	end

	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 + util.bound(self.money / (90 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 7)
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 + util.bound(self.money / (90 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 7)
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 + util.bound(self.money / (90 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 7)
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
	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 = (self.resists.all or 0) / 100
	local b = (self.resists[type] or 0) / 100
	local r = math.min(100 * (1 - (1 - a) * (1 - b)), (self.resists_cap.all or 0) + (self.resists_cap[type] or 0))
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
	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 + 5 + self:getTalentLevel(self.T_PIERCING_SIGHT) * self:getCun(15, true) end
	return self.level / 2 + self:getCun(25, true) + (self:attr("see_stealth") or 0) + bonus
end

--- Computes see invisible
function _M:combatSeeInvisible()
	local bonus = 0
	if self:knowTalent(self.T_PIERCING_SIGHT) then bonus = bonus + 5 + self:getTalentLevel(self.T_PIERCING_SIGHT) * self:getCun(15, true) 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
dg's avatar
dg committed
	if weapon and weapon.twohanded then return 0 end
dg's avatar
dg committed
--- Check if the actor dual wields
function _M:hasDualWeapon()
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 weapon = self:getInven("MAINHAND")[1]
	local offweapon = self:getInven("OFFHAND")[1]
	if not weapon or not offweapon or not weapon.combat or not offweapon.combat then
		return nil
	end
	return weapon, offweapon
end
dg's avatar
dg committed

--- Check if the actor uses psiblades
function _M:hasPsiblades(main, off)
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

	local weapon, offweapon = nil, nil
	if main then
		if not self:getInven("MAINHAND") then return end
		weapon = self:getInven("MAINHAND")[1]
		if not weapon or not weapon.combat or not weapon.psiblade_active then return nil, "unactivated psiblade" end
	end
	if off then
		if not self:getInven("OFFHAND") then return end
		offweapon = self:getInven("OFFHAND")[1]
		if not offweapon or not offweapon.combat or not offweapon.psiblade_active then return nil, "unactivated psiblade" end
dg's avatar
dg committed
--- Check if the actor has a light armor
function _M:hasLightArmor()
	if not self:getInven("BODY") then return end
	local armor = self:getInven("BODY")[1]
	if not armor or (armor.subtype ~= "cloth" and armor.subtype ~= "light") then
		return nil
	end
	return armor
end

dg's avatar
dg committed
--- Check if the actor has a heavy armor
function _M:hasHeavyArmor()
	if not self:getInven("BODY") then return end
	local armor = self:getInven("BODY")[1]
	if not armor or (armor.subtype ~= "heavy" and armor.subtype ~= "massive") then
dg's avatar
dg committed
		return nil
	end
dg's avatar
dg committed
	return armor
dg's avatar
dg committed
end

--- Check if the actor has a massive armor
function _M:hasMassiveArmor()
	if not self:getInven("BODY") then return end
	local armor = self:getInven("BODY")[1]
	if not armor or armor.subtype ~= "massive" then
		return nil
	end
dg's avatar
dg committed
	return armor
dg's avatar
dg committed
end
dg's avatar
dg committed

dg's avatar
dg committed
-- Unarmed Combat; this handles grapple checks and building combo points
-- Builds Comob; reduces the cooldown on all unarmed abilities on cooldown by one
function _M:buildCombo()
	local duration = 3
	local power = 1
	-- Combo String bonuses
	if self:knowTalent(self.T_COMBO_STRING) then
		local t = self:getTalentFromId(self.T_COMBO_STRING)
dg's avatar
dg committed
		if rng.percent(t.getChance(self, t)) then
			power = 2
		end
dg's avatar
dg committed
		duration = 3 + t.getDuration(self, t)
	end
dg's avatar
dg committed

dg's avatar
dg committed
	if self:knowTalent(self.T_RELENTLESS_STRIKES) then
		local t = self:getTalentFromId(self.T_RELENTLESS_STRIKES)
dg's avatar
dg committed
		self:incStamina(t.getStamina(self, t))
dg's avatar
dg committed
	end
dg's avatar
dg committed
	self:setEffect(self.EFF_COMBO, duration, {power=power})
end

function _M:getCombo(combo)
	local combo = 0
	local p = self:hasEffect(self.EFF_COMBO)
dg's avatar
dg committed
		combo = p.cur_power
	end
		return combo
end

function _M:clearCombo()
	if self:hasEffect(self.EFF_COMBO) then
		self:removeEffect(self.EFF_COMBO)
	end
end

-- Check to see if the target is already being grappled; many talents have extra effects on grappled targets
function _M:isGrappled(source)
	local p = self:hasEffect(self.EFF_GRAPPLED)
	if p and p.src == source then
dg's avatar
dg committed
		return true
	else
		return false
	end
end

-- Breaks active grapples; called by a few talents that involve a lot of movement
function _M:breakGrapples()
	if self:hasEffect(self.EFF_GRAPPLING) then
		local p = self:hasEffect(self.EFF_GRAPPLING)
		if p.trgt then
			p.trgt:removeEffect(p.trgt.EFF_GRAPPLED)
		end
dg's avatar
dg committed
		self:removeEffect(self.EFF_GRAPPLING)
	end
end

-- grapple size check; compares attackers size and targets size
function _M:grappleSizeCheck(target)
	size = target.size_category - self.size_category
	if size > 1 then
		game.logSeen(target, "%s fails because %s is too big!", self.name:capitalize(), target.name:capitalize())
		return true
	else
		return false
	end
end

-- Starts the grapple
function _M:startGrapple(target)
	-- pulls boosted grapple effect from the clinch talent if known
	if self:knowTalent(self.T_CLINCH) then
		local t = self:getTalentFromId(self.T_CLINCH)
		power = t.getPower(self, t)
		duration = t.getDuration(self, t)
		hitbonus = self:getTalentLevel(t)/2
	else
		power = 5
		duration = 4
		hitbonus = 0
	end
	-- Breaks the grapple before reapplying
	if self:hasEffect(self.EFF_GRAPPLING) then
		self:removeEffect(self.EFF_GRAPPLING, true)
		target:setEffect(target.EFF_GRAPPLED, duration, {src=self, power=power}, true)
		self:setEffect(self.EFF_GRAPPLING, duration, {trgt=target}, true)
dg's avatar
dg committed
		return true
dg's avatar
dg committed
		target:setEffect(target.EFF_GRAPPLED, duration, {src=self, power=power, apply_power=self:combatPhysicalpower()})
		target:crossTierEffect(target.EFF_GRAPPLED, self:combatPhysicalpower())
		self:setEffect(self.EFF_GRAPPLING, duration, {trgt=target})
dg's avatar
dg committed
		return true
	else
		game.logSeen(target, "%s resists the grapple!", target.name:capitalize())
		return false
	end