Skip to content
Snippets Groups Projects
Combat.lua 90.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • dg's avatar
    dg committed
    -- ToME - Tales of Maj'Eyal
    
    DarkGod's avatar
    DarkGod committed
    -- Copyright (C) 2009 - 2015 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
    
    dg's avatar
    dg committed
    		-- Talk ? Bump ?
    		if self.player and target.on_bump then
    			target:on_bump(self)
    		elseif 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:move(sx, sy, true)
    			self:move(tx, ty, true)
    
    			if target.describeFloor then target:describeFloor(target.x, target.y, true) end
    			if self.describeFloor then self:describeFloor(self.x, self.y, true) end
    
    dg's avatar
    dg committed
    
    
    			local energy = game.energy_to_act * self:combatMovementSpeed(x, y)
    
    dg's avatar
    dg committed
    			if self:attr("bump_swap_speed_divide") then
    
    				energy = energy / self:attr("bump_swap_speed_divide")
    
    dg's avatar
    dg committed
    			end
    
    			self:useEnergy(energy)
    			self.did_energy = 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
    ]]
    
    dg's avatar
    dg committed
    function _M:attackTarget(target, damtype, mult, noenergy, force_unharmed)
    
    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()
    
    Eric Wykoff's avatar
    Eric Wykoff committed
    	self:breakSpacetimeTuning()
    
    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
    
    DarkGod's avatar
    DarkGod committed
    		if self.player then self:logCombat(target, "#Target# notices you at the last moment!") end
    
    dg's avatar
    dg committed
    	end
    
    
    	if target:isTalentActive(target.T_INTUITIVE_SHOTS) and rng.percent(target:callTalent(target.T_INTUITIVE_SHOTS, "getChance")) then
    		local ret = target:callTalent(target.T_INTUITIVE_SHOTS, "proc", self)
    		if ret then return false end
    	end
    
    
    	if not target.turn_procs.warding_weapon and target:knowTalent(target.T_WARDING_WEAPON) and target:getTalentLevelRaw(target.T_WARDING_WEAPON) >= 5
    
    HousePet's avatar
    HousePet committed
    		and rng.percent(target:callTalent(target.T_WARDING_WEAPON, "getChance")) then
    		local t = self:getTalentFromId(self.T_WARDING_WEAPON)
    		if target:getPsi() >= t.psi then
    			target:setEffect(target.EFF_WEAPON_WARDING, 1, {})
    			target.turn_procs.warding_weapon = true
    			target:incPsi(-t.psi)
    		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
    
    Grayswandir's avatar
    Grayswandir committed
    			local damtype = table.get(gems[i], 'color_attributes', 'damage_type')
    			if damtype then table.insert(types, damtype) end
    
    dg's avatar
    dg committed
    		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, damtype, mult = hd.speed, hd.hit, hd.damtype, hd.mult
    
    dg's avatar
    dg committed
    		if hd.stop then return hit end
    
    dg's avatar
    dg committed
    	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
    
    
    dg's avatar
    dg committed
    	local mean
    	if not speed and not self:attr("disarmed") and not self:isUnarmed() and not force_unharmed 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
    
    dg's avatar
    dg committed
    				local combat = self:getObjectCombat(o, "mainhand")
    				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, mult)
    
    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
    
    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
    			for i, o in ipairs(self:getInven(self.INVEN_OFFHAND)) do
    
    dg's avatar
    dg committed
    				local offmult = self:getOffHandMult(o.combat, mult)
    
    dg's avatar
    dg committed
    				local combat = self:getObjectCombat(o, "offhand")
    
    dg's avatar
    dg committed
    				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
    
    dg's avatar
    dg committed
    		mean = "weapon"
    
    dg's avatar
    dg committed
    	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")
    
    		local combat = self:getObjectCombat(nil, "barehand")
    
    dg's avatar
    dg committed
    		local s, h = self:attackTargetWith(target, combat, damtype, mult)
    
    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
    		mean = "unharmed"
    
    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
    	if self:attr("unharmed_attack_on_hit") then
    		local v = self:attr("unharmed_attack_on_hit")
    		self:attr("unharmed_attack_on_hit", -v)
    
    		if rng.percent(60) then self:attackTarget(target, nil, 1, true, true) end
    
    dg's avatar
    dg committed
    		self:attr("unharmed_attack_on_hit", v)
    	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
    	return hit
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Determines the combat field to use for this item
    function _M:getObjectCombat(o, kind)
    	if kind == "barehand" then return self.combat end
    
    	if not o then return nil end
    
    dg's avatar
    dg committed
    	if kind == "mainhand" then return o.combat end
    	if kind == "offhand" then return o.combat end
    	return nil
    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)
    
    	if atk < 0 then atk = 0 end
    	if def < 0 then def = 0 end
    
    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)
    
    dg's avatar
    dg committed
    	self:setEffect(ct_effect, dur, {})
    
    dg's avatar
    dg committed
    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)
    
    	if atk < 0 then atk = 0 end
    	if def < 0 then def = 0 end
    
    dg's avatar
    dg committed
    	local min = min or 0
    
    dg's avatar
    dg committed
    	local max = max or 100
    
    dg's avatar
    dg committed
    	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") or self == target then return end
    
    	if target:attr("no_evasion") then return end
    
    dg's avatar
    dg committed
    
    	local evasion = target:attr("evasion")
    	print("checkEvasion", evasion, target.level, self.level)
    	print("=> evasion chance", evasion)
    	return rng.percent(evasion)
    end
    
    
    function _M:getAccuracyEffect(weapon, atk, def, scale, max)
    	max = max or 10000000
    	scale = scale or 1
    	return math.min(max, math.max(0, atk - def) * scale * (weapon.accuracy_effect_scale or 1))
    end
    
    function _M:isAccuracyEffect(weapon, kind)
    
    DarkGod's avatar
    DarkGod committed
    	if not weapon then return false, "none" end
    
    	local eff = weapon.accuracy_effect or weapon.talented
    	return eff == kind, eff
    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
    
    	if self:attr("force_melee_damtype") then
    		damtype = self.force_melee_damtype
    	end
    
    
    	--Life Steal
    	if weapon and weapon.lifesteal then
    		self:attr("lifesteal", weapon.lifesteal)
    		self:attr("silent_heal", 1)
    	end
    
    dg's avatar
    dg committed
    
    
    	local mode = "other"
    	if self:hasShield() then mode = "shield"
    
    	elseif self:hasTwoHandedWeapon() then mode = "twohanded"
    
    	elseif self:hasDualWeapon() then mode = "dualwield"
    	end
    
    dg's avatar
    dg committed
    	self.turn_procs.weapon_type = {kind=weapon and weapon.talented or "unknown", mode=mode}
    
    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
    
    	
    	if self:hasEffect(self.EFF_WARDEN_S_FOCUS) then
    		local eff = self:hasEffect(self.EFF_WARDEN_S_FOCUS)
    		if target == eff.target then
    			atk = atk + eff.atk
    		end
    	end
    
    
    dg's avatar
    dg committed
    
    
    	-- 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)
    
    
    	-- 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
    
    DarkGod's avatar
    DarkGod committed
    	local old_target_life = target.life
    
    DarkGod's avatar
    DarkGod committed
    
    
    	if target:knowTalent(target.T_SKIRMISHER_BUCKLER_EXPERTISE) then
    		local t = target:getTalentFromId(target.T_SKIRMISHER_BUCKLER_EXPERTISE)
    		if t.shouldEvade(target, t) then
    
    DarkGod's avatar
    DarkGod committed
    			game.logSeen(target, "#ORCHID#%s cleverly deflects the attack with %s shield!#LAST#", target.name:capitalize(), string.his_her(target))
    
    			t.onEvade(target, t, self)
    			repelled = true
    		end
    
    DarkGod's avatar
    DarkGod committed
    	end
    
    
    DarkGod's avatar
    DarkGod committed
    	if target:hasEffect(target.EFF_WEAPON_WARDING) then
    		local e = target.tempeffect_def[target.EFF_WEAPON_WARDING]
    		if e.do_block(target, target.tmp[target.EFF_WEAPON_WARDING], self) then
    			repelled = true
    		end
    	end
    
    
    	if repelled then
    
    DarkGod's avatar
    DarkGod committed
    		self:logCombat(target, "#Target# repels an attack from #Source#.")
    
    	elseif self:checkEvasion(target) then
    
    dg's avatar
    dg committed
    		evaded = true
    
    DarkGod's avatar
    DarkGod committed
    		self:logCombat(target, "#Target# evades #Source#.")
    
    DarkGod's avatar
    DarkGod committed
    	elseif not self.turn_procs.auto_melee_hit and self:attr("hit_penalty_2h") and rng.percent(20 - (self.size_category - 4) * 5) then
    
    DarkGod's avatar
    DarkGod committed
    		self:logCombat(target, "#Source# misses #Target#.")
    
    DarkGod's avatar
    DarkGod committed
    		target:fireTalentCheck("callbackOnMeleeMiss", self, dam)
    
    DarkGod's avatar
    DarkGod committed
    	elseif self.turn_procs.auto_melee_hit or (self:checkHit(atk, def) and (self:canSee(target) or self:attr("blind_fight") or target:attr("blind_fighted") or rng.chance(3))) then
    
    dg's avatar
    dg committed
    		local pres = util.bound(target:combatArmorHardiness() / 100, 0, 1)
    
    		if target.knowTalent and target:hasEffect(target.EFF_DUAL_WEAPON_DEFENSE) then
    
    DarkGod's avatar
    DarkGod committed
    			local deflect = math.min(dam, target:callTalent(target.T_DUAL_WEAPON_DEFENSE, "doDeflect"))
    
    			if deflect > 0 then
    
    DarkGod's avatar
    DarkGod committed
    				game:delayedLogDamage(self, target, 0, ("%s(%d parried#LAST#)"):format(DamageType:get(damtype).text_color or "#aaaaaa#", deflect), false)
    
    				dam = math.max(dam - deflect,0)
    				print("[ATTACK] after DUAL_WEAPON_DEFENSE", dam)
    
    DarkGod's avatar
    DarkGod committed
    		if target.knowTalent and target:hasEffect(target.EFF_GESTURE_OF_GUARDING) and not target:attr("encased_in_ice") then
    			local deflected = math.min(dam, target:callTalent(target.T_GESTURE_OF_GUARDING, "doGuard")) or 0
    
    DarkGod's avatar
    DarkGod committed
    			if deflected > 0 then
    				game:delayedLogDamage(self, target, 0, ("%s(%d gestured#LAST#)"):format(DamageType:get(damtype).text_color or "#aaaaaa#", deflected), false)
    				dam = dam - deflected
    			end
    
    			print("[ATTACK] after GESTURE_OF_GUARDING", dam)
    		end
    
    
    		if self:isAccuracyEffect(weapon, "knife") then
    			local bonus = 1 + self:getAccuracyEffect(weapon, atk, def, 0.005, 0.25)
    			print("[ATTACJ] dagger accuracy bonus", atk, def, "=", bonus, "previous", apr)
    			apr = apr * bonus
    		end
    
    
    		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)
    		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 self:isAccuracyEffect(weapon, "mace") then
    			local bonus = 1 + self:getAccuracyEffect(weapon, atk, def, 0.001, 0.1)
    			print("[ATTACK] mace accuracy bonus", atk, def, "=", bonus)
    			dam = dam * bonus
    		end
    
    
    		if target:hasEffect(target.EFF_COUNTERSTRIKE) then
    
    			dam = target:callEffect(target.EFF_COUNTERSTRIKE, "onStrike", dam, self)
    
    			print("[ATTACK] after counterstrike", dam)
    		end
    
    
    		if weapon and weapon.inc_damage_type then
    
    DarkGod's avatar
    DarkGod committed
    			local inc = 0
    
    			for k, v in pairs(weapon.inc_damage_type) do
    				if target:checkClassification(tostring(k)) then inc = math.max(inc, v) end
    
    DarkGod's avatar
    DarkGod committed
    			dam = dam + dam * inc / 100
    
    			--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
    
    dg's avatar
    dg committed
    
    
    DarkGod's avatar
    DarkGod committed
    		if crit then self:logCombat(target, "#{bold}##Source# performs a melee critical strike against #Target#!#{normal}#") end
    
    
    		-- Phasing, percent of weapon damage bypasses shields
    
    		-- It's done like this because onTakeHit has no knowledge of the weapon
    
    		if weapon and weapon.phasing then
    			self:attr("damage_shield_penetrate", weapon.phasing)
    		end
    
    		local oldproj = DamageType:getProjectingFor(self)
    		if self.__talent_running then DamageType:projectingFor(self, {project_type={talent=self.__talent_running}}) end
    
    DarkGod's avatar
    DarkGod committed
    		if weapon and weapon.crushing_blow then self:attr("crushing_blow", 1) end
    
    
    		-- Damage conversion?
    		-- Reduces base damage but converts it into another damage type
    		local conv_dam
    		local conv_damtype
    
    		if weapon and weapon.convert_damage then
    			for typ, conv in pairs(weapon.convert_damage) do
    				if dam > 0 then
    					conv_dam = math.min(dam, dam * (conv / 100))
    
    					total_conversion = total_conversion + conv_dam
    
    					conv_damtype = typ
    					dam = dam - conv_dam
    
    					if conv_dam > 0 then
    						DamageType:get(conv_damtype).projector(self, target.x, target.y, conv_damtype, math.max(0, conv_dam))
    					end
    
    				end
    			end
    		end
    
    		if dam > 0 then
    			DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam))
    		end
    
    DarkGod's avatar
    DarkGod committed
    		if weapon and weapon.crushing_blow then self:attr("crushing_blow", -1) end
    
    		if self.__talent_running then DamageType:projectingFor(self, oldproj) end
    
    
    		if weapon and weapon.phasing then
    			self:attr("damage_shield_penetrate", -weapon.phasing)
    		end
    
    
    		-- add damage conversion back in so the total damage still gets passed
    		if total_conversion > 0 then
    			dam = dam + total_conversion
    		end
    
    		target:fireTalentCheck("callbackOnMeleeHit", self, dam)
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    		hitted = true
    
    DarkGod's avatar
    DarkGod committed
    
    
    		if self:attr("vim_on_melee") and self ~= target then self:incVim(self:attr("vim_on_melee")) end
    
    dg's avatar
    dg committed
    	else
    
    DarkGod's avatar
    DarkGod committed
    		self:logCombat(target, "#Source# misses #Target#.")
    
    		target:fireTalentCheck("callbackOnMeleeMiss", self, dam)
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    
    
    	-- cross-tier effect for accuracy vs. defense
    
    	local tier_diff = self:getTierDiff(atk, target:combatDefense(false, target:attr("combat_def_ct")))
    
    	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)
    
    
    	if self:isAccuracyEffect(weapon, "staff") then
    
    		local bonus = 1 + self:getAccuracyEffect(weapon, atk, def, 0.025, 2)
    
    		print("[ATTACK] staff accuracy bonus", atk, def, "=", bonus)
    		self.__global_accuracy_damage_bonus = bonus
    	end
    
    
    	-- 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(self:callTalent(self.T_CARRIER, "getDiseaseSpread")) then
    
    dg's avatar
    dg committed
    		-- Use epidemic talent spreading
    
    		self:callTalent(self.T_EPIDEMIC, "do_spread", target, dam)
    
    dg's avatar
    dg committed
    	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
    	-- 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
    
    	-- 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
    
    	
    	-- Temporal Cast
    	if hitted and self:knowTalent(self.T_WEAPON_FOLDING) and self:isTalentActive(self.T_WEAPON_FOLDING) then
    		self:callTalent(self.T_WEAPON_FOLDING, "doWeaponFolding", target)
    	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
    
    
    dg's avatar
    dg committed
    	-- On hit talent
    
    	if hitted and not target.dead and weapon and weapon.talent_on_hit and next(weapon.talent_on_hit) and not self.turn_procs.melee_talent then
    
    		for tid, data in pairs(weapon.talent_on_hit) do
    			if rng.percent(data.chance) then
    
    				self.turn_procs.melee_talent = true
    
    				self:forceUseTalent(tid, {ignore_cd=true, ignore_energy=true, force_target=target, force_level=data.level, ignore_ressources=true})
    
    	-- On crit talent
    	if hitted and crit and not target.dead and weapon and weapon.talent_on_crit and next(weapon.talent_on_crit) and not self.turn_procs.melee_talent then
    		for tid, data in pairs(weapon.talent_on_crit) do
    			if rng.percent(data.chance) then
    				self.turn_procs.melee_talent = true
    				self:forceUseTalent(tid, {ignore_cd=true, ignore_energy=true, force_target=target, force_level=data.level, ignore_ressources=true})
    			end
    		end
    	end
    
    	-- 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
    
    		game.logSeen(target, "The shattering blow creates a shockwave!")
    
    		self:project({type="ball", radius=1, selffire=false, act_exclude={[target.uid]=true}}, target.x, target.y, DamageType.PHYSICAL, dam)  -- don't hit target with the AOE
    
    		self:incStamina(-8)
    
    		self.shattering_impact_last_turn = game.turn
    
    DarkGod's avatar
    DarkGod committed
    	if dam > 0 and self:attr("damage_backfire") then
    		local hurt = math.min(dam, old_target_life) * self.damage_backfire / 100
    
    		if hurt > 0 then
    			self:takeHit(hurt, self)
    		end
    	end
    
    	-- Burst on Hit
    
    	if hitted and weapon and weapon.burst_on_hit then
    
    		for typ, dam in pairs(weapon.burst_on_hit) do
    
    			if dam > 0 then
    				self:project({type="ball", radius=1, friendlyfire=false}, target.x, target.y, typ, dam)
    			end
    
    		end
    	end
    
    	-- Critical Burst (generally more damage then burst on hit and larger radius)
    
    	if hitted and crit and weapon and weapon.burst_on_crit then
    
    		for typ, dam in pairs(weapon.burst_on_crit) do
    
    			if dam > 0 then
    				self:project({type="ball", radius=2, friendlyfire=false}, target.x, target.y, typ, dam)
    			end
    
    dg's avatar
    dg committed
    	-- Arcane Destruction
    
    	if hitted and crit and weapon and self:knowTalent(self.T_ARCANE_DESTRUCTION) then
    
    DarkGod's avatar
    DarkGod committed
    		local chance = 100
    
    		if self:hasShield() then chance = 75
    
    DarkGod's avatar
    DarkGod committed
    		elseif self:hasDualWeapon() then chance = 50
    
    DarkGod's avatar
    DarkGod committed
    		if rng.percent(chance) then
    
    			local t = self:getTalentFromId(self.T_ARCANE_DESTRUCTION)
    
    DarkGod's avatar
    DarkGod committed
    			local typ = rng.table{{DamageType.FIRE,"ball_fire"}, {DamageType.LIGHTNING,"ball_lightning_beam"}, {DamageType.ARCANE,"ball_arcane"}}
    
    			self:project({type="ball", radius=self:getTalentRadius(t), friendlyfire=false}, target.x, target.y, typ[1], self:combatSpellpower() * 2 * t.getDamMult(self, t))
    			game.level.map:particleEmitter(target.x, target.y, self:getTalentRadius(t), typ[2], {radius=2, tx=target.x, ty=target.y})
    
    DarkGod's avatar
    DarkGod committed
    		end
    
    	-- Onslaught
    	if hitted and self:attr("onslaught") then
    
    dg's avatar
    dg committed
    		local dir = util.getDir(target.x, target.y, self.x, self.y) or 6
    		local lx, ly = util.coordAddDir(self.x, self.y, util.dirSides(dir, self.x, self.y).left)
    		local rx, ry = util.coordAddDir(self.x, self.y, util.dirSides(dir, self.x, self.y).right)
    
    		local lt, rt = game.level.map(lx, ly, Map.ACTOR), game.level.map(rx, ry, Map.ACTOR)
    
    
    dg's avatar
    dg committed
    		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 type(dam) == "number" then if dam > 0 then DamageType:get(typ).projector(target, self.x, self.y, typ, dam) end
    		elseif dam.dam and dam.dam > 0 then DamageType:get(typ).projector(target, self.x, self.y, typ, dam)
    
    dg's avatar
    dg committed
    		end
    
    dg's avatar
    dg committed
    	end end
    
    
    dg's avatar
    dg committed
    	-- Acid splash
    
    DarkGod's avatar
    DarkGod committed
    	if hitted and not target.dead and target:knowTalent(target.T_ACID_BLOOD) then
    
    dg's avatar
    dg committed
    		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
    
    
    	-- Psi Auras
    	local psiweapon = self:getInven("PSIONIC_FOCUS") and self:getInven("PSIONIC_FOCUS")[1]
    	if psiweapon and psiweapon.combat and psiweapon.subtype ~= "mindstar"  then
    
    		if hitted and not target.dead and self:knowTalent(self.T_KINETIC_AURA) and self:isTalentActive(self.T_KINETIC_AURA) and self.use_psi_combat then
    
    			local t = self:getTalentFromId(self.T_KINETIC_AURA)
    			t.do_combat(self, t, target)
    		end
    
    		if hitted and not target.dead and self:knowTalent(self.T_THERMAL_AURA) and self:isTalentActive(self.T_THERMAL_AURA) and self.use_psi_combat then
    
    			local t = self:getTalentFromId(self.T_THERMAL_AURA)
    			t.do_combat(self, t, target)
    		end
    
    		if hitted and not target.dead and self:knowTalent(self.T_CHARGED_AURA) and self:isTalentActive(self.T_CHARGED_AURA) and self.use_psi_combat then
    
    			local t = self:getTalentFromId(self.T_CHARGED_AURA)
    			t.do_combat(self, t, target)
    		end
    	end
    
    	-- Static dis-Charge
    	if hitted and not target.dead and self:hasEffect(self.EFF_STATIC_CHARGE) then
    		local eff = self:hasEffect(self.EFF_STATIC_CHARGE)
    		DamageType:get(DamageType.LIGHTNING).projector(self, target.x, target.y, DamageType.LIGHTNING, eff.power)
    		self:removeEffect(self.EFF_STATIC_CHARGE)
    
    dg's avatar
    dg committed
    	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
    	-- Lacerating Strikes
    
    	if hitted and not target.dead and self:isTalentActive(self.T_LACERATING_STRIKES) then
    
    dg's avatar
    dg committed
    		local t = self:getTalentFromId(self.T_LACERATING_STRIKES)
    		t.do_cut(self, t, target, dam)
    	end
    
    	-- Scoundrel's Strategies
    	if hitted and not target.dead and self:knowTalent(self.T_SCOUNDREL) and target:hasEffect(target.EFF_CUT) then
    		local t = self:getTalentFromId(self.T_SCOUNDREL)
    		t.do_scoundrel(self, t, target)
    	end
    
    
    dg's avatar
    dg committed
    	-- Special effect
    
    	if hitted and weapon and weapon.special_on_hit then
    		local specials = weapon.special_on_hit
    		if specials.fct then specials = {specials} end
    		for _, special in ipairs(specials) do
    			if special.fct and (not target.dead or special.on_kill) then
    				special.fct(weapon, self, target, dam)
    			end
    		end
    	end
    
    	if hitted and crit and weapon and weapon.special_on_crit then
    		local specials = weapon.special_on_crit
    		if specials.fct then specials = {specials} end
    		for _, special in ipairs(specials) do
    			if special.fct and (not target.dead or special.on_kill) then
    				special.fct(weapon, self, target, dam)
    			end
    		end
    
    dg's avatar
    dg committed
    	end
    
    
    	if hitted and weapon and weapon.special_on_kill and target.dead then
    		local specials = weapon.special_on_kill
    		if specials.fct then specials = {specials} end
    		for _, special in ipairs(specials) do
    			if special.fct then
    				special.fct(weapon, self, target, dam)
    			end
    		end
    	end
    
    dg's avatar
    dg committed
    	if hitted and weapon and weapon.special_on_kill and weapon.special_on_kill.fct and target.dead then
    
    DarkGod's avatar
    DarkGod committed
    		weapon.special_on_kill.fct(weapon, self, target, dam)
    
    dg's avatar
    dg committed
    	end
    
    DarkGod's avatar
    DarkGod committed
    	if hitted and crit and not target.dead and self:knowTalent(self.T_BACKSTAB) and not target:attr("stunned") and rng.percent(self:callTalent(self.T_BACKSTAB, "getStunChance")) then
    
    		if target:canBe("stun") then
    			target:setEffect(target.EFF_STUNNED, 3, {apply_power=self:combatAttack()})
    		end
    	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 self ~= target then
    		if hitted and not target.dead and target:attr("stamina_regen_when_hit") then target:incStamina(target.stamina_regen_when_hit) end
    		if hitted and not target.dead and target:attr("mana_regen_when_hit") then target:incMana(target.mana_regen_when_hit) end
    		if hitted and not target.dead and target:attr("equilibrium_regen_when_hit") then target:incEquilibrium(-target.equilibrium_regen_when_hit) end
    		if hitted and not target.dead and target:attr("psi_regen_when_hit") then target:incPsi(target.psi_regen_when_hit) end
    		if hitted and not target.dead and target:attr("hate_regen_when_hit") then target:incHate(target.hate_regen_when_hit) end
    		if hitted and not target.dead and target:attr("vim_regen_when_hit") then target:incVim(target.vim_regen_when_hit) end
    
    		-- Resource regen on hit
    		if hitted and self:attr("stamina_regen_on_hit") then self:incStamina(self.stamina_regen_on_hit) end
    		if hitted and self:attr("mana_regen_on_hit") then self:incMana(self.mana_regen_on_hit) end
    		if hitted and self:attr("psi_regen_on_hit") then self:incPsi(self.psi_regen_on_hit) end
    	end
    
    Eric Wykoff's avatar
    Eric Wykoff 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
    		else
    			-- Deactivate without loosing energy
    			target:forceUseTalent(target.T_CARBON_SPIKES, {ignore_energy=true})
    		end
    	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
    	-- 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!
    
    DarkGod's avatar
    DarkGod committed
    	if not hitted and not target.dead and target:knowTalent(target.T_COUNTER_ATTACK) and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:knowTalent(target.T_COUNTER_ATTACK) and self:isNear(target.x,target.y, 1) then --Adjacency check
    
    		local cadam = target:callTalent(target.T_COUNTER_ATTACK,"checkCounterAttack")
    		if cadam then
    			game.logSeen(self, "%s counters the attack!", target.name:capitalize())
    			target:attackTarget(self, nil, cadam, true)
    
    	-- Gesture of Guarding counterattack
    
    	if hitted and not target.dead and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:hasEffect(target.EFF_GESTURE_OF_GUARDING) then
    
    		local t = target:getTalentFromId(target.T_GESTURE_OF_GUARDING)
    		t.on_hit(target, t, self)
    	end
    
    
    	-- Defensive Throw!
    
    DarkGod's avatar
    DarkGod committed
    	if not hitted and not target.dead and target:knowTalent(target.T_DEFENSIVE_THROW) and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:isNear(self.x,self.y,1) 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
    
    dg's avatar
    dg committed
    	-- Roll with it
    	if hitted and target:attr("knockback_on_hit") and not target.turn_procs.roll_with_it and rng.percent(util.bound(dam, 0, 100)) then
    		local ox, oy = self.x, self.y
    
    		game:onTickEnd(function()
    			target:knockback(ox, oy, 1)
    
    			if not target:hasEffect(target.EFF_WILD_SPEED) then target:setEffect(target.EFF_WILD_SPEED, 1, {power=200}) end
    		end)
    
    dg's avatar
    dg committed
    		target.turn_procs.roll_with_it = true
    	end
    
    
    	-- Weakness hate bonus
    	if hitted and effGloomWeakness and effGloomWeakness.hateBonus or 0 > 0 then
    		self:incHate(effGloomWeakness.hateBonus)
    		game.logPlayer(self, "#F53CBE#You revel in attacking a weakened foe! (+%d hate)", effGloomWeakness.hateBonus)
    		effGloomWeakness.hateBonus = nil
    	end
    
    	-- Rampage
    	if hitted and crit then
    		local eff = self:hasEffect(self.EFF_RAMPAGE)
    
    		if eff and not eff.critHit and eff.actualDuration < eff.maxDuration and self:knowTalent(self.T_BRUTALITY) then
    
    			game.logPlayer(self, "#F53CBE#Your rampage is invigorated by your fierce attack! (+1 duration)")
    			eff.critHit = true
    			eff.actualDuration = eff.actualDuration + 1
    			eff.dur = eff.dur + 1
    		end
    	end
    
    	-- Marked Prey
    	if hitted and not target.dead and effPredator and effPredator.type == target.type then
    		if effPredator.subtype == target.subtype then
    			-- Anatomy stun
    			if effPredator.subtypeStunChance > 0 and rng.percent(effPredator.subtypeStunChance) then
    				if target:canBe("stun") then
    					target:setEffect(target.EFF_STUNNED, 3, {})
    				else
    					game.logSeen(target, "%s resists the stun!", target.name:capitalize())
    				end
    			end
    
    			-- Outmaneuver
    			if effPredator.subtypeOutmaneuverChance > 0 and rng.percent(effPredator.subtypeOutmaneuverChance) then
    				local t = self:getTalentFromId(self.T_OUTMANEUVER)
    
    				target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { physicalResistChange=t.getPhysicalResistChange(self, t), statReduction=t.getStatReduction(self, t) })
    
    			end
    		else
    			-- Outmaneuver
    			if effPredator.typeOutmaneuverChance > 0 and rng.percent(effPredator.typeOutmaneuverChance) then
    				local t = self:getTalentFromId(self.T_OUTMANEUVER)
    
    dg's avatar
    dg committed
    				target:setEffect(target.EFF_OUTMANEUVERED, t.getDuration(self, t), { physicalResistChange=t.getPhysicalResistChange(self, t), statReduction=t.getStatReduction(self, t) })
    
    	if hitted and crit and target:hasEffect(target.EFF_DISMAYED) then
    		target:removeEffect(target.EFF_DISMAYED)
    	end
    
    	if hitted and not target.dead then