Skip to content
Snippets Groups Projects
Combat.lua 96.8 KiB
Newer Older
dg's avatar
dg committed
	-- Zero gravity
	if hitted and game.level.data.zero_gravity and rng.percent(util.bound(dam, 0, 100)) then
dg's avatar
dg committed
		target:knockback(self.x, self.y, math.ceil(math.log(dam)))
	end
dg's avatar
dg committed
	-- Roll with it
	if hitted and target:attr("knockback_on_hit") and not target.turn_procs.roll_with_it and rng.percent(util.bound(dam, 0, 100)) then
		local ox, oy = self.x, self.y
		game:onTickEnd(function()
			target:knockback(ox, oy, 1)
			if not target:hasEffect(target.EFF_WILD_SPEED) then target:setEffect(target.EFF_WILD_SPEED, 1, {power=200}) end
		end)
dg's avatar
dg committed
		target.turn_procs.roll_with_it = true
	end

	-- Weakness hate bonus
	if hitted and effGloomWeakness and effGloomWeakness.hateBonus or 0 > 0 then
		self:incHate(effGloomWeakness.hateBonus)
		game.logPlayer(self, "#F53CBE#You revel in attacking a weakened foe! (+%d hate)", effGloomWeakness.hateBonus)
		effGloomWeakness.hateBonus = nil
	end
	-- Rampage
	if hitted and crit then
		local eff = self:hasEffect(self.EFF_RAMPAGE)
		if eff and not eff.critHit and eff.actualDuration < eff.maxDuration and self:knowTalent(self.T_BRUTALITY) then
			game.logPlayer(self, "#F53CBE#Your rampage is invigorated by your fierce attack! (+1 duration)")
			eff.critHit = true
			eff.actualDuration = eff.actualDuration + 1
			eff.dur = eff.dur + 1
		end
	end
	-- Marked Prey
	if hitted and not target.dead and effPredator and effPredator.type == target.type then
		if effPredator.subtype == target.subtype then
			-- Anatomy stun
			if effPredator.subtypeStunChance > 0 and rng.percent(effPredator.subtypeStunChance) then
				if target:canBe("stun") then
					target:setEffect(target.EFF_STUNNED, 3, {})
				else
					game.logSeen(target, "%s resists the stun!", target.name:capitalize())
				end
			end
			-- Outmaneuver
			if effPredator.subtypeOutmaneuverChance > 0 and rng.percent(effPredator.subtypeOutmaneuverChance) then
				local t = self:getTalentFromId(self.T_OUTMANEUVER)
				target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { physicalResistChange=t.getPhysicalResistChange(self, t), statReduction=t.getStatReduction(self, t) })
			end
		else
			-- Outmaneuver
			if effPredator.typeOutmaneuverChance > 0 and rng.percent(effPredator.typeOutmaneuverChance) then
				local t = self:getTalentFromId(self.T_OUTMANEUVER)
dg's avatar
dg committed
				target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { physicalResistChange=t.getPhysicalResistChange(self, t), statReduction=t.getStatReduction(self, t) })
	if hitted and crit and target:hasEffect(target.EFF_DISMAYED) then
		target:removeEffect(target.EFF_DISMAYED)
	end
	if hitted and not target.dead then
		-- Curse of Madness: Twisted Mind
		if self.hasEffect and self:hasEffect(self.EFF_CURSE_OF_MADNESS) then
			local eff = self:hasEffect(self.EFF_CURSE_OF_MADNESS)
			local def = self.tempeffect_def[self.EFF_CURSE_OF_MADNESS]
			def.doConspirator(self, eff, target)
		end
		if target.hasEffect and target:hasEffect(target.EFF_CURSE_OF_MADNESS) then
			local eff = target:hasEffect(target.EFF_CURSE_OF_MADNESS)
			local def = target.tempeffect_def[target.EFF_CURSE_OF_MADNESS]
			def.doConspirator(target, eff, self)
		end
		-- Curse of Nightmares: Suffocate
		if self.hasEffect and self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES) then
			local eff = self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES)
			local def = self.tempeffect_def[self.EFF_CURSE_OF_NIGHTMARES]
			def.doSuffocate(self, eff, target)
		end
		if target.hasEffect and target:hasEffect(target.EFF_CURSE_OF_NIGHTMARES) then
			local eff = target:hasEffect(target.EFF_CURSE_OF_NIGHTMARES)
			local def = target.tempeffect_def[target.EFF_CURSE_OF_NIGHTMARES]
			def.doSuffocate(target, eff, self)
		end
	end
dg's avatar
dg committed

	self:fireTalentCheck("callbackOnMeleeAttack", target, hitted, crit, weapon, damtype, mult, dam)

	local hd = {"Combat:attackTargetWith", hitted=hitted, crit=crit, target=target, weapon=weapon, damtype=damtype, mult=mult, dam=dam}
	if self:triggerHook(hd) then hitted = hd.hitted end

	self.__global_accuracy_damage_bonus = nil
dg's avatar
dg committed
end

dg's avatar
dg committed
_M.weapon_talents = {
Eric Wykoff's avatar
Eric Wykoff committed
	sword =   {"T_WEAPONS_MASTERY", "T_STRENGTH_OF_PURPOSE"},
	axe =     {"T_WEAPONS_MASTERY", "T_STRENGTH_OF_PURPOSE"},
	mace =    {"T_WEAPONS_MASTERY", "T_STRENGTH_OF_PURPOSE"},
	knife =   {"T_KNIFE_MASTERY", "T_STRENGTH_OF_PURPOSE"},
DarkGod's avatar
DarkGod committed
	whip  =   "T_EXOTIC_WEAPONS_MASTERY",
	trident = "T_EXOTIC_WEAPONS_MASTERY",
Eric Wykoff's avatar
Eric Wykoff committed
	bow =     {"T_BOW_MASTERY", "T_STRENGTH_OF_PURPOSE"},
DarkGod's avatar
DarkGod committed
	sling =   {"T_SLING_MASTERY", "T_SKIRMISHER_SLING_SUPREMACY"},
DarkGod's avatar
DarkGod committed
	staff =   "T_STAFF_MASTERY",
	mindstar ="T_PSIBLADES",
	dream =   "T_DREAM_CRUSHER",
	unarmed = "T_UNARMED_MASTERY",
dg's avatar
dg committed
}

-- Training Talents can have the following fields:
-- getMasteryPriority(self, t, kind) - Only the talent with the highest is used. Defaults to the talent level.
-- getDamage(self, t, kind) - Extra physical power granted. Defaults to level * 10.
-- getPercentInc(self, t, kind) - Percentage increase to damage overall. Defaults to sqrt(level / 5) / 2.
function _M:addCombatTraining(kind, tid)
	local wt = _M.weapon_talents
	if not wt[kind] then wt[kind] = tid return end

	if type(wt[kind]) == "table" then
		wt[kind][#wt[kind]+1] = tid
	else
		wt[kind] = { wt[kind] }
		wt[kind][#wt[kind]+1] = tid
	end
end

--- Checks weapon training
function _M:combatGetTraining(weapon)
	if not weapon then return nil end
	if not weapon.talented then return nil end
	if not _M.weapon_talents[weapon.talented] then return nil end
	if type(_M.weapon_talents[weapon.talented]) == "table" then
		local get_priority = function(tid)
			local t = self:getTalentFromId(tid)
			if t.getMasteryPriority then return util.getval(t.getMasteryPriority, self, t, weapon.talented) end
			return self:getTalentLevel(t)
		end
		local max_tid -- = _M.weapon_talents[weapon.talented][1]
		local max_priority = -math.huge -- get_priority(max_tid)
		for _, tid in ipairs(_M.weapon_talents[weapon.talented]) do
				local priority = get_priority(tid)
				if priority > max_priority then
					max_tid = tid
					max_priority = priority
		return self:getTalentFromId(max_tid)
	else
		return self:getTalentFromId(_M.weapon_talents[weapon.talented])
	end
-- Gets the added damage for a weapon based on training.
function _M:combatTrainingDamage(weapon)
	local t = self:combatGetTraining(weapon)
	if not t then return 0 end
	if t.getDamage then return util.getval(t.getDamage, self, t, weapon.talented) end
	return self:getTalentLevel(t) * 10
end

-- Gets the percent increase for a weapon based on training.
function _M:combatTrainingPercentInc(weapon)
	local t = self:combatGetTraining(weapon)
	if not t then return 0 end
	if t.getPercentInc then return util.getval(t.getPercentInc, self, t, weapon.talented) end
	return math.sqrt(self:getTalentLevel(t) / 5) / 2
dg's avatar
dg committed
end

dg's avatar
dg committed
--- Gets the defense
--- Fake denotes a check not actually being made, used by character sheets etc.
dg's avatar
dg committed
function _M:combatDefenseBase(fake)
dg's avatar
dg committed
	local add = 0
DarkGod's avatar
DarkGod committed
	if not self:attr("encased_in_ice") then
		if self:hasDualWeapon() and self:knowTalent(self.T_DUAL_WEAPON_DEFENSE) then
			add = add + self:callTalent(self.T_DUAL_WEAPON_DEFENSE,"getDefense")
		end
		if not fake then
			add = add + (self:checkOnDefenseCall("defense") or 0)
		end
		if self:knowTalent(self.T_TACTICAL_EXPERT) then
			local t = self:getTalentFromId(self.T_TACTICAL_EXPERT)
			add = add + t.do_tact_update(self, t)
		end
		if self:knowTalent(self.T_CORRUPTED_SHELL) then
			add = add + self:getCon() / 3
		end
		if self:knowTalent(self.T_STEADY_MIND) then
			local t = self:getTalentFromId(self.T_STEADY_MIND)
			add = add + t.getDefense(self, t)
		end
		if self:isTalentActive(Talents.T_SURGE) then
			local t = self:getTalentFromId(self.T_SURGE)
			add = add + t.getDefenseChange(self, t)
		end
	local d = math.max(0, self.combat_def + (self:getDex() - 10) * 0.35 + (self:getLck() - 50) * 0.4)
	local mult = 1
dg's avatar
dg committed
	if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
		mult = mult + self:callTalent(self.T_MOBILE_DEFENCE,"getDef")
dg's avatar
dg committed
	end

dg's avatar
dg committed
	if self:knowTalent(self.T_MISDIRECTION) then
		mult = mult + self:callTalent(self.T_MISDIRECTION,"getDefense")/100
dg's avatar
dg committed
	end
	return math.max(0, d * mult + add) -- Add bonuses last to avoid compounding defense multipliers from talents
dg's avatar
dg committed
end

dg's avatar
dg committed
--- Gets the defense ranged
function _M:combatDefense(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 + (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_ARMOUR_OF_SHADOWS) and not game.level.map.lites(self.x, self.y) then
		add = add + self:callTalent(self.T_ARMOUR_OF_SHADOWS,"ArmourBonus")
dg's avatar
dg committed
	end
Eric Wykoff's avatar
Eric Wykoff committed
	if self:knowTalent(self.T_CARBON_SPIKES) and self:isTalentActive(self.T_CARBON_SPIKES) then
		add = add + self.carbon_armor
	end
DarkGod's avatar
DarkGod committed
	if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then add = add + self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getArmorBoost") 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
Eric Wykoff's avatar
Eric Wykoff committed
	local multi = 1
DarkGod's avatar
DarkGod committed
	if self:knowTalent(self.T_SKIRMISHER_BUCKLER_EXPERTISE) then
		add = add + self:callTalent(self.T_SKIRMISHER_BUCKLER_EXPERTISE, "getHardiness")
	end
	if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
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
Eric Wykoff's avatar
Eric Wykoff committed
	if self:hasEffect(self.EFF_BREACH) then
		multi = 0.5
	end
	return util.bound(30 + self.combat_armor_hardiness + add, 0, 100) * multi
dg's avatar
dg committed
--- Gets the attack
function _M:combatAttackBase(weapon, ammo)
dg's avatar
dg committed
	weapon = weapon or self.combat or {}
DarkGod's avatar
DarkGod committed
	local atk = 4 + self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 10 + (weapon.atk or 0) + (ammo and ammo.atk or 0) + (self:getLck() - 50) * 0.4

	if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then atk = atk + self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getDamBoost", weapon) end

	return atk
end
function _M:combatAttack(weapon, ammo)
dg's avatar
dg committed
	local stats
	if self:attr("use_psi_combat") then stats = (self:getCun(100, true) - 10) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
	elseif weapon and weapon.wil_attack then stats = self:getWil(100, true) - 10
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
DarkGod's avatar
DarkGod committed
	if self:attr("scoured") then d = d / 1.2 end
dg's avatar
dg committed
	return self:rescaleCombatStats(d)
dg's avatar
dg committed
end

dg's avatar
dg committed
function _M:combatAttackRanged(weapon, ammo)
	local stats
	if self:attr("use_psi_combat") then stats = (self:getCun(100, true) - 10) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
dg's avatar
dg committed
	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
DarkGod's avatar
DarkGod committed
	if self:attr("scoured") then d = d / 1.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
DarkGod's avatar
DarkGod committed
	if self:attr("scoured") then d = d / 1.2 end
dg's avatar
dg committed
	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
DarkGod's avatar
DarkGod committed
	if self:attr("scoured") then d = d / 1.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 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
DarkGod's avatar
DarkGod committed
	if self:attr("scoured") then d = d / 1.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 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, add)
dg's avatar
dg committed
	weapon = weapon or self.combat or {}
	return (weapon.physspeed or 1) / math.max(self.combat_physspeed + (add or 0), 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
Alex Ksandra's avatar
Alex Ksandra committed
	if self:knowTalent(Talents.T_ARCANE_MIGHT) then
DarkGod's avatar
DarkGod committed
		addcrit = addcrit + 0.25 * self.combat_spellcrit
Alex Ksandra's avatar
Alex Ksandra committed
	end
	local crit = self.combat_physcrit + (self.combat_generic_crit or 0) + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + (weapon.physcrit or 1) + addcrit
	return math.max(crit, 0) -- note: crit > 100% may be offset by crit reduction elsewhere
dg's avatar
dg committed
end

dg's avatar
dg committed
--- Gets the damage range
function _M:combatDamageRange(weapon, add)
dg's avatar
dg committed
	weapon = weapon or self.combat or {}
Alex Ksandra's avatar
Alex Ksandra committed
	return (self.combat_damrange or 0) + (weapon.damrange or (1.1 - (add or 0))) + (add or 0)
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
DarkGod's avatar
DarkGod committed
--	returns (limit - add)*x/(x + halfpoint) + add (= add when x = 0 and limit when x = infinity)
dg's avatar
dg committed
-- 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
DarkGod's avatar
DarkGod committed
--		local halfpoint = (p-m)/(y_high - y_low)
--		local add = (limit*(x_high*y_low-x_low*y_high) + y_high*y_low*(x_low-x_high))/(p-m)
--		return (limit-add)*x/(x + halfpoint) + add
		local ah = (limit*(x_high*y_low-x_low*y_high)+ y_high*y_low*(x_low-x_high))/(y_high - y_low) -- add*halfpoint product calculated at once to avoid possible divide by zero
		return (limit*x + ah)/(x + (p-m)/(y_high - y_low)) --factored version of above formula
dg's avatar
dg committed
--		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
dg's avatar
dg committed
		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
dg's avatar
dg committed
		return math.max(0, m * (stat + shift)^power + b + add)
--		return math.max(0, m * (stat + shift)^power + b + add), m, b
	end
end

-- Compute a diminishing returns value based on talent level that cannot go beyond a limit
-- t = talent def table or a numeric value
-- limit = value approached as talent levels increase
-- high = value at talent level 5
-- low = value at talent level 1 (optional)
-- raw if true specifies use of raw talent level
--	returns (limit - add)*TL/(TL + halfpoint) + add == add when TL = 0 and limit when TL = infinity
-- TL = talent level, halfpoint and add are internally computed to match the desired high/low values
-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
function _M:combatTalentLimit(t, limit, low, high, raw)
	local x_low, x_high = 1,5 -- Implied talent levels for low and high values respectively
	local tl = type(t) == "table" and (raw and self:getTalentLevelRaw(t) or self:getTalentLevel(t)) or t
	if low then
		local p = limit*(x_high-x_low)
		local m = x_high*high - x_low*low
--		local halfpoint = (p-m)/(high - low) -- point at which half progress towards the limit is reached
--		local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
		local ah = (limit*(x_high*low-x_low*high)+ high*low*(x_low-x_high))/(high - low) -- add*halfpoint product calculated at once to avoid possible divide by zero
		return (limit*tl + ah)/(tl + (p-m)/(high - low)) --factored version of above formula
dg's avatar
dg committed
--		return (limit-add)*tl/(tl + halfpoint) + add, halfpoint, add
	else -- assume low and x_low are both 0
		local halfpoint = limit*x_high/high-x_high
		return limit*tl/(tl + halfpoint)
dg's avatar
dg committed
--		return (limit-add)*tl/(tl + halfpoint) + add, halfpoint, add
	end
end

-- Compute a diminishing returns value based on a stat value that cannot go beyond a limit
-- stat == "str", "con",.... or a numeric value
-- limit = value approached as talent levels increase
-- high = value to match when stat = 100
-- low = value to match when stat = 10 (optional)
--	returns (limit - add)*stat/(stat + halfpoint) + add == add when STAT = 0 and limit when stat = infinity
-- halfpoint and add are internally computed to match the desired high/low values
-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
function _M:combatStatLimit(stat, limit, low, high)
	local x_low, x_high = 10,100 -- Implied stat levels for low and high values respectively
dg's avatar
dg committed
	stat = type(stat) == "string" and self:getStat(stat,nil,true) or stat
	if low then
		local p = limit*(x_high-x_low)
		local m = x_high*high - x_low*low
--		local halfpoint = (p-m)/(high - low) -- point at which half progress towards the limit is reached
--		local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
		local ah = (limit*(x_high*low-x_low*high)+ high*low*(x_low-x_high))/(high - low) -- add*halfpoint product calculated at once to avoid possible divide by zero
		return (limit*stat + ah)/(stat + (p-m)/(high - low)) --factored version of above formula
dg's avatar
dg committed
--		return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
	else -- assume low and x_low are both 0
		local halfpoint = limit*x_high/high-x_high
		return limit*stat/(stat + halfpoint)
dg's avatar
dg committed
--		return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
	end
end

--- Gets the dammod table for a given weapon.
function _M:getDammod(combat)
	combat = combat or self.combat or {}

	local dammod = table.clone(combat.dammod or {str = 0.6}, true)

	local sub = function(from, to)
		dammod[to] = (dammod[from] or 0) + (dammod[to] or 0)
		dammod[from] = nil
	end

	if combat.talented == 'knife' and self:knowTalent('T_LETHALITY') then sub('str', 'cun') end
	if combat.talented and self:knowTalent('T_STRENGTH_OF_PURPOSE') then sub('str', 'mag') end
	if self:attr 'use_psi_combat' then
			dammod['str'] = (dammod['str'] or 0) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
		if dammod['dex'] then 
			dammod['dex'] = (dammod['dex'] or 0) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
	end

	-- Add stuff like lethality here.
	local hd = {"Combat:getDammod:subs", combat=combat, dammod=dammod, sub=sub}
	if self:triggerHook(hd) then dammod = hd.dammod end

	local add = function(stat, val)
		dammod[stat] = (dammod[stat] or 0) + val
	end

	if self:knowTalent(self.T_SUPERPOWER) then add('wil', 0.3) end
	if self:knowTalent(self.T_ARCANE_MIGHT) then add('mag', 0.5) end

	return dammod
end

-- Calculate combat damage for a weapon (with an optional damage field for ranged)
-- Talent bonuses are always based on the base weapon
function _M:combatDamage(weapon, adddammod, damage)
dg's avatar
dg committed
	weapon = weapon or self.combat or {}
	local dammod = self:getDammod(damage or weapon)
	for stat, mod in pairs(dammod) do
		totstat = totstat + self:getStat(stat) * mod
dg's avatar
dg committed
	end
	if adddammod then
		for stat, mod in pairs(adddammod) do
			totstat = totstat + self:getStat(stat) * mod
		end
	end
	local talented_mod = 1 + self:combatTrainingPercentInc(weapon)
	local power = self:combatDamagePower(damage or weapon)
	return self:rescaleDamage(0.3*(self:combatPhysicalpower(nil, weapon) + totstat) * power * talented_mod)
dg's avatar
dg committed
end

--- Gets the 'power' portion of the damage
function _M:combatDamagePower(weapon_combat, add)
DarkGod's avatar
DarkGod committed
	if not weapon_combat then return 1 end
	local power = math.max((weapon_combat.dam or 1) + (add or 0), 1)
DarkGod's avatar
DarkGod committed

	if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then power = power + self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getDamBoost", weapon_combat) end

	return (math.sqrt(power / 10) - 1) * 0.5 + 1
end

function _M:combatPhysicalpower(mod, weapon, add)
dg's avatar
dg committed
	mod = mod or 1

	if self.combat_generic_power then
		add = add + self.combat_generic_power
	end
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
	if self:knowTalent(self.T_BLOODY_BUTCHER) then
		add = add + self:callTalent(Talents.T_BLOODY_BUTCHER, "getDam")
	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 + self:combatTrainingDamage(weapon)
Eric Wykoff's avatar
Eric Wykoff committed
	local str = self:getStr()
	local d = math.max(0, (self.combat_dam or 0) + add + str) -- allows strong debuffs to offset strength
dg's avatar
dg committed
	if self:attr("dazed") then d = d / 2 end
DarkGod's avatar
DarkGod committed
	if self:attr("scoured") then d = d / 1.2 end
	if self:attr("hit_penalty_2h") then d = d * (1 - math.max(0, 20 - (self.size_category - 4) * 5) / 100) end

Alex Ksandra's avatar
Alex Ksandra committed
	if self:knowTalent(self.T_ARCANE_MIGHT) then
Alex Ksandra's avatar
Alex Ksandra committed
		return self:combatSpellpower(mod, d) -- will do the rescaling and multiplying for us
	else
		return self:rescaleCombatStats(d) * mod
Alex Ksandra's avatar
Alex Ksandra committed
	end
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.combat_generic_power then
		add = add + self.combat_generic_power
	end
	if self:knowTalent(self.T_ARCANE_CUNNING) then
		add = add + self:callTalent(self.T_ARCANE_CUNNING,"getSpellpower") * self:getCun() / 100
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 = math.max(0, (self.combat_spellpower or 0) + add + self:getMag())
dg's avatar
dg committed
	if self:attr("dazed") then d = d / 2 end
DarkGod's avatar
DarkGod committed
	if self:attr("scoured") then d = d / 1.2 end
	if self:attr("hit_penalty_2h") then d = d * (1 - math.max(0, 20 - (self.size_category - 4) * 5) / 100) end

	return self:rescaleCombatStats(d) * mod * am
end

--- Gets damage based on talent
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)
	if combat and combat.range and not combat.dam then return mult or 1 end --no penalty for ranged shooters
	local offmult = 1/2
	-- Take the bigger multiplier from Dual weapon training and Corrupted Strength
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()
DarkGod's avatar
DarkGod committed
	local min = self.min_fatigue or 0
DarkGod's avatar
DarkGod committed
	local fatigue = self.fatigue
DarkGod's avatar
DarkGod committed

DarkGod's avatar
DarkGod committed
	if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then fatigue = fatigue - self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getFatigueBoost") end

DarkGod's avatar
DarkGod committed
	if fatigue < min then return min end
	if self:knowTalent(self.T_NO_FATIGUE) then return min end
DarkGod's avatar
DarkGod committed
	return fatigue
dg's avatar
dg committed
end

dg's avatar
dg committed
--- Gets spellcrit
function _M:combatSpellCrit()
	local crit = self.combat_spellcrit + (self.combat_generic_crit or 0) + (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.combat_generic_crit or 0) + (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 chance = self:combatCrit(weapon) + (add_chance or 0)
	crit_power_add = crit_power_add or 0
dg's avatar
dg committed

	if target and target:hasEffect(target.EFF_DISMAYED) then
dg's avatar
dg committed
		chance = 100
	end

dg's avatar
dg committed
	local crit = false
	if self:knowTalent(self.T_BACKSTAB) and target and target:attr("stunned") then chance = chance + self:callTalent(self.T_BACKSTAB,"getCriticalChance") end
dg's avatar
dg committed

	if target and target:attr("combat_crit_vulnerable") then
		chance = chance + target:attr("combat_crit_vulnerable")
dg's avatar
dg committed
	end
	if target and target:hasEffect(target.EFF_SET_UP) then
dg's avatar
dg committed
		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
Eric Wykoff's avatar
Eric Wykoff committed
	if target and self:hasEffect(self.EFF_WARDEN_S_FOCUS) then
		local eff = self:hasEffect(self.EFF_WARDEN_S_FOCUS)
		if eff and eff.target == target then
			chance = chance + eff.power
			crit_power_add = crit_power_add + (eff.power/100)
		end
	end
	if target then
		chance = chance - target:combatCritReduction()
	end
dg's avatar
dg committed
	-- Scoundrel's Strategies
	if self:attr("cut") and target and target:knowTalent(self.T_SCOUNDREL) then
		chance = chance - target:callTalent(target.T_SCOUNDREL,"getCritPenalty")
	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and target and not target:canSee(self) then -- bug fix
		self.turn_procs.shadowstrike_crit = self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
		crit_power_add = crit_power_add + self.turn_procs.shadowstrike_crit
	if self:isAccuracyEffect(weapon, "axe") then
		local bonus = self:getAccuracyEffect(weapon, atk, def, 0.2, 10)
		print("[PHYS CRIT %] axe accuracy bonus", atk, def, "=", bonus)
		chance = chance + bonus
	end

dg's avatar
dg committed
	chance = util.bound(chance, 0, 100)

	print("[PHYS CRIT %]", self.turn_procs.auto_phys_crit and 100 or chance)
	if self.turn_procs.auto_phys_crit or rng.percent(chance) then
		if target and target:hasEffect(target.EFF_OFFGUARD) then
			crit_power_add = crit_power_add + 0.1
dg's avatar
dg committed
		end

		if self:isAccuracyEffect(weapon, "sword") then
			local bonus = self:getAccuracyEffect(weapon, atk, def, 0.004, 0.25)
			print("[PHYS CRIT %] sword accuracy bonus", atk, def, "=", bonus)
			crit_power_add = crit_power_add + bonus
		end

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
		self:fireTalentCheck("callbackOnCrit", "physical", dam, chance, target)
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
	-- Unlike physical crits we can't know anything about our target here so we can't check if they can see us
	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) then
		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
		self.turn_procs.shadowstrike_crit = self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
	print("[SPELL CRIT %]", self.turn_procs.auto_spell_crit and 100 or chance)
	if self.turn_procs.auto_spell_crit or rng.percent(chance) then
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

		self:fireTalentCheck("callbackOnCrit", "spell", dam, chance)
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:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) then
		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
		self.turn_procs.shadowstrike_crit = self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
	print("[MIND CRIT %]", self.turn_procs.auto_mind_crit and 100 or chance)
	if self.turn_procs.auto_mind_crit or rng.percent(chance) then
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

		self:fireTalentCheck("callbackOnCrit", "mind", dam, chance)
	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.combat_generic_power then
		add = add + self.combat_generic_power
	end

	if self:knowTalent(self.T_SUPERPOWER) then
		add = add + 50 * self:getStr() / 100
	if self:knowTalent(self.T_GESTURE_OF_POWER) then