Skip to content
Snippets Groups Projects
Combat.lua 60.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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)
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats((self.combat_dam > 0 and self.combat_dam or 0) + add + self:getStr()) * 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
    
    	return self:rescaleCombatStats((self.combat_spellpower > 0 and self.combat_spellpower or 0) + add + self:getMag()) * 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
    
    		offmult = (mult or 1) / (2 - (math.min(self:getTalentLevel(Talents.T_DUAL_WEAPON_TRAINING), 8) / 6))
    
    dg's avatar
    dg committed
    	elseif self:knowTalent(Talents.T_CORRUPTED_STRENGTH) then
    
    		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
    	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
    		crit_reduction = crit_reduction + self:getTalentLevel(self.T_ARMOUR_TRAINING) * 1.9
    	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
    	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
    	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()
    
    	if target:hasEffect(target.EFF_DISMAYED) then
    		chance = 100
    	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
    		dam = dam * (1.5 + crit_power_add + (self.combat_critical_power or 0) / 100)
    
    dg's avatar
    dg committed
    		crit = true
    
    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)
    	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
    
    		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("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)
    
    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)
    	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
    
    		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
    
    	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
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats((self.combat_mindpower > 0 and self.combat_mindpower or 0) + add + self:getWil() * 0.7 + self:getCun() * 0.4) * 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 total = self:rescaleCombatStats(self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.35 + add)
    
    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 total = self:rescaleCombatStats(self.combat_spellresist + (self:getMag() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add)
    
    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
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats(self.combat_mentalresist + (self:getCun() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add)
    
    -- 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 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))
    	return r
    end
    
    
    dg's avatar
    dg committed
    --- Computes movement speed
    
    function _M:combatMovementSpeed(x, y)
    
    dg's avatar
    dg committed
    	local mult = 1
    
    	if game.level and game.level.data.zero_gravity then
    
    dg's avatar
    dg committed
    		mult = 3
    	end
    
    dg's avatar
    dg committed
    
    
    	local movement_speed = self.movement_speed
    	if x and y and game.level.map:checkAllEntities(x, y, "creepingDark") and self:knowTalent(self.T_DARK_VISION) then
    		local t = self:getTalentFromId(self.T_DARK_VISION)
    		movement_speed = movement_speed + t.getMovementSpeedChange(self, t)
    	end
    	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