Skip to content
Snippets Groups Projects
Combat.lua 36.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • dg's avatar
    dg committed
    -- ToME - Tales of Maj'Eyal
    
    dg's avatar
    dg committed
    -- Copyright (C) 2009, 2010, 2011 Nicolas Casalini
    
    dg's avatar
    dg committed
    --
    -- This program is free software: you can redistribute it and/or modify
    -- it under the terms of the GNU General Public License as published by
    -- the Free Software Foundation, either version 3 of the License, or
    -- (at your option) any later version.
    --
    -- This program is distributed in the hope that it will be useful,
    -- but WITHOUT ANY WARRANTY; without even the implied warranty of
    -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    -- GNU General Public License for more details.
    --
    -- You should have received a copy of the GNU General Public License
    -- along with this program.  If not, see <http://www.gnu.org/licenses/>.
    --
    -- Nicolas Casalini "DarkGod"
    -- darkgod@te4.org
    
    
    dg's avatar
    dg committed
    require "engine.class"
    
    dg's avatar
    dg committed
    local DamageType = require "engine.DamageType"
    
    local Map = require "engine.Map"
    
    local Chat = require "engine.Chat"
    
    dg's avatar
    dg committed
    local Target = require "engine.Target"
    
    dg's avatar
    dg committed
    local Talents = require "engine.interface.ActorTalents"
    
    dg's avatar
    dg committed
    
    --- Interface to add ToME combat system
    module(..., package.seeall, class.make)
    
    
    --- Checks what to do with the target
    -- Talk ? attack ? displace ?
    function _M:bumpInto(target)
    	local reaction = self:reactionToward(target)
    	if reaction < 0 then
    
    		if target.encounterAttack and self.player then self:onWorldEncounter(target) return end
    
    		return self:useTalent(self.T_ATTACK, nil, nil, nil, target)
    
    	elseif reaction >= 0 then
    		-- Talk ?
    		if self.player and target.can_talk then
    
    			local chat = Chat.new(target.can_talk, target, self)
    			chat:invoke()
    
    			if target.can_talk_only_once then target.can_talk = nil end
    
    		elseif target.player and self.can_talk then
    
    			local chat = Chat.new(self.can_talk, self, target)
    			chat:invoke()
    
    			if target.can_talk_only_once then target.can_talk = nil end
    
    		elseif self.move_others and not target.cant_be_moved then
    
    			if target.move_others and self ~= game.player then return end
    
    
    			-- Displace
    
    dg's avatar
    dg committed
    			local tx, ty, sx, sy = target.x, target.y, self.x, self.y
    			target.x = nil target.y = nil
    			self.x = nil self.y = nil
    			target:move(sx, sy, true)
    			self:move(tx, ty, true)
    
    		end
    	end
    end
    
    --- Makes the death happen!
    
    dg's avatar
    dg committed
    --[[
    The ToME combat system has the following attributes:
    
    dg's avatar
    dg committed
    - attack: increases chances to hit against high defense
    - defense: increases chances to miss against high attack power
    
    dg's avatar
    dg committed
    - armor: direct reduction of damage done
    - armor penetration: reduction of target's armor
    - damage: raw damage done
    ]]
    
    function _M:attackTarget(target, damtype, mult, noenergy)
    
    dg's avatar
    dg committed
    	local speed, hit = nil, false
    
    dg's avatar
    dg committed
    	local sound, sound_miss = nil, nil
    
    dg's avatar
    dg committed
    
    
    	-- Break before we do the blow, because it might start step up, we dont want to insta-cancel it
    	self:breakStepUp()
    
    
    dg's avatar
    dg committed
    	if self:attr("feared") then
    		if not noenergy then
    			self:useEnergy(game.energy_to_act * speed)
    			self.did_energy = true
    		end
    		game.logSeen(self, "%s is too afraid to attack.", self.name:capitalize())
    		return false
    	end
    
    
    dg's avatar
    dg committed
    	-- Cancel stealth early if we are noticed
    	if self:isTalentActive(self.T_STEALTH) and target:canSee(self) then
    		self:useTalent(self.T_STEALTH)
    		self.changed = true
    		game.logPlayer(self, "%s notices you at the last moment!", target.name:capitalize())
    	end
    
    
    dg's avatar
    dg committed
    	if target and target:hasEffect(self.EFF_DOMINATED) and target.dominatedSource and target.dominatedSource == self then
    		-- target is being dominated by self
    		mult = (mult or 1) * (target.dominatedDamMult or 1)
    	end
    
    
    dg's avatar
    dg committed
    	-- Change attack type if using gems
    	if not damtype and self:getInven(self.INVEN_GEM) then
    		local gems = self:getInven(self.INVEN_GEM)
    		local types = {}
    		for i = 1, #gems do
    			if gems[i] and gems[i].attack_type then types[#types+1] = gems[i].attack_type end
    		end
    		if #types > 0 then
    			damtype = rng.table(types)
    		end
    	end
    
    
    dg's avatar
    dg committed
    	local break_stealth = false
    
    dg's avatar
    dg committed
    	if not self:attr("disarmed") and not self:isUnarmed() then
    
    dg's avatar
    dg committed
    		-- All weapons in main hands
    		if self:getInven(self.INVEN_MAINHAND) then
    			for i, o in ipairs(self:getInven(self.INVEN_MAINHAND)) do
    
    				if o.combat and not o.archery then
    
    dg's avatar
    dg committed
    					print("[ATTACK] attacking with", o.name)
    					local s, h = self:attackTargetWith(target, o.combat, damtype, mult)
    					speed = math.max(speed or 0, s)
    					hit = hit or h
    					if hit and not sound then sound = o.combat.sound
    					elseif not hit and not sound_miss then sound_miss = o.combat.sound_miss end
    
    dg's avatar
    dg committed
    					if not o.combat.no_stealth_break then break_stealth = true end
    
    dg's avatar
    dg committed
    				end
    
    dg's avatar
    dg committed
    			end
    		end
    
    dg's avatar
    dg committed
    		-- All weapons in off hands
    		-- Offhand attacks are with a damage penalty, that can be reduced by talents
    
    dg's avatar
    dg committed
    		if self:getInven(self.INVEN_OFFHAND) then
    
    dg's avatar
    dg committed
    			local offmult = self:getOffHandMult(mult)
    
    dg's avatar
    dg committed
    			for i, o in ipairs(self:getInven(self.INVEN_OFFHAND)) do
    
    				if o.combat and not o.archery then
    
    dg's avatar
    dg committed
    					print("[ATTACK] attacking with", o.name)
    					local s, h = self:attackTargetWith(target, o.combat, damtype, offmult)
    					speed = math.max(speed or 0, s)
    					hit = hit or h
    					if hit and not sound then sound = o.combat.sound
    					elseif not hit and not sound_miss then sound_miss = o.combat.sound_miss end
    
    dg's avatar
    dg committed
    					if not o.combat.no_stealth_break then break_stealth = true end
    
    dg's avatar
    dg committed
    				end
    
    dg's avatar
    dg committed
    			end
    		end
    	end
    
    	-- Barehanded ?
    
    dg's avatar
    dg committed
    	if not speed and self.combat then
    
    dg's avatar
    dg committed
    		print("[ATTACK] attacking with innate combat")
    
    dg's avatar
    dg committed
    		local s, h = self:attackTargetWith(target, self.combat, damtype, mult)
    		speed = math.max(speed or 0, s)
    		hit = hit or h
    
    dg's avatar
    dg committed
    		if hit and not sound then sound = self.combat.sound
    		elseif not hit and not sound_miss then sound_miss = self.combat.sound_miss end
    
    dg's avatar
    dg committed
    		if not self.combat.no_stealth_break then break_stealth = true end
    
    dg's avatar
    dg committed
    	end
    
    
    dg's avatar
    dg committed
    	-- Mount attack ?
    	local mount = self:hasMount()
    	if mount and mount.mount.attack_with_rider and math.floor(core.fov.distance(self.x, self.y, target.x, target.y)) <= 1 then
    		mount.mount.actor:attackTarget(target, nil, nil, nil)
    	end
    
    
    dg's avatar
    dg committed
    	-- We use up our own energy
    
    	if speed and not noenergy then
    
    dg's avatar
    dg committed
    		self:useEnergy(game.energy_to_act * speed)
    		self.did_energy = true
    	end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	if sound then game:playSoundNear(self, sound)
    	elseif sound_miss then game:playSoundNear(self, sound_miss) end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	-- cleave second attack
    	if self:knowTalent(self.T_CLEAVE) then
    		local t = self:getTalentFromId(self.T_CLEAVE)
    		t.on_attackTarget(self, t, target, multiplier)
    	end
    
    
    dg's avatar
    dg committed
    	-- Cancel stealth!
    
    dg's avatar
    dg committed
    	if break_stealth then self:breakStealth() end
    
    dg's avatar
    dg committed
    	self:breakLightningSpeed()
    
    dg's avatar
    dg committed
    	self:breakGatherTheThreads()
    
    dg's avatar
    dg committed
    	return hit
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Computes a logarithmic chance to hit, opposing chance to hit to chance to miss
    -- This will be used for melee attacks, physical and spell resistance
    
    function _M:checkHit(atk, def, min, max, factor)
    
    dg's avatar
    dg committed
    	print("checkHit", atk, def)
    
    dg's avatar
    dg committed
    	if atk == 0 then atk = 1 end
    	local hit = nil
    	factor = factor or 5
    
    
    	local one = 1 / (1 + math.exp(-(atk - def) / 7))
    	local two = 0
    
    dg's avatar
    dg committed
    	if atk + def ~= 0 then two = atk / (atk + def) end
    
    dg's avatar
    dg committed
    	hit = util.bound(hit, min or 5, max or 95)
    
    dg's avatar
    dg committed
    	print("=> chance to hit", hit)
    
    	return rng.percent(hit), hit
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Try to totally evade an attack
    
    dg's avatar
    dg committed
    function _M:checkEvasion(target)
    	if not target:attr("evasion") then return end
    
    	local evasion = target:attr("evasion")
    	print("checkEvasion", evasion, target.level, self.level)
    	evasion = evasion * (target.level / self.level)
    	print("=> evasion chance", evasion)
    	return rng.percent(evasion)
    end
    
    
    dg's avatar
    dg committed
    --- Attacks with one weapon
    
    dg's avatar
    dg committed
    function _M:attackTargetWith(target, weapon, damtype, mult)
    
    	damtype = damtype or weapon.damtype or DamageType.PHYSICAL
    
    dg's avatar
    dg committed
    	mult = mult or 1
    
    
    dg's avatar
    dg committed
    	-- Does the blow connect? yes .. complex :/
    
    	local atk, def = self:combatAttack(weapon), target:combatDefense()
    
    dg's avatar
    dg committed
    	if not self:canSee(target) then atk = atk / 3 end
    
    dg's avatar
    dg committed
    	local dam, apr, armor = self:combatDamage(weapon), self:combatAPR(weapon), target:combatArmor()
    
    	print("[ATTACK] to ", target.name, " :: ", dam, apr, armor, def, "::", mult)
    
    	if target:knowTalent(target.T_DUCK_AND_DODGE) then
    		local diff = util.bound((self.size_category or 3) - (target.size_category or 2), 0, 5)
    		def = def + diff * target:getTalentLevelRaw(target.T_DUCK_AND_DODGE) * 1.2
    	end
    
    dg's avatar
    dg committed
    
    	-- If hit is over 0 it connects, if it is 0 we still have 50% chance
    
    dg's avatar
    dg committed
    	local hitted = false
    
    	local crit = false
    
    dg's avatar
    dg committed
    	local evaded = false
    	if self:checkEvasion(target) then
    		evaded = true
    		game.logSeen(target, "%s evades %s.", target.name:capitalize(), self.name)
    	elseif self:checkHit(atk, def) then
    
    		local pres = util.bound(self:combatArmorHardiness() / 100, 0, 1)
    		print("[ATTACK] raw dam", dam, "versus", armor, pres, "with APR", apr)
    
    dg's avatar
    dg committed
    		armor = math.max(0, armor - apr)
    
    		dam = math.max(dam * pres - armor, 0) + (dam * (1 - pres))
    
    dg's avatar
    dg committed
    		print("[ATTACK] after armor", dam)
    
    dg's avatar
    dg committed
    		local damrange = self:combatDamageRange(weapon)
    		dam = rng.range(dam, dam * damrange)
    
    dg's avatar
    dg committed
    		print("[ATTACK] after range", dam)
    
    dg's avatar
    dg committed
    		dam, crit = self:physicalCrit(dam, weapon, target)
    
    dg's avatar
    dg committed
    		print("[ATTACK] after crit", dam)
    
    dg's avatar
    dg committed
    		dam = dam * mult
    		print("[ATTACK] after mult", dam)
    
    
    		if weapon.inc_damage_type then
    			for t, idt in pairs(weapon.inc_damage_type) do
    				if target.type.."/"..target.subtype == t or target.type == t then dam = dam + dam * idt / 100 break end
    			end
    			print("[ATTACK] after inc by type", dam)
    		end
    
    
    		if crit then game.logSeen(self, "%s performs a critical strike!", self.name:capitalize()) end
    
    dg's avatar
    dg committed
    		DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam))
    
    dg's avatar
    dg committed
    		hitted = true
    
    dg's avatar
    dg committed
    	else
    
    		local srcname = game.level.map.seens(self.x, self.y) and self.name:capitalize() or "Something"
    		game.logSeen(target, "%s misses %s.", srcname, target.name)
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	-- Spread diseases
    	if hitted and self:knowTalent(self.T_CARRIER) and rng.percent(4 * self:getTalentLevelRaw(self.T_CARRIER)) then
    		-- Use epidemic talent spreading
    		local t = self:getTalentFromId(self.T_EPIDEMIC)
    		t.do_spread(self, t, target)
    	end
    
    
    dg's avatar
    dg committed
    	-- Melee project
    
    	if hitted and not target.dead and weapon.melee_project then for typ, dam in pairs(weapon.melee_project) do
    		if dam > 0 then
    			DamageType:get(typ).projector(self, target.x, target.y, typ, dam)
    		end
    	end end
    
    	if hitted and not target.dead then for typ, dam in pairs(self.melee_project) do
    
    dg's avatar
    dg committed
    		if dam > 0 then
    			DamageType:get(typ).projector(self, target.x, target.y, typ, dam)
    		end
    
    dg's avatar
    dg committed
    	end end
    
    
    dg's avatar
    dg committed
    	-- Weapon of light cast
    
    	if hitted and not target.dead and self:knowTalent(self.T_WEAPON_OF_LIGHT) and self:isTalentActive(self.T_WEAPON_OF_LIGHT) then
    
    dg's avatar
    dg committed
    		local dam = 7 + self:getTalentLevel(self.T_WEAPON_OF_LIGHT) * self:combatSpellpower(0.092)
    		DamageType:get(DamageType.LIGHT).projector(self, target.x, target.y, DamageType.LIGHT, dam)
    		self:incPositive(-3)
    		if self:getPositive() <= 0 then
    
    			self:forceUseTalent(self.T_WEAPON_OF_LIGHT, {ignore_energy=true})
    
    dg's avatar
    dg committed
    		end
    	end
    
    
    dg's avatar
    dg committed
    	-- Shadow cast
    
    	if hitted and not target.dead and self:knowTalent(self.T_SHADOW_COMBAT) and self:isTalentActive(self.T_SHADOW_COMBAT) and self:getMana() > 0 then
    
    dg's avatar
    dg committed
    		local dam = 3 + self:getTalentLevel(self.T_SHADOW_COMBAT) * 2
    
    		local mana = 1 + self:getTalentLevelRaw(self.T_SHADOW_COMBAT) / 1.5
    		if self:getMana() > mana then
    			DamageType:get(DamageType.DARKNESS).projector(self, target.x, target.y, DamageType.DARKNESS, dam)
    			self:incMana(-mana)
    		end
    
    	if hitted and not target.dead and self:knowTalent(self.T_WEAPON_FOLDING) and self:isTalentActive(self.T_WEAPON_FOLDING) then
    
    		local t = self:getTalentFromId(self.T_WEAPON_FOLDING)
    		local dam = t.getDamage(self, t)
    		DamageType:get(DamageType.TEMPORAL).projector(self, target.x, target.y, DamageType.TEMPORAL, dam)
    	end
    
    
    dg's avatar
    dg committed
    	-- Autospell cast
    
    	if hitted and not target.dead and self:knowTalent(self.T_ARCANE_COMBAT) and self:isTalentActive(self.T_ARCANE_COMBAT) then
    		local t = self:getTalentFromId(self.T_ARCANE_COMBAT)
    		t.do_trigger(self, t, target)
    
    dg's avatar
    dg committed
    	end
    
    
    	-- On hit talent
    	if hitted and not target.dead and weapon.talent_on_hit and next(weapon.talent_on_hit) then
    		for tid, data in pairs(weapon.talent_on_hit) do
    			if rng.percent(data.chance) then
    
    				self:forceUseTalent(tid, {ignore_energy=true, force_target=target, force_level=data.level})
    
    	-- Shattering Impact
    	if hitted and self:attr("shattering_impact") then
    		local dam = dam * self.shattering_impact
    
    		self:project({type="ball", radius=1, selffire=false}, target.x, target.y, DamageType.PHYSICAL, dam)
    
    		self:incStamina(-15)
    	end
    
    	-- Onslaught
    	if hitted and self:attr("onslaught") then
    		local dir = util.getDir(target.x, target.y, self.x, self.y)
    		local lx, ly = util.coordAddDir(self.x, self.y, dir_sides[dir].left)
    		local rx, ry = util.coordAddDir(self.x, self.y, dir_sides[dir].right)
    		local lt, rt = game.level.map(lx, ly, Map.ACTOR), game.level.map(rx, ry, Map.ACTOR)
    
    		if target:checkHit(self:combatAttack(weapon), target:combatPhysicalResist(), 0, 95, 10) and target:canBe("knockback") then
    			target:knockback(self.x, self.y, self:attr("onslaught"))
    		end
    
    dg's avatar
    dg committed
    		if lt and lt:checkHit(self:combatAttack(weapon), lt:combatPhysicalResist(), 0, 95, 10) and lt:canBe("knockback") then
    
    			lt:knockback(self.x, self.y, self:attr("onslaught"))
    		end
    
    dg's avatar
    dg committed
    		if rt and rt:checkHit(self:combatAttack(weapon), rt:combatPhysicalResist(), 0, 95, 10) and rt:canBe("knockback") then
    
    			rt:knockback(self.x, self.y, self:attr("onslaught"))
    		end
    	end
    
    dg's avatar
    dg committed
    	-- Reactive target on hit damage
    	if hitted then for typ, dam in pairs(target.on_melee_hit) do
    
    dg's avatar
    dg committed
    		if dam > 0 then
    			DamageType:get(typ).projector(target, self.x, self.y, typ, dam)
    		end
    
    dg's avatar
    dg committed
    	end end
    
    
    dg's avatar
    dg committed
    	-- Acid splash
    	if hitted and target:knowTalent(target.T_ACID_BLOOD) then
    		local t = target:getTalentFromId(target.T_ACID_BLOOD)
    		t.do_splash(target, t, self)
    	end
    
    
    	-- Bloodbath
    	if hitted and crit and self:knowTalent(self.T_BLOODBATH) then
    		local t = self:getTalentFromId(self.T_BLOODBATH)
    		t.do_bloodbath(self, t)
    	end
    
    	-- Mortal Terror
    	if hitted and not target.dead and self:knowTalent(self.T_MORTAL_TERROR) then
    		local t = self:getTalentFromId(self.T_MORTAL_TERROR)
    		t.do_terror(self, t, target, dam)
    	end
    
    
    dg's avatar
    dg committed
    	-- Mortal Terror
    	if hitted and not target.dead and target:knowTalent(target.T_STONESKIN) and rng.percent(15) then
    		target:setEffect(target.EFF_STONE_SKIN, 5, {power=target:getTalentLevelRaw(target.T_STONESKIN)*3})
    	end
    
    
    dg's avatar
    dg committed
    	-- Conduit (Psi)
    
    	if hitted and not target.dead and self:knowTalent(self.T_CONDUIT) and self:isTalentActive(self.T_CONDUIT) and self.use_psi_combat then
    
    dg's avatar
    dg committed
    		local t =  self:getTalentFromId(self.T_CONDUIT)
    		t.do_combat(self, t, target)
    	end
    
    dg's avatar
    dg committed
    	-- Exploit Weakness
    	if hitted and not target.dead and self:knowTalent(self.T_EXPLOIT_WEAKNESS) and self:isTalentActive(self.T_EXPLOIT_WEAKNESS) then
    		local t = self:getTalentFromId(self.T_EXPLOIT_WEAKNESS)
    		t.do_weakness(self, t, target)
    	end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	-- Special effect
    	if hitted and not target.dead and weapon.special_on_hit and weapon.special_on_hit.fct then
    		weapon.special_on_hit.fct(weapon, self, target)
    	end
    
    
    dg's avatar
    dg committed
    	-- Regen on being hit
    	if hitted and not target.dead and target:attr("stamina_regen_on_hit") then target:incStamina(target.stamina_regen_on_hit) end
    	if hitted and not target.dead and target:attr("mana_regen_on_hit") then target:incMana(target.mana_regen_on_hit) end
    	if hitted and not target.dead and target:attr("equilibrium_regen_on_hit") then target:incEquilibrium(-target.equilibrium_regen_on_hit) end
    
    dg's avatar
    dg committed
    	-- Ablative Armor
    	if hitted and not target.dead and target:attr("carbon_spikes") then
    		if target.carbon_armor >= 1 then
    
    			target.carbon_armor = target.carbon_armor - 1
    
    dg's avatar
    dg committed
    		else
    			-- Deactivate without loosing energy
    			target:forceUseTalent(target.T_CARBON_SPIKES, {ignore_energy=true})
    		end
    	end
    
    	-- Riposte!
    
    dg's avatar
    dg committed
    	if not hitted and not target.dead and not evaded and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:knowTalent(target.T_RIPOSTE) and rng.percent(target:getTalentLevel(target.T_RIPOSTE) * (5 + target:getDex(5))) then
    
    		game.logSeen(self, "%s ripostes!", target.name:capitalize())
    		target:attackTarget(self, nil, nil, true)
    	end
    
    dg's avatar
    dg committed
    	-- Set Up
    	if not hitted and not target.dead and not evaded and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:hasEffect(target.EFF_DEFENSIVE_MANEUVER) then
    		local t = target:getTalentFromId(target.T_SET_UP)
    		local power = t.getPower(target, t)
    		self:setEffect(self.EFF_SET_UP, 2, {src = target, power=power})
    	end
    
    dg's avatar
    dg committed
    	-- Defensive Throw!
    	if not hitted and not target.dead and not evaded and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:knowTalent(target.T_DEFENSIVE_THROW) and rng.percent(target:getTalentLevel(target.T_DEFENSIVE_THROW) * (5 + target:getCun(5))) then
    		local t = target:getTalentFromId(target.T_DEFENSIVE_THROW)
    		t.do_throw(target, self, t)
    	end
    
    dg's avatar
    dg committed
    	-- Counter Attack!
    	if not hitted and not target.dead and not evaded and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:knowTalent(target.T_COUNTER_ATTACK) and rng.percent(target:getTalentLevel(target.T_COUNTER_ATTACK) * (5 + target:getCun(5))) then
    		game.logSeen(self, "%s counters the attack!", target.name:capitalize())
    		local t = target:getTalentFromId(target.T_COUNTER_ATTACK)
    		local damage = t.getDamage(target, t)
    		local hit = target:attackTarget(self, nil, damage, true)
    	end
    
    	-- Greater Weapon Focus
    	local gwf = self:hasEffect(self.EFF_GREATER_WEAPON_FOCUS)
    	if hitted and not target.dead and gwf and not gwf.inside and rng.percent(gwf.chance) then
    		gwf.inside = true
    		self:attackTargetWith(target, weapon, damtype, mult)
    		gwf.inside = nil
    	end
    
    
    	-- Visual feedback
    	if hitted then game.level.map:particleEmitter(target.x, target.y, 1, "melee_attack", {color=target.blood_color}) end
    
    dg's avatar
    dg committed
    	return self:combatSpeed(weapon), hitted
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    local weapon_talents = {
    
    	sword = Talents.T_WEAPONS_MASTERY,
    	axe =   Talents.T_WEAPONS_MASTERY,
    	mace =  Talents.T_WEAPONS_MASTERY,
    
    	knife = Talents.T_KNIFE_MASTERY,
    
    	whip  = Talents.T_EXOTIC_WEAPONS_MASTERY,
    	trident=Talents.T_EXOTIC_WEAPONS_MASTERY,
    
    dg's avatar
    dg committed
    	bow =   Talents.T_BOW_MASTERY,
    
    dg's avatar
    dg committed
    	sling = Talents.T_SLING_MASTERY,
    
    dg's avatar
    dg committed
    	staff = Talents.T_STAFF_MASTERY,
    
    dg's avatar
    dg committed
    }
    
    --- Checks weapon training
    function _M:combatCheckTraining(weapon)
    	if not weapon.talented then return 0 end
    
    	if not weapon_talents[weapon.talented] then return 0 end
    	return self:getTalentLevel(weapon_talents[weapon.talented])
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the defense
    function _M:combatDefense()
    
    dg's avatar
    dg committed
    	local add = 0
    	if self:hasDualWeapon() and self:knowTalent(self.T_DUAL_WEAPON_DEFENSE) then
    		add = add + 4 + (self:getTalentLevel(self.T_DUAL_WEAPON_DEFENSE) * self:getDex()) / 12
    	end
    
    dg's avatar
    dg committed
    	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_STEADY_MIND) then
    		local t = self:getTalentFromId(self.T_STEADY_MIND)
    		add = add + t.getDefense(self, t)
    	end
    
    	return self.combat_def + (self:getDex() - 10) * 0.35 + add + (self:getLck() - 50) * 0.4
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the defense ranged
    function _M:combatDefenseRanged()
    	return self:combatDefense() + (self.combat_def_ranged or 0)
    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
    		add = add + self:getTalentLevel(self.T_ARMOUR_TRAINING) * 1.4
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_PHYSICAL_CONDITIONING) then
    		local t = self:getTalentFromId(self.T_PHYSICAL_CONDITIONING)
    		add = add + t.getArmor(self, t)
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_CARBON_SPIKES) and self:isTalentActive(self.T_CARBON_SPIKES) then
    		add = add + self.carbon_armor
    	end
    
    dg's avatar
    dg committed
    	return self.combat_armor + add
    
    dg's avatar
    dg committed
    end
    
    
    --- Gets armor hardiness
    -- This is how much % of a blow we can reduce with armor
    function _M:combatArmorHardiness()
    	local add = 0
    	if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
    		add = add + self:getTalentLevel(self.T_ARMOUR_TRAINING) * 5
    	end
    	return 30 + self.combat_armor_hardiness + add
    end
    
    
    dg's avatar
    dg committed
    --- Gets the attack
    
    function _M:combatAttackBase(weapon, ammo)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    	return self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 5 + (weapon.atk or 0) + (ammo and ammo.atk or 0) + (self:getLck() - 50) * 0.4
    end
    function _M:combatAttack(weapon, ammo)
    
    dg's avatar
    dg committed
    	local stats
    	if self.use_psi_combat then stats = (self:getWil(50) - 5) + (self:getCun(50) - 5)
    	else stats = (self:getStr(50) - 5) + (self:getDex(50) - 5)
    	end
    
    	return self:combatAttackBase(weapon, ammo) + stats
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack using only strength
    
    function _M:combatAttackStr(weapon, ammo)
    	return self:combatAttackBase(weapon, ammo) + (self:getStr(100) - 10)
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack using only dexterity
    
    function _M:combatAttackDex(weapon, ammo)
    	return self:combatAttackBase(weapon, ammo) + (self:getDex(100) - 10)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the attack using only magic
    
    function _M:combatAttackMag(weapon, ammo)
    	return self:combatAttackBase(weapon, ammo) + (self:getMag(100) - 10)
    
    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 {}
    
    	if self:knowTalent(Talents.T_WEAPON_FOLDING) and self:isTalentActive(self.T_WEAPON_FOLDING) then
    
    		local t = self:getTalentFromId(self.T_WEAPON_FOLDING)
    		addapr = t.getArmorPen(self, t)
    	end
    	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
    
    	return self.combat_physcrit + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + (weapon.physcrit or 1) + addcrit
    
    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
    -- This makes low damage values equal to what they should be and puts disminishing returns to super high values
    function _M:rescaleDamage(dam)
    	if dam <= 0 then return dam end
    	return dam * (1 - math.log10(dam * 2) / 7)
    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
    
    
    	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
    
    	if self.use_psi_combat then totstat = totstat * 0.6 end
    
    dg's avatar
    dg committed
    	if self:knowTalent(Talents.T_ARCANE_DESTRUCTION) then
    		add = add + self:combatSpellpower() * self:getTalentLevel(Talents.T_ARCANE_DESTRUCTION) / 9
    	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 weapon == self.combat then
    		local t = self:getTalentFromId(self.T_EMPTY_HAND)
    		add = add + t.getDamage(self, t)
    	end
    
    dg's avatar
    dg committed
    		-- Handles unarmed mastery
    		talented_mod = math.sqrt(self:getTalentLevel(Talents.T_UNARMED_MASTERY) / 10) + 1 or 0
    
    dg's avatar
    dg committed
    		talented_mod = math.sqrt(self:combatCheckTraining(weapon) / 10) + 1
    	end
    
    dg's avatar
    dg committed
    	local power = math.max(self.combat_dam + (weapon.dam or 1) + add, 1)
    
    	power = (math.sqrt(power / 10) - 1) * 0.8 + 1
    
    	print(("[COMBAT DAMAGE] power(%f) totstat(%f) talent_mod(%f)"):format(power, totstat, talented_mod))
    
    dg's avatar
    dg committed
    	return self:rescaleDamage(totstat / 1.5 * power * talented_mod)
    
    dg's avatar
    dg committed
    end
    
    --- Gets spellpower
    function _M:combatSpellpower(mod)
    	mod = mod or 1
    
    dg's avatar
    dg committed
    	local add = 0
    	if self:knowTalent(self.T_ARCANE_DEXTERITY) then
    
    dg's avatar
    dg committed
    		add = add + (15 + self:getTalentLevel(self.T_ARCANE_DEXTERITY) * 5) * self:getDex() / 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) * 3) * self:getCun() / 100
    	end
    	if self:hasEffect(self.EFF_BLOODLUST) then
    		add = add + self:hasEffect(self.EFF_BLOODLUST).dur
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    
    
    	return (self.combat_spellpower + add + self:getMag()) * mod
    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)
    	print("[TALENT WEAPON MULT]", self:getTalentLevel(t), base, max, t2, mult)
    	return mult
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the off hand multiplier
    function _M:getOffHandMult(mult)
    	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
    	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()
    
    	return self.combat_spellcrit + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + 1
    
    dg's avatar
    dg committed
    end
    
    --- Gets spellspeed
    function _M:combatSpellSpeed()
    
    	return 1 / self.combat_spellspeed
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    --- Computes physical crit for a damage
    
    dg's avatar
    dg committed
    function _M:physicalCrit(dam, weapon, target)
    
    dg's avatar
    dg committed
    	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
    
    dg's avatar
    dg committed
    		return dam * (1.5 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7), true
    
    dg's avatar
    dg committed
    	end
    
    
    dg's avatar
    dg committed
    	if target.stalker and target.stalker == self and self:knowTalent(self.T_STALK) then
    		local t = self:getTalentFromId(self.T_STALK)
    		if rng.percent(math.min(100, 40 + self:getTalentLevel(t) * 12)) then
    			return dam * 1.5, true
    		end
    	end
    
    
    dg's avatar
    dg committed
    	local chance = self:combatCrit(weapon)
    
    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
    
    
    dg's avatar
    dg committed
    	if target:attr("combat_critical") then
    		chance = chance + target:attr("combat_critical")
    	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
    	if target:knowTalent(target.T_PROBABILITY_WEAVING) and target:isTalentActive(target.T_PROBABILITY_WEAVING) then
    
    dg's avatar
    dg committed
    		chance = chance - target:getTalentLevel(target.T_PROBABILITY_WEAVING)
    
    	if target:hasHeavyArmor() and target:knowTalent(target.T_ARMOUR_TRAINING) then
    		chance = chance - target:getTalentLevel(target.T_ARMOUR_TRAINING) * 1.9
    
    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
    
    dg's avatar
    dg committed
    		dam = dam * (1.5 + (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
    
    function _M:spellCrit(dam, add_chance)
    
    dg's avatar
    dg committed
    	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
    
    dg's avatar
    dg committed
    		return dam * (1.5 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7), true
    
    dg's avatar
    dg committed
    	end
    
    
    	local chance = self:combatSpellCrit() + (add_chance or 0)
    
    dg's avatar
    dg committed
    	local crit = false
    
    dg's avatar
    dg committed
    	print("[SPELL CRIT %]", chance)
    
    dg's avatar
    dg committed
    	if rng.percent(chance) then
    
    dg's avatar
    dg committed
    		dam = dam * (1.5 + (self.combat_critical_power or 0) / 100)
    
    dg's avatar
    dg committed
    		crit = true
    
    		game.logSeen(self, "%s's spell attains critical power!", self.name:capitalize())
    
    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
    
    
    --- Do we get hit by our own AOE ?
    function _M:spellFriendlyFire()
    
    	local chance = self:getTalentLevelRaw(self.T_SPELL_SHAPING) * 20 + (self:getLck() - 50) * 0.2
    	chance = 100 - chance
    	print("[SPELL] friendly fire chance", chance)
    	return chance
    
    --- Gets mindpower
    function _M:combatMindpower(mod)
    	mod = mod or 1
    	local add = 0
    	return (self.combat_mindpower + 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
    	return self:rescaleDamage((base + (self:getStat(stat))) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod)
    
    dg's avatar
    dg committed
    --- Gets damage based on talent, stat, and interval
    function _M:combatTalentIntervalDamage(t, stat, min, max)
    
    dg's avatar
    dg committed
    	return self:rescaleDamage(min + (1 + (self:getStat(stat) / 100) * (max / 6.5 - 1)) * self:getTalentLevel(t))
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Computes physical resistance
    function _M:combatPhysicalResist()
    
    dg's avatar
    dg committed
    	local add = 0
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_PHYSICAL_CONDITIONING) then
    		local t = self:getTalentFromId(self.T_PHYSICAL_CONDITIONING)
    		add = add + t.getPhysical(self, t)
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(self.T_POWER_IS_MONEY) then
    		add = add + util.bound(self.money / (80 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 10)
    	end
    	return self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.35 + add
    
    dg's avatar
    dg committed
    end
    
    --- Computes spell resistance
    function _M:combatSpellResist()
    
    dg's avatar
    dg committed
    	local add = 0
    	if self:knowTalent(self.T_POWER_IS_MONEY) then
    		add = add + util.bound(self.money / (60 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 10)
    	end
    	return self.combat_spellresist + (self:getMag() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    --- Computes mental resistance
    function _M:combatMentalResist()
    
    dg's avatar
    dg committed
    	local add = 0
    	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 / (60 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 10)
    	end
    	return self.combat_mentalresist + (self:getCun() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add
    
    --- 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()
    
    	return (self.base_movement_speed or 1) / self.movement_speed
    
    dg's avatar
    dg committed
    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 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
    
    
    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 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
    
    --- Check if the actor has a mount
    function _M:hasMount()
    	if not self:getInven("MOUNT") then return end
    	local mount = self:getInven("MOUNT")[1]
    	if not mount or mount.type ~= "mount" then
    		return nil
    	end
    	return mount
    end
    
    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)
    		if rng.percent(t.getChance(self, t)) then
    			power = 2
    		end
    		duration = 3 + math.ceil(t.getDuration(self, t))
    	end
    	-- Relentless Strike bonus
    	if self:hasEffect(self.EFF_RELENTLESS_STRIKES) then
    		for tid, cd in pairs(self.talents_cd) do
    			local tt = self:getTalentFromId(tid)
    			if tt.type[1]:find("^technique/") then
    				self.talents_cd[tid] = cd - 1
    			end
    		end
    	end
    
    dg's avatar
    dg committed
    	self:setEffect(self.EFF_COMBO, duration, {power=power})
    
    dg's avatar
    dg committed
    end