Skip to content
Snippets Groups Projects
Combat.lua 17.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • dg's avatar
    dg committed
    -- ToME - Tales of Middle-Earth
    -- Copyright (C) 2009, 2010 Nicolas Casalini
    --
    -- 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"
    
    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
    		return self:attackTarget(target)
    	elseif reaction >= 0 then
    		-- Talk ?
    		if self.player and target.can_talk then
    			-- TODO: implement !
    		elseif target.player and self.can_talk then
    
    dg's avatar
    dg committed
    			-- TODO: implement! request the player to talk
    
    dg's avatar
    dg committed
    		elseif self.move_others then
    
    			-- Displace
    			game.level.map:remove(self.x, self.y, Map.ACTOR)
    			game.level.map:remove(target.x, target.y, Map.ACTOR)
    			game.level.map(self.x, self.y, Map.ACTOR, target)
    			game.level.map(target.x, target.y, Map.ACTOR, self)
    			self.x, self.y, target.x, target.y = target.x, target.y, self.x, self.y
    		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 defence
    
    dg's avatar
    dg committed
    - defence: increases chances to miss against high attack power
    - 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
    
    
    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
    	-- All weaponsin main hands
    	if self:getInven(self.INVEN_MAINHAND) then
    		for i, o in ipairs(self:getInven(self.INVEN_MAINHAND)) do
    			if o.combat then
    
    dg's avatar
    dg committed
    				local s, h = self:attackTargetWith(target, o.combat, damtype, mult)
    
    dg's avatar
    dg committed
    				speed = math.max(speed or 0, s)
    
    dg's avatar
    dg committed
    				hit = hit or h
    
    dg's avatar
    dg committed
    				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
    			end
    		end
    	end
    	-- All wpeaons in off hands
    
    	-- Offhand atatcks are with a damage penality, taht can be reduced by talents
    
    dg's avatar
    dg committed
    	if self:getInven(self.INVEN_OFFHAND) then
    
    		local offmult = (mult or 1) / 2
    		if self:knowTalent(Talents.T_DUAL_WEAPON_TRAINING) then
    			offmult = (mult or 1) / (2 - (self:getTalentLevel(Talents.T_DUAL_WEAPON_TRAINING) / 6))
    		end
    
    dg's avatar
    dg committed
    		for i, o in ipairs(self:getInven(self.INVEN_OFFHAND)) do
    			if o.combat then
    
    dg's avatar
    dg committed
    				local s, h = self:attackTargetWith(target, o.combat, damtype, offmult)
    
    dg's avatar
    dg committed
    				speed = math.max(speed or 0, s)
    
    dg's avatar
    dg committed
    				hit = hit or h
    
    dg's avatar
    dg committed
    				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
    			end
    		end
    	end
    
    	-- Barehanded ?
    
    dg's avatar
    dg committed
    	if not speed and self.combat then
    
    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
    	end
    
    	-- 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
    	-- Cancel stealth!
    
    dg's avatar
    dg committed
    	self:breakStealth()
    
    dg's avatar
    dg committed
    	return hit
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    function _M:archeryShoot(damtype, mult, on_hit, tg, params)
    
    dg's avatar
    dg committed
    	local weapon, ammo = self:hasArcheryWeapon()
    
    dg's avatar
    dg committed
    	local sound, sound_miss = nil, nil
    
    dg's avatar
    dg committed
    	if not weapon then
    		game.logPlayer(self, "You must wield a bow or a sling (%s)!", ammo)
    		return nil
    	end
    
    dg's avatar
    dg committed
    	params = params or {}
    
    dg's avatar
    dg committed
    
    	print("[SHOOT WITH]", weapon.name, ammo.name)
    
    dg's avatar
    dg committed
    	local realweapon = weapon
    
    dg's avatar
    dg committed
    	weapon = weapon.combat
    
    	local ret = {}
    
    
    dg's avatar
    dg committed
    	local tg = tg or {type="bolt"}
    	if not tg.range then tg.range=weapon.range or 10 end
    	local x, y = self:getTarget(tg)
    	if not x or not y then return nil end
    
    dg's avatar
    dg committed
    	self:project(tg, x, y, function(tx, ty)
    
    dg's avatar
    dg committed
    		for i = 1, params.multishots or 1 do
    			local ammo = ammo
    			if not params.one_shot then
    				ammo = self:removeObject(self:getInven("QUIVER"), 1)
    				if not ammo then return end
    			end
    			if params.limit_shots then
    				if params.limit_shots <= 0 then return end
    				params.limit_shots = params.limit_shots - 1
    			end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    			local target = game.level.map(tx, ty, game.level.map.ACTOR)
    			if not target then return end
    			ammo = ammo.combat
    
    			damtype = damtype or ammo.damtype or DamageType.PHYSICAL
    			mult = mult or 1
    
    			-- Does the blow connect? yes .. complex :/
    			local atk, def = self:combatAttack(weapon), target:combatDefense()
    			local dam, apr, armor = self:combatDamage(ammo), self:combatAPR(ammo), target:combatArmor()
    			print("[ATTACK] to ", target.name, " :: ", dam, apr, armor, "::", mult)
    
    			-- If hit is over 0 it connects, if it is 0 we still have 50% chance
    			local hitted = false
    			if self:checkHit(atk, def) then
    				print("[ATTACK] raw dam", dam, "versus", armor, "with APR", apr)
    				local dam = math.max(0, dam - math.max(0, armor - apr))
    				local damrange = self:combatDamageRange(ammo)
    				dam = rng.range(dam, dam * damrange)
    				print("[ATTACK] after range", dam)
    				local crit
    
    dg's avatar
    dg committed
    				dam, crit = self:physicalCrit(dam, ammo, target)
    
    dg's avatar
    dg committed
    				print("[ATTACK] after crit", dam)
    				dam = dam * mult
    				print("[ATTACK] after mult", dam)
    				if crit then game.logSeen(self, "%s performs a critical stike!", self.name:capitalize()) end
    				DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam))
    				game.level.map:particleEmitter(target.x, target.y, 1, "archery")
    				hitted = true
    
    				if on_hit then on_hit(target, target.x, target.y) end
    			else
    				game.logSeen(target, "%s misses %s.", self.name:capitalize(), target.name)
    			end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    			ret.speed = self:combatSpeed(weapon)
    			ret.hitted = hitted
    		end
    	end)
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	if ret.hitted and not sound then sound = realweapon.sound
    	elseif not ret.hitted and not sound_miss then sound_miss = realweapon.sound_miss end
    
    
    dg's avatar
    dg committed
    	print("[SHOOT] speed", ret.speed or 1, "=>", game.energy_to_act * (ret.speed or 1))
    	self:useEnergy(game.energy_to_act * (ret.speed or 1))
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	-- If we used only one arrow, use it
    	if params.one_shot then self:removeObject(self:getInven("QUIVER"), 1) end
    
    
    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
    	return ret.hitted
    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
    	if atk > def then
    
    dg's avatar
    dg committed
    		local d = atk - def
    		hit = math.log10(1 + 5 * d / 50) * 100 + 50
    
    dg's avatar
    dg committed
    	else
    
    dg's avatar
    dg committed
    		local d = def - atk
    		hit = -math.log10(1 + 5 * d / 50) * 100 + 50
    
    dg's avatar
    dg committed
    	end
    	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 totaly evade an attack
    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()
    	local dam, apr, armor = self:combatDamage(weapon), self:combatAPR(weapon), target:combatArmor()
    
    dg's avatar
    dg committed
    	print("[ATTACK] to ", target.name, " :: ", dam, apr, armor, "::", mult)
    
    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
    
    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
    
    dg's avatar
    dg committed
    		print("[ATTACK] raw dam", dam, "versus", armor, "with APR", apr)
    
    dg's avatar
    dg committed
    		local dam = math.max(0, dam - math.max(0, armor - apr))
    
    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
    		local crit
    
    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)
    
    dg's avatar
    dg committed
    		if crit then game.logSeen(self, "%s performs a critical stike!", 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
    		game.logSeen(target, "%s misses %s.", self.name:capitalize(), target.name)
    	end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	-- Melee project
    	if hitted 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
    	-- 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
    
    
    	-- Riposte!
    
    dg's avatar
    dg committed
    	if not hitted and not evaded and target:knowTalent(target.T_RIPOSTE) and rng.percent(util.bound(target:getTalentLevel(target.T_RIPOSTE) * target:getDex(40), 10, 60)) then
    
    		game.logSeen(self, "%s ripostes!", target.name:capitalize())
    		target:attackTarget(self, nil, nil, true)
    	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_SWORD_MASTERY,
    	axe =   Talents.T_AXE_MASTERY,
    	mace =  Talents.T_MACE_MASTERY,
    	knife = Talents.T_KNIFE_MASTERY,
    
    dg's avatar
    dg committed
    	bow = Talents.T_BOW_MASTERY,
    	sling = Talents.T_SLING_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
    
    	return self.combat_def + (self:getDex() - 10) * 0.35 + add + (self:getLck() - 50) * 0.4
    
    dg's avatar
    dg committed
    end
    
    --- Gets the armor
    function _M:combatArmor()
    
    dg's avatar
    dg committed
    	local add = 0
    	if self:hasHeavyArmor() and self:knowTalent(self.T_HEAVY_ARMOUR_TRAINING) then
    		add = add + self:getTalentLevel(self.T_HEAVY_ARMOUR_TRAINING)
    	end
    	if self:hasMassiveArmor() and self:knowTalent(self.T_MASSIVE_ARMOUR_TRAINING) then
    		add = add + self:getTalentLevel(self.T_MASSIVE_ARMOUR_TRAINING)
    	end
    	return self.combat_armor + add
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack
    function _M:combatAttack(weapon)
    	weapon = weapon or self.combat
    
    	return self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 5 + (weapon.atk or 0) + (self:getStr(50) - 5) + (self:getDex(50) - 5) + (self:getLck() - 50) * 0.4
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack using only strength
    function _M:combatAttackStr(weapon)
    	weapon = weapon or self.combat
    
    	return self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 5 + (weapon.atk or 0) + (self:getStr(100) - 10) + (self:getLck() - 50) * 0.4
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack using only dexterity
    function _M:combatAttackDex(weapon)
    	weapon = weapon or self.combat
    
    	return self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 5 + (weapon.atk or 0) + (self:getDex(100) - 10) + (self:getLck() - 50) * 0.4
    
    dg's avatar
    dg committed
    end
    
    --- Gets the armor penetration
    function _M:combatAPR(weapon)
    	weapon = weapon or self.combat
    
    dg's avatar
    dg committed
    	return self.combat_apr + (weapon.apr or 0)
    
    dg's avatar
    dg committed
    end
    
    --- Gets the weapon speed
    function _M:combatSpeed(weapon)
    	weapon = weapon or self.combat
    	return self.combat_physspeed + (weapon.physspeed or 1)
    end
    
    --- Gets the crit rate
    function _M:combatCrit(weapon)
    	weapon = weapon or self.combat
    
    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)
    	weapon = weapon or self.combat
    
    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
    --- Gets the damage
    function _M:combatDamage(weapon)
    	weapon = weapon or self.combat
    
    dg's avatar
    dg committed
    
    	local sub_con_to_str = false
    	if weapon.talented and weapon.talented == "knife" and self:knowTalent(Talents.T_LETHALITY) then sub_con_to_str = true end
    
    
    dg's avatar
    dg committed
    	local add = 0
    	if weapon.dammod then
    		for stat, mod in pairs(weapon.dammod) do
    
    dg's avatar
    dg committed
    			if sub_con_to_str and stat == "str" then stat = "cun" end
    
    dg's avatar
    dg committed
    			add = add + (self:getStat(stat) - 10) * 0.7 * mod
    
    dg's avatar
    dg committed
    		end
    	end
    
    dg's avatar
    dg committed
    	local talented_mod = self:combatCheckTraining(weapon)
    
    dg's avatar
    dg committed
    	return self.combat_dam + (weapon.dam or 1) * (1 + talented_mod / 4) + add
    
    dg's avatar
    dg committed
    end
    
    --- Gets spellpower
    function _M:combatSpellpower(mod)
    	mod = mod or 1
    
    dg's avatar
    dg committed
    	return (self.combat_spellpower + self:getMag() * 0.7) * mod
    
    dg's avatar
    dg committed
    end
    
    --- 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()
    
    dg's avatar
    dg committed
    	return self.combat_spellspeed + 1
    
    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
    		return dam * (2 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 5), true
    	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
    	print("[PHYS CRIT %]", chance)
    
    dg's avatar
    dg committed
    	if rng.percent(chance) then
    		dam = dam * 2
    
    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)
    
    dg's avatar
    dg committed
    	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
    		return dam * (2 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 5), true
    	end
    
    
    dg's avatar
    dg committed
    	local chance = self:combatSpellCrit()
    
    dg's avatar
    dg committed
    	local crit = false
    
    dg's avatar
    dg committed
    --	if self:knowTalent(self.T_BACKSTAB) then chance = chance + self:getTalentLevel(self.T_BACKSTAB) * 10 end
    
    dg's avatar
    dg committed
    	print("[SPELL CRIT %]", chance)
    
    dg's avatar
    dg committed
    	if rng.percent(chance) then
    		dam = dam * 2
    
    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
    
    dg's avatar
    dg committed
    
    
    --- Do we get hit by our own AOE ?
    function _M:spellFriendlyFire()
    	return rng.chance(self:getTalentLevelRaw(self.T_SPELL_SHAPING) * 20 + (self:getLck() - 50) * 0.2)
    end
    
    
    dg's avatar
    dg committed
    --- Computes physical resistance
    function _M:combatPhysicalResist()
    
    	return self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.25
    
    dg's avatar
    dg committed
    end
    
    --- Computes spell resistance
    function _M:combatSpellResist()
    
    	return self.combat_spellresist + (self:getMag() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.25
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    --- Computes mental resistance
    function _M:combatMentalResist()
    
    	return self.combat_mentalresist + (self:getCun() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.25
    
    dg's avatar
    dg committed
    --- Check if the actor has a bow or sling and corresponding ammo
    function _M:hasArcheryWeapon()
    	if not self:getInven("MAINHAND") then return nil, "no shooter" end
    	if not self:getInven("QUIVER") then return nil, "no ammo" end
    	local weapon = self:getInven("MAINHAND")[1]
    	local ammo = self:getInven("QUIVER")[1]
    	if not weapon or not weapon.archery then
    		return nil, "no shooter"
    	end
    	if not ammo or not ammo.archery_ammo or weapon.archery ~= ammo.archery_ammo then
    		return nil, "bad or no ammo"
    	end
    	return weapon, ammo
    end
    
    
    --- Check if the actor has a two handed weapon
    
    dg's avatar
    dg committed
    function _M:hasTwoHandedWeapon()
    
    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 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
    
    --- Check if the actor dual wields
    function _M:hasDualWeapon()
    	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" then
    		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