Skip to content
Snippets Groups Projects
Combat.lua 54.2 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, x, y)
    
    	local reaction = self:reactionToward(target)
    	if reaction < 0 then
    
    		if target.encounterAttack and self.player then self:onWorldEncounter(target, x, y) return end
    
    		if game.player == self and ((not config.settings.tome.actor_based_movement_mode and game.bump_attack_disabled) or (config.settings.tome.actor_based_movement_mode and self.bump_attack_disabled)) then 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
    
    dg's avatar
    dg committed
    			local chat = Chat.new(target.can_talk, target, self, {npc=target, player=self})
    
    			chat:invoke()
    
    			if target.can_talk_only_once then target.can_talk = nil end
    
    		elseif target.player and self.can_talk then
    
    dg's avatar
    dg committed
    			local chat = Chat.new(self.can_talk, self, target, {npc=self, player=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
    
    
    			-- Check we can both walk in the tile we will end up in
    			local blocks = game.level.map:checkAllEntitiesLayersNoStop(target.x, target.y, "block_move", self)
    			for kind, v in pairs(blocks) do if kind[1] ~= Map.ACTOR and v then return end end
    			blocks = game.level.map:checkAllEntitiesLayersNoStop(self.x, self.y, "block_move", target)
    			for kind, v in pairs(blocks) do if kind[1] ~= Map.ACTOR and v then return end 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
    
    	if self:attr("terrified") and rng.percent(self:attr("terrified")) then
    		if not noenergy then
    			self:useEnergy(game.energy_to_act)
    			self.did_energy = true
    		end
    		game.logSeen(self, "%s is too terrified to attack.", self.name:capitalize())
    		return false
    	end
    
    dg's avatar
    dg committed
    
    
    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
    	-- 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
    
    dg's avatar
    dg committed
    	elseif not damtype and self:attr("force_melee_damage_type") then
    		damtype = self:attr("force_melee_damage_type")
    
    dg's avatar
    dg committed
    	end
    
    
    dg's avatar
    dg committed
    	local break_stealth = false
    
    dg's avatar
    dg committed
    
    	local hd = {"Combat:attackTarget", target=target, damtype=damtype, mult=mult, noenergy=noenergy}
    	if self:triggerHook(hd) then
    		speed, hit = ht.speed, hd.hit
    		if ht.stop then return hit end
    	end
    
    	if not speed and self:isTalentActive(self.T_GESTURE_OF_PAIN) then
    
    		print("[ATTACK] attacking with Gesture of Pain")
    		local t = self:getTalentFromId(self.T_GESTURE_OF_PAIN)
    
    dg's avatar
    dg committed
    		if not t.preAttack(self, t, target) then return false end
    
    		speed, hit = t.attack(self, t, target)
    		break_stealth = true
    	end
    
    	if not speed and 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
    
    dg's avatar
    dg committed
    				local combat = o.combat
    				if o.special_combat and o.subtype == "shield" and self:knowTalent(self.T_STONESHIELD) then combat = o.special_combat end
    				if combat and not o.archery then
    
    dg's avatar
    dg committed
    					print("[ATTACK] attacking with", o.name)
    
    dg's avatar
    dg committed
    					local s, h = self:attackTargetWith(target, combat, damtype, offmult)
    
    dg's avatar
    dg committed
    					speed = math.max(speed or 0, s)
    					hit = hit or h
    
    dg's avatar
    dg committed
    					if hit and not sound then sound = combat.sound
    					elseif not hit and not sound_miss then sound_miss = combat.sound_miss end
    					if not 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
    
    	-- 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
    
    
    	game:playSoundNear(self, self.on_hit_sound or "actions/melee_hit_squish")
    	if self.sound_moam and rng.chance(7) then game:playSoundNear(self, self.sound_moam) end
    
    
    dg's avatar
    dg committed
    	-- cleave second attack
    
    	if self:isTalentActive(self.T_CLEAVE) then
    
    dg's avatar
    dg committed
    		local t = self:getTalentFromId(self.T_CLEAVE)
    
    		t.on_attackTarget(self, t, target)
    
    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
    
    dg's avatar
    dg committed
    
    function _M:checkHitOld(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
    --Tells the tier difference between two values
    function _M:crossTierEffect(eff_id, apply_power, apply_save, use_given_e)
    	local q = game.player:hasQuest("tutorial-combat-stats")
    	if q and not q:isCompleted("final-lesson")then
    		return
    	end
    	local ct_effect
    	local save_for_effects = {
    		physical = "combatPhysicalResist",
    		magical = "combatSpellResist",
    		mental = "combatMentalResist",
    	}
    	local cross_tier_effects = {
    		combatPhysicalResist = self.EFF_OFFBALANCE,
    		combatSpellResist = self.EFF_SPELLSHOCKED,
    		combatMentalResist = self.EFF_BRAINLOCKED,
    	}
    	local e = self.tempeffect_def[eff_id]
    	if not apply_power or not save_for_effects[e.type] then return end
    
    dg's avatar
    dg committed
    	local save = self[apply_save or save_for_effects[e.type]](self, true)
    
    dg's avatar
    dg committed
    
    	if use_given_e then
    		ct_effect = self["EFF_"..e.name]
    	else
    		ct_effect = cross_tier_effects[save_for_effects[e.type]]
    	end
    	local dur = self:getTierDiff(apply_power, save)
    	self:setEffect(ct_effect, dur, {})
    end
    
    function _M:getTierDiff(atk, def)
    	atk = math.floor(atk)
    	def = math.floor(def)
    	return math.max(0, math.max(math.ceil(atk/20), 1) - math.max(math.ceil(def/20), 1))
    end
    
    --New, simpler checkHit that relies on rescaleCombatStats() being used elsewhere
    function _M:checkHit(atk, def, min, max, factor, p)
    	local min = min or 0
    	local max = max or 100
    	if game.player:hasQuest("tutorial-combat-stats") then
    		min = 0
    		max = 100
    	end --ensures predictable combat for the tutorial
    	print("checkHit", atk, def)
    
    	local hit = math.ceil(50 + 2.5 * (atk - def))
    
    dg's avatar
    dg committed
    	hit = util.bound(hit, min, max)
    	print("=> chance to hit", hit)
    	return rng.percent(hit), hit
    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
    
    function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
    	damtype = damtype or (weapon and 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
    
    
    	-- add stalker damage and attack bonus
    	local effStalker = self:hasEffect(self.EFF_STALKER)
    	if effStalker and effStalker.target == target then
    		local t = self:getTalentFromId(self.T_STALK)
    		atk = atk + t.getAttackChange(self, t, effStalker.bonus)
    		mult = mult * t.getStalkedDamageMultiplier(self, t, effStalker.bonus)
    	end
    
    	-- add marked prey damage and attack bonus
    	local effPredator = self:hasEffect(self.EFF_PREDATOR)
    	if effPredator and effPredator.type == target.type then
    		if effPredator.subtype == target.subtype then
    			mult = mult + effPredator.subtypeDamageChange
    			atk = atk + effPredator.subtypeAttackChange
    		else
    			mult = mult + effPredator.typeDamageChange
    			atk = atk + effPredator.typeAttackChange
    		end
    	end
    
    	-- track weakness for hate bonus before the target removes it
    	local effGloomWeakness = target:hasEffect(target.EFF_GLOOM_WEAKNESS)
    
    dg's avatar
    dg committed
    
    
    	local dam, apr, armor = force_dam or 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
    
    
    	-- check repel
    	local repelled = false
    	if target:isTalentActive(target.T_REPEL) then
    		local t = target:getTalentFromId(target.T_REPEL)
    		repelled = t.isRepelled(target, t)
    	end
    
    dg's avatar
    dg committed
    
    
    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 repelled then
    		game.logSeen(target, "%s repels an attack from %s.", target.name:capitalize(), self.name)
    	elseif self:checkEvasion(target) then
    
    dg's avatar
    dg committed
    		evaded = true
    		game.logSeen(target, "%s evades %s.", target.name:capitalize(), self.name)
    
    	elseif self:checkHit(atk, def) and (self:canSee(target) or self:attr("blind_fight") or rng.chance(3)) then
    
    dg's avatar
    dg committed
    		local pres = util.bound(target: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, atk, def)
    
    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 and 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, "#{bold}#%s performs a critical strike!#{normal}#", 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
    
    
    	-- cross-tier effect for accuracy vs. defense
    	local tier_diff = self:getTierDiff(atk, def)
    	if hitted and not target.dead and tier_diff > 0 then
    
    		local reapplied = false
    		-- silence the apply message it if the target already has the effect
    		for eff_id, p in pairs(target.tmp) do
    			local e = target.tempeffect_def[eff_id]
    			if e.desc == "Off-guard" then
    				reapplied = true
    			end
    		end
    		target:setEffect(target.EFF_OFFGUARD, tier_diff, {}, reapplied)
    
    	-- handle stalk targeting for hits (also handled in Actor for turn end effects)
    	if hitted and target ~= self then
    		if effStalker then
    			-- mark if stalkee was hit
    			effStalker.hit = effStalker.hit or effStalker.target == target
    		elseif self:isTalentActive(self.T_STALK) then
    			local stalk = self:isTalentActive(self.T_STALK)
    
    dg's avatar
    dg committed
    
    
    			if not stalk.hit then
    				-- mark a new target
    				stalk.hit = true
    				stalk.hit_target = target
    			elseif stalk.hit_target ~= target then
    				-- more than one target; clear it
    				stalk.hit_target = nil
    			end
    		end
    	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 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 = 2 + self:combatTalentSpellDamage(self.T_SHADOW_COMBAT, 2, 50)
    		local mana = 2
    
    		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)
    
    		self:incParadox(- t.getParadoxReduction(self, t))
    
    	-- Ruin
    	if hitted and not target.dead and self:knowTalent(self.T_RUIN) and self:isTalentActive(self.T_RUIN) then
    		local t = self:getTalentFromId(self.T_RUIN)
    		local dam = t.getDamage(self, t)
    		DamageType:get(DamageType.DRAINLIFE).projector(self, target.x, target.y, DamageType.DRAINLIFE, dam)
    	end
    
    dg's avatar
    dg committed
    
    
    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 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_cd=true, ignore_energy=true, force_target=target, force_level=data.level, ignore_ressources=true})
    
    	-- Shattering Impact
    
    	if hitted and self:attr("shattering_impact") and (not self.shattering_impact_last_turn or self.shattering_impact_last_turn < game.turn) 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)
    
    		self.shattering_impact_last_turn = game.turn
    
    	end
    
    	-- Onslaught
    	if hitted and self:attr("onslaught") then
    		local dir = util.getDir(target.x, target.y, self.x, self.y)
    
    dg's avatar
    dg committed
    		local lx, ly = util.coordAddDir(self.x, self.y, dir_sides[dir or 6].left)
    		local rx, ry = util.coordAddDir(self.x, self.y, dir_sides[dir or 6].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"))
    
    dg's avatar
    dg committed
    			target:crossTierEffect(target.EFF_OFFBALANCE, self:combatAttack())
    
    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"))
    
    dg's avatar
    dg committed
    			target:crossTierEffect(target.EFF_OFFBALANCE, self:combatAttack())
    
    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"))
    
    dg's avatar
    dg committed
    			target:crossTierEffect(target.EFF_OFFBALANCE, self:combatAttack())
    
    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
    	-- Dwarves stoneskin
    	if hitted and not target.dead and target:attr("auto_stoneskin") and rng.percent(15) then
    		target:setEffect(target.EFF_STONE_SKIN, 5, {power=target:attr("auto_stoneskin")})
    
    dg's avatar
    dg committed
    	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
    
    		local t = self:getTalentFromId(self.T_CONDUIT)
    
    dg's avatar
    dg committed
    		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 and weapon.special_on_hit and weapon.special_on_hit.fct then
    
    dg's avatar
    dg committed
    		weapon.special_on_hit.fct(weapon, self, target)
    	end
    
    
    dg's avatar
    dg committed
    	-- Poison coating
    
    dg's avatar
    dg committed
    	if hitted and not target.dead and self.vile_poisons and next(self.vile_poisons) and target:canBe("poison") then
    
    dg's avatar
    dg committed
    		local tid = rng.table(table.keys(self.vile_poisons))
    		if tid then
    			local t = self:getTalentFromId(tid)
    			t.proc(self, t, target, weapon)
    		end
    	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
    
    	if hitted and self:attr("stamina_use_on_hit") then
    		self:incStamina(-self.stamina_use_on_hit)
    	end
    
    
    dg's avatar
    dg committed
    	if hitted and not target.dead and target:knowTalent(target.T_STONESHIELD) then
    		local t = target:getTalentFromId(target.T_STONESHIELD)
    		local m, mm, e, em = t.getValues(self, t)
    		target:incMana(math.min(dam * m, mm))
    		target:incEquilibrium(-math.min(dam * e, em))
    	end
    
    
    dg's avatar
    dg committed
    	-- Ablative Armor
    	if hitted and not target.dead and target:attr("carbon_spikes") then
    
    		local t = target:getTalentFromId(target.T_CARBON_SPIKES)
    		t.do_carbonLoss(target, t)
    
    dg's avatar
    dg committed
    	end
    
    	-- Riposte!
    
    	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, true))) 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
    	-- Counter Attack!
    
    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_COUNTER_ATTACK) and rng.percent(target:getTalentLevel(target.T_COUNTER_ATTACK) * (5 + target:getCun(5, true))) then
    
    dg's avatar
    dg committed
    		game.logSeen(self, "%s counters the attack!", target.name:capitalize())
    		local t = target:getTalentFromId(target.T_COUNTER_ATTACK)
    
    dg's avatar
    dg committed
    		target:attackTarget(self, nil, t.getDamage(target, t), true)
    
    dg's avatar
    dg committed
    	end
    
    	-- 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, true))) then
    
    		local t = target:getTalentFromId(target.T_DEFENSIVE_THROW)
    		t.do_throw(target, self, t)
    	end
    
    	-- Greater Weapon Focus
    	local gwf = self:hasEffect(self.EFF_GREATER_WEAPON_FOCUS)
    
    	if hitted and not target.dead and weapon and gwf and not gwf.inside and rng.percent(gwf.chance) then
    
    		gwf.inside = true
    
    		game.logSeen(self, "%s focuses and gains an extra blow!", self.name:capitalize())
    
    		self:attackTargetWith(target, weapon, damtype, mult)
    		gwf.inside = nil
    	end
    
    
    dg's avatar
    dg committed
    	-- Zero gravity
    
    	if hitted and game.level.data.zero_gravity and rng.percent(util.bound(dam, 0, 100)) then
    
    dg's avatar
    dg committed
    		target:knockback(self.x, self.y, math.ceil(math.log(dam)))
    	end
    
    	-- Weakness hate bonus
    	if hitted and effGloomWeakness and effGloomWeakness.hateBonus or 0 > 0 then
    		self:incHate(effGloomWeakness.hateBonus)
    		game.logPlayer(self, "#F53CBE#You revel in attacking a weakened foe! (+%d hate)", effGloomWeakness.hateBonus)
    		effGloomWeakness.hateBonus = nil
    	end
    
    	-- Rampage
    	if hitted and crit then
    		local eff = self:hasEffect(self.EFF_RAMPAGE)
    		if eff and not eff.critHit and eff.actualDuration < eff.maxDuration then
    			game.logPlayer(self, "#F53CBE#Your rampage is invigorated by your fierce attack! (+1 duration)")
    			eff.critHit = true
    			eff.actualDuration = eff.actualDuration + 1
    			eff.dur = eff.dur + 1
    		end
    	end
    
    	-- Marked Prey
    	if hitted and not target.dead and effPredator and effPredator.type == target.type then
    		if effPredator.subtype == target.subtype then
    			-- Anatomy stun
    			if effPredator.subtypeStunChance > 0 and rng.percent(effPredator.subtypeStunChance) then
    				if target:canBe("stun") then
    					target:setEffect(target.EFF_STUNNED, 3, {})
    				else
    					game.logSeen(target, "%s resists the stun!", target.name:capitalize())
    				end
    			end
    
    			-- Outmaneuver
    			if effPredator.subtypeOutmaneuverChance > 0 and rng.percent(effPredator.subtypeOutmaneuverChance) then
    				local t = self:getTalentFromId(self.T_OUTMANEUVER)
    
    				target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { physicalResistChange=t.getPhysicalResistChange(self, t), statReduction=t.getStatReduction(self, t) })
    
    			end
    		else
    			-- Outmaneuver
    			if effPredator.typeOutmaneuverChance > 0 and rng.percent(effPredator.typeOutmaneuverChance) then
    				local t = self:getTalentFromId(self.T_OUTMANEUVER)
    				target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { reduction=t.getReduction(self, t) })
    			end
    		end
    	end
    
    	if hitted and crit and target:hasEffect(target.EFF_DISMAYED) then
    		target:removeEffect(target.EFF_DISMAYED)
    	end
    
    	if hitted and not target.dead then
    		-- Curse of Madness: Twisted Mind
    		if self.hasEffect and self:hasEffect(self.EFF_CURSE_OF_MADNESS) then
    			local eff = self:hasEffect(self.EFF_CURSE_OF_MADNESS)
    			local def = self.tempeffect_def[self.EFF_CURSE_OF_MADNESS]
    			def.doConspirator(self, eff, target)
    		end
    		if target.hasEffect and target:hasEffect(target.EFF_CURSE_OF_MADNESS) then
    			local eff = target:hasEffect(target.EFF_CURSE_OF_MADNESS)
    			local def = target.tempeffect_def[target.EFF_CURSE_OF_MADNESS]
    			def.doConspirator(target, eff, self)
    		end
    
    		-- Curse of Nightmares: Suffocate
    		if self.hasEffect and self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES) then
    			local eff = self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES)
    			local def = self.tempeffect_def[self.EFF_CURSE_OF_NIGHTMARES]
    			def.doSuffocate(self, eff, target)
    		end
    		if target.hasEffect and target:hasEffect(target.EFF_CURSE_OF_NIGHTMARES) then
    			local eff = target:hasEffect(target.EFF_CURSE_OF_NIGHTMARES)
    			local def = target.tempeffect_def[target.EFF_CURSE_OF_NIGHTMARES]
    			def.doSuffocate(target, eff, self)
    		end
    	end
    
    dg's avatar
    dg committed
    
    
    	local hd = {"Combat:attackTargetWith", hitted=hitted, target=target, weapon=weapon, damtype=damtype, mult=mult}
    	if self:triggerHook(hd) then hitted = hd.hitted 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 = {
    
    dg's avatar
    dg committed
    	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,
    	sling =   Talents.T_SLING_MASTERY,
    	staff =   Talents.T_STAFF_MASTERY,
    	unarmed = Talents.T_UNARMED_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
    
    --- Fake denotes a check not actually being made, used by character sheets etc.
    
    dg's avatar
    dg committed
    function _M:combatDefenseBase(fake)
    
    dg's avatar
    dg committed
    	local add = 0
    	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 not fake then
    
    		add = add + (self:checkOnDefenseCall("defense") or 0)
    	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
    
    	if self:isTalentActive(Talents.T_SURGE) then
    		local t = self:getTalentFromId(self.T_SURGE)
    		add = add + t.getDefenseChange(self, t)
    	end
    
    dg's avatar
    dg committed
    	local d = math.max(0, self.combat_def + (self:getDex() - 10) * 0.35 + add + (self:getLck() - 50) * 0.4)
    
    	if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
    		d = d * (1 + self:getTalentLevel(self.T_MOBILE_DEFENCE) * 0.08)
    	end
    
    	return d
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the defense ranged
    
    dg's avatar
    dg committed
    function _M:combatDefense(fake)
    	local base_defense = self:combatDefenseBase(true)
    	if not fake then base_defense = self:combatDefenseBase() end
    	local d = base_defense
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats(d)
    end
    
    --- Gets the defense ranged
    
    dg's avatar
    dg committed
    function _M:combatDefenseRanged(fake)
    	local base_defense = self:combatDefenseBase(true)
    	if not fake then base_defense = self:combatDefenseBase() end
    	local d = math.max(0, base_defense + (self.combat_def_ranged or 0))
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats(d)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the armor
    function _M:combatArmor()
    
    dg's avatar
    dg committed
    	local add = 0
    
    	if self:hasHeavyArmor() and self:knowTalent(self.T_ARMOUR_TRAINING) then
    		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_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
    
    dg's avatar
    dg committed
    	if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
    		add = add + self:getTalentLevel(self.T_MOBILE_DEFENCE) * 6
    	end
    
    dg's avatar
    dg committed
    	return util.bound(30 + self.combat_armor_hardiness + add, 0, 100)
    
    dg's avatar
    dg committed
    --- Gets the attack
    
    function _M:combatAttackBase(weapon, ammo)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    dg's avatar
    dg committed
    	return 4 + 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
    
    dg's avatar
    dg committed
    	if self.use_psi_combat then stats = self:getCun(100, true) - 10
    	else stats = self:getDex(100, true) - 10
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats(self:combatAttackBase(weapon, ammo) + stats)
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack using only strength
    
    function _M:combatAttackStr(weapon, ammo)
    
    	return self:rescaleCombatStats(self:combatAttackBase(weapon, ammo) + (self:getStr(100, true) - 10))
    
    dg's avatar
    dg committed
    end
    
    --- Gets the attack using only dexterity
    
    function _M:combatAttackDex(weapon, ammo)
    
    	return self:rescaleCombatStats(self:combatAttackBase(weapon, ammo) + (self:getDex(100, true) - 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:rescaleCombatStats(self:combatAttackBase(weapon, ammo) + (self:getMag(100, true) - 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 {}
    
    	local addapr = 0
    	return self.combat_apr + (weapon.apr or 0) + addapr
    
    dg's avatar
    dg committed
    end
    
    --- Gets the weapon speed
    function _M:combatSpeed(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    	return (weapon.physspeed or 1) / self.combat_physspeed
    
    dg's avatar
    dg committed
    end
    
    --- Gets the crit rate
    function _M:combatCrit(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    dg's avatar
    dg committed
    	local addcrit = 0
    	if weapon.talented and weapon.talented == "knife" and self:knowTalent(Talents.T_LETHALITY) then
    		addcrit = 1 + self:getTalentLevel(Talents.T_LETHALITY) * 1.3
    	end
    
    	local crit = self.combat_physcrit + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + (weapon.physcrit or 1) + addcrit
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the damage range
    function _M:combatDamageRange(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    dg's avatar
    dg committed
    	return (self.combat_damrange or 0) + (weapon.damrange or 1.1)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    
    --- Scale damage values
    
    dg's avatar
    dg committed
    -- This currently beefs up high-end damage values to make up for the combat stat rescale nerf.
    
    dg's avatar
    dg committed
    function _M:rescaleDamage(dam)
    	if dam <= 0 then return dam end
    
    dg's avatar
    dg committed
    --	return dam * (1 - math.log10(dam * 2) / 7) --this is the old version, pre-combat-stat-rescale
    	return dam ^ 1.04
    end
    --Diminishing-returns method of scaling combat stats, observing this rule: the first twenty ranks cost 1 point each, the second twenty cost two each, and so on. This is much, much better for players than some logarithmic mess, since they always know exactly what's going on, and there are nice breakpoints to strive for.
    function _M:rescaleCombatStats(raw_combat_stat_value)
    	local x = raw_combat_stat_value
    	local tiers = 5 -- Just increase this if you want to add high-level content that allows for combat stat scores over 100.
    	--return math.floor(math.min(x, 20) + math.min(math.max((x-20), 0)/2, 20) + math.min(math.max((x-60), 0)/3, 20) + math.min(math.max((x-120), 0)/4, 20) + math.min(math.max((x-200), 0)/5, 20)) --Five terms of the summation below.
    	local total = 0
    	for i = 1, tiers do
    		local sub = 20*(i*(i-1)/2)
    		total = total + math.min(math.max(x-sub, 0)/i, 20)
    	end
    	return total
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Gets the damage
    function _M:combatDamage(weapon)
    
    dg's avatar
    dg committed
    	weapon = weapon or self.combat or {}
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	local sub_cun_to_str = false
    	if weapon.talented and weapon.talented == "knife" and self:knowTalent(Talents.T_LETHALITY) then sub_cun_to_str = true end
    
    dg's avatar
    dg committed
    
    
    	local dammod = weapon.dammod or {str=0.6}
    	for stat, mod in pairs(dammod) do
    
    dg's avatar
    dg committed
    		if sub_cun_to_str and stat == "str" then stat = "cun" end
    
    dg's avatar
    dg committed
    		if self.use_psi_combat and stat == "str" then stat = "wil" end
    		if self.use_psi_combat and stat == "dex" then stat = "cun" end
    
    		totstat = totstat + self:getStat(stat) * mod
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if self.use_psi_combat then
    
    		if self:knowTalent(self.T_GREATER_TELEKINETIC_GRASP) then
    
    			local g = self:getTalentFromId(self.T_GREATER_TELEKINETIC_GRASP)
    
    			totstat = totstat * g.stat_sub(self, g)
    		else
    
    dg's avatar
    dg committed
    			totstat = totstat * 0.6
    
    dg's avatar
    dg committed
    	local talented_mod = math.sqrt(self:combatCheckTraining(weapon) / 10) / 2 + 1
    
    	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() + totstat) * power * talented_mod)
    end
    
    function _M:combatPhysicalpower(mod)
    	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) / 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 self:isUnarmed() then
    
    dg's avatar
    dg committed
    		local t = self:getTalentFromId(self.T_EMPTY_HAND)
    		add = add + t.getDamage(self, t)
    	end
    
    dg's avatar
    dg committed
    	if self:knowTalent(Talents.T_WEAPONS_MASTERY) then
    		add = add + 5 * self:getTalentLevel(Talents.T_WEAPONS_MASTERY)
    	end
    	if self:knowTalent(Talents.T_KNIFE_MASTERY) then
    
    dg's avatar
    dg committed
    		add = add + 5 * self:getTalentLevel(Talents.T_KNIFE_MASTERY)
    
    dg's avatar
    dg committed
    	end
    	if self:knowTalent(Talents.T_EXOTIC_WEAPONS_MASTERY) then
    
    dg's avatar
    dg committed
    		add = add + 5 * self:getTalentLevel(Talents.T_EXOTIC_WEAPONS_MASTERY)
    
    dg's avatar
    dg committed
    	end
    	if self:knowTalent(Talents.T_UNARMED_MASTERY) then
    		add = add + 5 * self:getTalentLevel(Talents.T_UNARMED_MASTERY)
    	end
    	if self:knowTalent(Talents.T_STAFF_MASTERY) then
    		add = add + 5 * self:getTalentLevel(Talents.T_STAFF_MASTERY)
    	end
    	if self:knowTalent(Talents.T_BOW_MASTERY) then
    		add = add + 5 * self:getTalentLevel(Talents.T_BOW_MASTERY)
    	end
    	if self:knowTalent(Talents.T_SLING_MASTERY) then
    		add = add + 5 * self:getTalentLevel(Talents.T_SLING_MASTERY)
    	end
    
    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)
    	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
    
    
    dg's avatar
    dg committed
    	return self:rescaleCombatStats((self.combat_spellpower > 0 and self.combat_spellpower or 0) + 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)
    
    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
    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
    
    	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