You need to sign in or sign up before continuing.
Newer
Older
--
-- 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
--- 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)
-- Talk ? Bump ?
if self.player and target.on_bump then
target:on_bump(self)
elseif self.player and target.can_talk then
if target.can_talk_only_once then target.can_talk = nil end
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
dg
committed
-- 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
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
local energy = game.energy_to_act * self:combatMovementSpeed(x, y)
energy = energy / self:attr("bump_swap_speed_divide")
self:useEnergy(energy)
self.did_energy = true
end
end
end
--- Makes the death happen!
- attack: increases chances to hit against high defense
- defense: increases chances to miss against high attack power
- armor: direct reduction of damage done
- armor penetration: reduction of target's armor
- damage: raw damage done
]]
-- Attempts to attack a target with all melee weapons (calls self:attackTargetWith for each)
-- @param target - target actor
-- @param damtype a damage type ID <PHYSICAL>
-- @param mult a damage multiplier <1>
-- @noenergy if true the attack uses no energy
-- @force_unarmed if true the attacker uses unarmed (innate) combat parameters
-- @return true if an attack hit the target, false otherwise
function _M:attackTarget(target, damtype, mult, noenergy, force_unarmed)
-- Break before we do the blow, because it might start step up, we dont want to insta-cancel it
self:breakStepUp()
self:useEnergy(game.energy_to_act)
self.did_energy = true
end
game.logSeen(self, "%s is too afraid to attack.", self.name:capitalize())
return false
end
dg
committed
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
-- 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
if self.player then self:logCombat(target, "#Target# notices you at the last moment!") end
DarkGod
committed
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
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
DarkGod
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
local damtype = table.get(gems[i], 'color_attributes', 'damage_type')
if damtype then table.insert(types, damtype) end
elseif not damtype and self:attr("force_melee_damage_type") then
damtype = self:attr("force_melee_damage_type")
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
end
if not speed and self:isTalentActive(self.T_GESTURE_OF_PAIN) then
if self.no_gesture_of_pain_recurse then return false end
dg
committed
print("[ATTACK] attacking with Gesture of Pain")
local t = self:getTalentFromId(self.T_GESTURE_OF_PAIN)
dg
committed
speed, hit = t.attack(self, t, target)
dg
committed
break_stealth = true
end
if not speed and not self:attr("disarmed") and not self:isUnarmed() and not force_unarmed then
local double_weapon
-- All weapons in main hands
if self:getInven(self.INVEN_MAINHAND) then
for i, o in ipairs(self:getInven(self.INVEN_MAINHAND)) do
local combat = self:getObjectCombat(o, "mainhand")
if combat and not o.archery then
if o.double_weapon and not double_weapon then double_weapon = o end
print("[ATTACK] attacking with (mainhand)", o.name)
speed = math.max(speed or 0, s)
hit = hit or h
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
local oh_weaps, offhand = table.clone(self:getInven(self.INVEN_OFFHAND)) or {}, false
-- Offhand attacks are with a damage penalty, that can be reduced by talents
if double_weapon then oh_weaps[#oh_weaps+1] = double_weapon end -- use double weapon as OFFHAND if there are no others
for i = 1, #oh_weaps do
if i == #oh_weaps and double_weapon and offhand then break end
local o = oh_weaps[i]
local offmult = self:getOffHandMult(o.combat, mult)
local combat = self:getObjectCombat(o, "offhand")
if o.special_combat and o.subtype == "shield" and self:knowTalent(self.T_STONESHIELD) then combat = o.special_combat end
-- no offhand unarmed attacks
if combat and not o.archery then
if combat.use_resources and not self:useResources(combat.use_resources, true) then
print("[ATTACK] Cancelling attack (offhand) with", o.name , "(resources)")
else
offhand = true
print("[ATTACK] attacking with (offhand)", o.name)
local s, h = self:attackTargetWith(target, combat, damtype, offmult)
speed = math.max(speed or 0, s)
hit = hit or h
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
local combat = self:getObjectCombat(nil, "barehand")
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
if speed and not noenergy then
self:useEnergy(game.energy_to_act * speed)
self.did_energy = true
end
if sound then game:playSoundNear(self, sound)
elseif sound_miss then game:playSoundNear(self, sound_miss) end
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
if self:isTalentActive(self.T_CLEAVE) then
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
--- Determines the combat field to use for this item
function _M:getObjectCombat(o, kind)
if kind == "barehand" then return self.combat end
if kind == "mainhand" then return o.combat end
if kind == "offhand" then return o.combat end
return nil
end
--- 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
if atk < 0 then atk = 0 end
if def < 0 then def = 0 end
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
hit = 50 * (one + two)
--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
local save = self[apply_save or save_for_effects[e.type]](self, true)
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)
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
if game.player:hasQuest("tutorial-combat-stats") then
min = 0
max = 100
end --ensures predictable combat for the tutorial
local hit = math.ceil(50 + 2.5 * (atk - def))
print("checkHit", atk, "vs", def, "=> chance to hit", hit)
if not target:attr("evasion") or self == target then return end
if target:attr("no_evasion") then return end
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)
local eff = weapon.accuracy_effect or weapon.talented
return eff == kind, eff
end
function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
-- if insufficient resources, try to use unarmed or cancel attack
local unarmed = self:getObjectCombat(nil, "barehand")
if (weapon or unarmed).use_resources and not self:useResources((weapon or unarmed).use_resources) then
-- if weapon.use_resources and not self:useResources(weapon.use_resources) then
-- local unarmed = self:getObjectCombat(nil, "barehand")
if unarmed == weapon then
print("[attackTargetWith] (unarmed) against ", target.name, "unarmed attack fails due to resources")
return self:combatSpeed(unarmed), false, 0
else
weapon = unarmed
print("[attackTargetWith] against ", target.name, "insufficient weapon resources, using unarmed combat")
if weapon.use_resources and not self:useResources(weapon.use_resources) then
print("[attackTargetWith] against ", target.name, "unarmed attack fails due to resources")
return self:combatSpeed(weapon), false, 0
end
end
end
damtype = damtype or (weapon and weapon.damtype) or DamageType.PHYSICAL
DarkGod
committed
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
local mode = "other"
if self:hasShield() then mode = "shield"
DarkGod
committed
elseif self:hasTwoHandedWeapon() then mode = "twohanded"
elseif self:hasDualWeapon() then mode = "dualwield"
end
self.turn_procs.weapon_type = {kind=weapon and weapon.talented or "unknown", mode=mode}
local atk, def = self:combatAttack(weapon), target:combatDefense()
-- 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
dg
committed
-- 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
dg
committed
-- track weakness for hate bonus before the target removes it
local effGloomWeakness = target:hasEffect(target.EFF_GLOOM_WEAKNESS)
local dam, apr, armor = force_dam or self:combatDamage(weapon), self:combatAPR(weapon), target:combatArmor()
print("[ATTACK] to ", target.name, " :: ", dam, apr, armor, atk, "vs.", 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
if target:knowTalent(target.T_SKIRMISHER_BUCKLER_EXPERTISE) then
local t = target:getTalentFromId(target.T_SKIRMISHER_BUCKLER_EXPERTISE)
if t.shouldEvade(target, t) then
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
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 target:knowTalent(target.T_BLADE_WARD) and target:hasDualWeapon() then
game.logSeen(target, "#ORCHID#%s parries the attack with %s dual weapons!#LAST#", target.name:capitalize(), string.his_her(target))
repelled = true
end
end
self:logCombat(target, "#Target# repels an attack from #Source#.")
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
if target.knowTalent and target:hasEffect(target.EFF_DUAL_WEAPON_DEFENSE) then
local deflect = math.min(dam, target:callTalent(target.T_DUAL_WEAPON_DEFENSE, "doDeflect"))
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
committed
end
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
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("[ATTACK] dagger accuracy bonus", atk, def, "=", bonus, "previous", apr)
apr = apr * bonus
end
print("[ATTACK] raw dam", dam, "versus", armor, pres, "with APR", apr)
dam = math.max(dam * pres - armor, 0) + (dam * (1 - pres))
local damrange = self:combatDamageRange(weapon)
dam = rng.range(dam, dam * damrange)
print("[ATTACK] after range", 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
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
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
if crit then self:logCombat(target, "#{bold}##Source# performs a melee critical strike against #Target#!#{normal}#") end
-- Phasing, percent of weapon damage bypasses shields
dg
committed
-- 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
DarkGod
committed
local oldproj = DamageType:getProjectingFor(self)
if self.__talent_running then DamageType:projectingFor(self, {project_type={talent=self.__talent_running}}) end
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
dg
committed
local total_conversion = 0
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))
dg
committed
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
committed
if weapon and weapon.crushing_blow then self:attr("crushing_blow", -1) end
if self.__talent_running then DamageType:projectingFor(self, oldproj) end
dg
committed
-- remove phasing
if weapon and weapon.phasing then
self:attr("damage_shield_penetrate", -weapon.phasing)
end
dg
committed
-- 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)
if self:attr("vim_on_melee") and self ~= target then self:incVim(self:attr("vim_on_melee")) end
target:fireTalentCheck("callbackOnMeleeMiss", self, dam)
-- 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)
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
hitted = self:attackTargetHitProcs(target, weapon, dam, apr, armor, damtype, mult, atk, def, hitted, crit, evaded, repelled, old_target_life)
-- Visual feedback
if hitted then game.level.map:particleEmitter(target.x, target.y, 1, "melee_attack", {color=target.blood_color}) end
if Map.tiles and Map.tiles.use_images then if self.x and target.x then if target.x < self.x then self:MOflipX(self:isTileFlipped()) elseif target.x > self.x then self:MOflipX(not self:isTileFlipped()) end end end
self.turn_procs.weapon_type = nil
--Life Steal
if weapon and weapon.lifesteal then
self:attr("lifesteal", -weapon.lifesteal)
self:attr("silent_heal", -1)
end
if self.__attacktargetwith_recursing or (weapon and weapon.attack_recurse) then
if self.__attacktargetwith_recursing then
self.__attacktargetwith_recursing = self.__attacktargetwith_recursing - 1
else
self.__attacktargetwith_recursing = weapon.attack_recurse - 1
end
if self.__attacktargetwith_recursing > 0 then
local _, newhitted, newdam = self:attackTargetWith(target, weapon, damtype, mult, force_dam)
hitted = newhitted or hitted
dam = math.max(dam, newdam)
else
self.__attacktargetwith_recursing = nil
end
end
return self:combatSpeed(weapon), hitted, dam
end
function _M:attackTargetHitProcs(target, weapon, dam, apr, armor, damtype, mult, atk, def, hitted, crit, evaded, repelled, old_target_life)
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
if self:attr("hit_penalty_2h") then
self.__global_accuracy_damage_bonus = self.__global_accuracy_damage_bonus or 1
self.__global_accuracy_damage_bonus = self.__global_accuracy_damage_bonus * 0.5
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)
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
if hitted and self:knowTalent(self.T_CARRIER) and rng.percent(self:callTalent(self.T_CARRIER, "getDiseaseSpread")) then
self:callTalent(self.T_EPIDEMIC, "do_spread", target, dam)
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
if dam > 0 then
DamageType:get(typ).projector(self, target.x, target.y, typ, dam)
end
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
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
committed
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)
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
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!")
Hachem_Muche
committed
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.shattering_impact_last_turn = game.turn
DarkGod
committed
-- Damage Backlash
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
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
if hitted and crit and weapon and self:knowTalent(self.T_ARCANE_DESTRUCTION) then
if self:hasShield() then chance = 75
local t = self:getTalentFromId(self.T_ARCANE_DESTRUCTION)
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})
-- Onslaught
if hitted and self:attr("onslaught") then
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)
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"))
target:crossTierEffect(target.EFF_OFFBALANCE, self:combatAttack())
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"))
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"))
-- Reactive target on hit damage
if hitted then for typ, dam in pairs(target.on_melee_hit) do
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)
if hitted and not target.dead 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
-- 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")})
-- 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)
-- 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
if hitted and not target.dead and self:isTalentActive(self.T_LACERATING_STRIKES) then
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
Hachem_Muche
committed
-- Special weapon effects (passing the special definition to facilitate encapsulating multiple special effects)
DarkGod
committed
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
Hachem_Muche
committed
special.fct(weapon, self, target, dam, special)
DarkGod
committed
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
Hachem_Muche
committed
special.fct(weapon, self, target, dam, special)
DarkGod
committed
end
end
DarkGod
committed
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
Hachem_Muche
committed
special.fct(weapon, self, target, dam, special)
DarkGod
committed
end
end
end
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
dg
committed
if target:canBe("stun") then
target:setEffect(target.EFF_STUNNED, 3, {apply_power=self:combatAttack()})
end
end
if hitted and not target.dead and self.vile_poisons and next(self.vile_poisons) and target:canBe("poison") then
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
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
-- 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
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
-- 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
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)
DarkGod
committed
end
end
-- 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
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
game.logSeen(self, "%s focuses and gains an extra blow!", self.name:capitalize())
self:attackTargetWith(target, weapon, damtype, mult)
gwf.inside = nil
end