Skip to content
Snippets Groups Projects
Combat.lua 67.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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 weapon.talented == "knife" and self:knowTalent(Talents.T_LETHALITY) then
    		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
    
    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)
    
    dg's avatar
    dg committed
    		if inven and inven[1] then weapon = inven[1].combat 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_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
    
    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
    
    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
    
    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